Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml67
-rw-r--r--.rspec1
-rw-r--r--.rubocop.yml2
-rw-r--r--CHANGELOG105
-rw-r--r--CONTRIBUTING.md16
-rw-r--r--Gemfile109
-rw-r--r--Gemfile.lock334
-rw-r--r--Guardfile27
-rw-r--r--README.md4
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/authbuttons/google_64.pngbin3169 -> 5281 bytes
-rw-r--r--app/assets/images/authbuttons/twitter_64.pngbin3054 -> 4835 bytes
-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/javascripts/application.js.coffee33
-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/calendar.js.coffee1
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee24
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee52
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee22
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee20
-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.coffee20
-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.coffee152
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee159
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee58
-rw-r--r--app/assets/javascripts/notes.js.coffee55
-rw-r--r--app/assets/javascripts/profile.js.coffee13
-rw-r--r--app/assets/javascripts/project_new.js.coffee6
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee1
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee4
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee20
-rw-r--r--app/assets/javascripts/wikis.js.coffee18
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee37
-rw-r--r--app/assets/stylesheets/application.scss16
-rw-r--r--app/assets/stylesheets/base/layout.scss2
-rw-r--r--app/assets/stylesheets/base/variables.scss5
-rw-r--r--app/assets/stylesheets/generic/common.scss16
-rw-r--r--app/assets/stylesheets/generic/forms.scss8
-rw-r--r--app/assets/stylesheets/generic/header.scss280
-rw-r--r--app/assets/stylesheets/generic/lists.scss1
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss16
-rw-r--r--app/assets/stylesheets/generic/mobile.scss22
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss119
-rw-r--r--app/assets/stylesheets/generic/typography.scss7
-rw-r--r--app/assets/stylesheets/generic/zen.scss34
-rw-r--r--app/assets/stylesheets/pages/dashboard.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss22
-rw-r--r--app/assets/stylesheets/pages/note_form.scss6
-rw-r--r--app/assets/stylesheets/pages/profile.scss80
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss56
-rw-r--r--app/assets/stylesheets/pages/projects.scss33
-rw-r--r--app/assets/stylesheets/pages/themes.scss0
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss58
-rw-r--r--app/assets/stylesheets/themes/ui_basic.scss8
-rw-r--r--app/assets/stylesheets/themes/ui_blue.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_color.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_gray.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_modern.scss6
-rw-r--r--app/controllers/admin/application_settings_controller.rb3
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb7
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/admin/identities_controller.rb41
-rw-r--r--app/controllers/admin/projects_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb17
-rw-r--r--app/controllers/application_controller.rb15
-rw-r--r--app/controllers/dashboard_controller.rb4
-rw-r--r--app/controllers/groups/group_members_controller.rb6
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb8
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb5
-rw-r--r--app/controllers/passwords_controller.rb2
-rw-r--r--app/controllers/profiles/preferences_controller.rb38
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb13
-rw-r--r--app/controllers/profiles_controller.rb23
-rw-r--r--app/controllers/projects/blob_controller.rb60
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb9
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb46
-rw-r--r--app/controllers/projects/notes_controller.rb19
-rw-r--r--app/controllers/projects/project_members_controller.rb8
-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.rb29
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/root_controller.rb28
-rw-r--r--app/controllers/sessions_controller.rb18
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/finders/README.md2
-rw-r--r--app/finders/issuable_finder.rb106
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb108
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb10
-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.rb94
-rw-r--r--app/helpers/gitlab_routing_helper.rb8
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb38
-rw-r--r--app/helpers/notes_helper.rb14
-rw-r--r--app/helpers/notifications_helper.rb2
-rw-r--r--app/helpers/oauth_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb53
-rw-r--r--app/helpers/projects_helper.rb30
-rw-r--r--app/helpers/submodule_helper.rb2
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/mailers/emails/projects.rb3
-rw-r--r--app/models/ability.rb88
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/commit.rb49
-rw-r--r--app/models/commit_range.rb30
-rw-r--r--app/models/concerns/mentionable.rb23
-rw-r--r--app/models/concerns/participable.rb8
-rw-r--r--app/models/concerns/referable.rb61
-rw-r--r--app/models/concerns/taskable.rb1
-rw-r--r--app/models/external_issue.rb11
-rw-r--r--app/models/group.rb18
-rw-r--r--app/models/group_milestone.rb2
-rw-r--r--app/models/identity.rb1
-rw-r--r--app/models/issue.rb29
-rw-r--r--app/models/label.rb41
-rw-r--r--app/models/merge_request.rb65
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/namespace.rb15
-rw-r--r--app/models/note.rb11
-rw-r--r--app/models/project.rb16
-rw-r--r--app/models/project_services/gitlab_ci_service.rb18
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/project_services/issue_tracker_service.rb15
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb60
-rw-r--r--app/models/snippet.rb33
-rw-r--r--app/models/user.rb98
-rw-r--r--app/services/delete_user_service.rb22
-rw-r--r--app/services/destroy_group_service.rb17
-rw-r--r--app/services/files/base_service.rb80
-rw-r--r--app/services/files/create_service.rb44
-rw-r--r--app/services/files/delete_service.rb33
-rw-r--r--app/services/files/update_service.rb36
-rw-r--r--app/services/git_push_service.rb29
-rw-r--r--app/services/issuable_base_service.rb19
-rw-r--r--app/services/issues/bulk_update_service.rb2
-rw-r--r--app/services/issues/close_service.rb2
-rw-r--r--app/services/issues/create_service.rb1
-rw-r--r--app/services/issues/update_service.rb5
-rw-r--r--app/services/merge_requests/auto_merge_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb19
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb67
-rw-r--r--app/services/projects/participants_service.rb4
-rw-r--r--app/services/search/global_service.rb2
-rw-r--r--app/services/system_note_service.rb102
-rw-r--r--app/services/update_snippet_service.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml103
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--app/views/admin/deploy_keys/index.html.haml3
-rw-r--r--app/views/admin/deploy_keys/show.html.haml35
-rw-r--r--app/views/admin/groups/_form.html.haml3
-rw-r--r--app/views/admin/groups/index.html.haml2
-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/projects/show.html.haml3
-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.haml17
-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.haml374
-rw-r--r--app/views/dashboard/_activities.html.haml1
-rw-r--r--app/views/dashboard/groups/index.html.haml13
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml4
-rw-r--r--app/views/dashboard/milestones/show.html.haml3
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml5
-rw-r--r--app/views/doorkeeper/applications/_form.html.haml30
-rw-r--r--app/views/doorkeeper/applications/new.html.haml7
-rw-r--r--app/views/events/_event.html.haml21
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml3
-rw-r--r--app/views/groups/edit.html.haml3
-rw-r--r--app/views/groups/group_members/_group_member.html.haml5
-rw-r--r--app/views/groups/group_members/index.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/groups/milestones/_milestone.html.haml4
-rw-r--r--app/views/groups/milestones/show.html.haml3
-rw-r--r--app/views/groups/new.html.haml3
-rw-r--r--app/views/groups/show.html.haml3
-rw-r--r--app/views/help/_shortcuts.html.haml2
-rw-r--r--app/views/layouts/_empty_head_panel.html.haml4
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_head_panel.html.haml48
-rw-r--r--app/views/layouts/_page.html.haml7
-rw-r--r--app/views/layouts/_public_head_panel.html.haml22
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml11
-rw-r--r--app/views/layouts/devise.html.haml4
-rw-r--r--app/views/layouts/errors.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml46
-rw-r--r--app/views/layouts/header/_empty.html.haml4
-rw-r--r--app/views/layouts/header/_public.html.haml15
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_profile.html.haml9
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml17
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml4
-rw-r--r--app/views/layouts/project.html.haml8
-rw-r--r--app/views/notify/new_issue_email.text.erb4
-rw-r--r--app/views/notify/new_merge_request_email.text.erb4
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml121
-rw-r--r--app/views/profiles/applications.html.haml62
-rw-r--r--app/views/profiles/design.html.haml54
-rw-r--r--app/views/profiles/emails/index.html.haml34
-rw-r--r--app/views/profiles/keys/_key.html.haml3
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml2
-rw-r--r--app/views/profiles/passwords/edit.html.haml3
-rw-r--r--app/views/profiles/preferences/show.html.haml42
-rw-r--r--app/views/profiles/preferences/update.js.erb9
-rw-r--r--app/views/profiles/show.html.haml14
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml29
-rw-r--r--app/views/profiles/update.js.erb3
-rw-r--r--app/views/projects/_aside.html.haml154
-rw-r--r--app/views/projects/_bitbucket_import_modal.html.haml2
-rw-r--r--app/views/projects/_github_import_modal.html.haml2
-rw-r--r--app/views/projects/_gitlab_import_modal.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_issuable_form.html.haml96
-rw-r--r--app/views/projects/_md_preview.html.haml37
-rw-r--r--app/views/projects/_section.html.haml1
-rw-r--r--app/views/projects/_zen.html.haml2
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/_remove.html.haml10
-rw-r--r--app/views/projects/blob/edit.html.haml8
-rw-r--r--app/views/projects/blob/new.html.haml13
-rw-r--r--app/views/projects/branches/new.html.haml3
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml57
-rw-r--r--app/views/projects/compare/_form.html.haml8
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml16
-rw-r--r--app/views/projects/deploy_keys/show.html.haml14
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml6
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/edit.html.haml45
-rw-r--r--app/views/projects/issues/_discussion.html.haml11
-rw-r--r--app/views/projects/issues/_form.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml78
-rw-r--r--app/views/projects/issues/_issue_context.html.haml46
-rw-r--r--app/views/projects/issues/index.html.haml8
-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.haml4
-rw-r--r--app/views/projects/labels/_label.html.haml4
-rw-r--r--app/views/projects/labels/index.html.haml5
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml7
-rw-r--r--app/views/projects/merge_requests/_form.html.haml7
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml11
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml10
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml49
-rw-r--r--app/views/projects/merge_requests/_show.html.haml61
-rw-r--r--app/views/projects/merge_requests/automerge.js.haml8
-rw-r--r--app/views/projects/merge_requests/index.html.haml13
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml48
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml88
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_ci.html.haml34
-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/show/_remove_source_branch.html.haml17
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml50
-rw-r--r--app/views/projects/merge_requests/update.js.haml11
-rw-r--r--app/views/projects/merge_requests/widget/_closed.html.haml9
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml44
-rw-r--r--app/views/projects/merge_requests/widget/_locked.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml41
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml29
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml20
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml32
-rw-r--r--app/views/projects/merge_requests/widget/open/_archived.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_check.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/open/_conflicts.html.haml9
-rw-r--r--app/views/projects/merge_requests/widget/open/_missing_branch.html.haml (renamed from app/views/projects/merge_requests/show/_no_accept.html.haml)0
-rw-r--r--app/views/projects/merge_requests/widget/open/_no_satellite.html.haml3
-rw-r--r--app/views/projects/merge_requests/widget/open/_not_allowed.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_nothing.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/open/_reload.html.haml1
-rw-r--r--app/views/projects/merge_requests/widget/open/_wip.html.haml13
-rw-r--r--app/views/projects/milestones/_form.html.haml7
-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.haml4
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml32
-rw-r--r--app/views/projects/notes/_form.html.haml9
-rw-r--r--app/views/projects/notes/_note.html.haml9
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml6
-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/project_members/_project_member.html.haml5
-rw-r--r--app/views/projects/project_members/index.html.haml2
-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/update.js.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml6
-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/projects/wikis/_new.html.haml4
-rw-r--r--app/views/search/_form.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml5
-rw-r--r--app/views/shared/_project.html.haml5
-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)32
-rw-r--r--app/views/shared/issuable/_form.html.haml116
-rw-r--r--app/views/shared/issuable/_search_form.html.haml (renamed from app/views/shared/_issuable_search_form.html.haml)2
-rw-r--r--app/views/shared/snippets/_form.html.haml8
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/views/users/show.html.haml5
-rwxr-xr-xbin/guard16
-rwxr-xr-xbin/rake5
-rwxr-xr-xbin/spring11
-rw-r--r--config.ru11
-rw-r--r--config/aws.yml.example3
-rw-r--r--config/gitlab.yml.example40
-rw-r--r--config/initializers/1_settings.rb9
-rw-r--r--config/initializers/6_rack_profiler.rb5
-rw-r--r--config/initializers/7_omniauth.rb10
-rw-r--r--config/initializers/rack_attack.rb.example1
-rw-r--r--config/initializers/session_store.rb6
-rw-r--r--config/initializers/smtp_settings.rb.sample1
-rw-r--r--config/resque.yml.example3
-rw-r--r--config/routes.rb15
-rw-r--r--config/unicorn.rb.example3
-rw-r--r--db/fixtures/development/01_admin.rb2
-rw-r--r--db/fixtures/development/04_project.rb4
-rw-r--r--db/fixtures/development/05_users.rb6
-rw-r--r--db/fixtures/development/07_milestones.rb2
-rw-r--r--db/fixtures/development/09_issues.rb4
-rw-r--r--db/fixtures/development/10_merge_requests.rb4
-rw-r--r--db/fixtures/development/12_snippets.rb4
-rw-r--r--db/fixtures/development/13_comments.rb4
-rw-r--r--db/fixtures/production/001_admin.rb4
-rw-r--r--db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb5
-rw-r--r--db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb5
-rw-r--r--db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb5
-rw-r--r--db/migrate/20150610065936_add_dashboard_to_users.rb9
-rw-r--r--db/migrate/20150620233230_add_default_otp_required_for_login_value.rb11
-rw-r--r--db/schema.rb12
-rw-r--r--doc/README.md17
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/groups.md370
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/namespaces.md44
-rw-r--r--doc/api/users.md6
-rw-r--r--doc/development/db_dump.md5
-rw-r--r--doc/development/shell_commands.md30
-rw-r--r--doc/gitlab_basics/README.md7
-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/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_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/create_your_ssh_keys.md37
-rw-r--r--doc/gitlab_basics/start_using_git.md67
-rw-r--r--doc/install/installation.md20
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/bitbucket.md54
-rw-r--r--doc/integration/ldap.md7
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/saml.md77
-rw-r--r--doc/operations/README.md1
-rw-r--r--doc/operations/unicorn.md86
-rw-r--r--doc/profile/2fa.pngbin0 -> 23415 bytes
-rw-r--r--doc/profile/2fa_auth.pngbin0 -> 15569 bytes
-rw-r--r--doc/profile/README.md4
-rw-r--r--doc/profile/preferences.md32
-rw-r--r--doc/profile/two_factor_authentication.md67
-rw-r--r--doc/project_services/irker.md2
-rw-r--r--doc/raketasks/backup_restore.md25
-rw-r--r--doc/raketasks/maintenance.md16
-rw-r--r--doc/release/monthly.md23
-rw-r--r--doc/ssh/README.md30
-rw-r--r--doc/update/6.x-or-7.x-to-7.12.md (renamed from doc/update/6.x-or-7.x-to-7.11.md)25
-rw-r--r--doc/update/7.11-to-7.12.md129
-rw-r--r--doc/workflow/README.md17
-rw-r--r--doc/workflow/import_projects_from_github.md13
-rw-r--r--doc/workflow/importing/README.md9
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.pngbin0 -> 30083 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.pngbin0 -> 16502 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.pngbin0 -> 46606 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.pngbin0 -> 16121 bytes
-rw-r--r--doc/workflow/importing/github_importer/importer.png (renamed from doc/workflow/github_importer/importer.png)bin39335 -> 39335 bytes
-rw-r--r--doc/workflow/importing/github_importer/new_project_page.png (renamed from doc/workflow/github_importer/new_project_page.png)bin46276 -> 46276 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/importer.png (renamed from doc/workflow/gitlab_importer/importer.png)bin40778 -> 40778 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/new_project_page.png (renamed from doc/workflow/gitlab_importer/new_project_page.png)bin72663 -> 72663 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_bitbucket.md26
-rw-r--r--doc/workflow/importing/import_projects_from_github.md20
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md (renamed from doc/workflow/import_projects_from_gitlab_com.md)0
-rw-r--r--doc/workflow/importing/migrating_from_svn.md (renamed from doc/workflow/migrating_from_svn.md)0
-rw-r--r--doc/workflow/shortcuts.md5
-rw-r--r--doc/workflow/shortcuts.pngbin0 -> 78736 bytes
-rw-r--r--doc/workflow/timezone.md18
-rw-r--r--doc/workflow/wip_merge_requests.md13
-rw-r--r--doc/workflow/wip_merge_requests/blocked_accept_button.pngbin0 -> 65231 bytes
-rw-r--r--doc/workflow/wip_merge_requests/mark_as_wip.pngbin0 -> 41549 bytes
-rw-r--r--doc/workflow/wip_merge_requests/unmark_as_wip.pngbin0 -> 32151 bytes
-rw-r--r--doc_styleguide.md35
-rw-r--r--docker/README.md29
-rw-r--r--features/admin/deploy_keys.feature5
-rw-r--r--features/admin/users.feature20
-rw-r--r--features/dashboard/group.feature3
-rw-r--r--features/profile/active_tab.feature6
-rw-r--r--features/profile/profile.feature13
-rw-r--r--features/project/active_tab.feature12
-rw-r--r--features/project/commits/comments.feature1
-rw-r--r--features/project/commits/diff_comments.feature14
-rw-r--r--features/project/forked_merge_requests.feature1
-rw-r--r--features/project/issues/issues.feature12
-rw-r--r--features/project/merge_requests.feature38
-rw-r--r--features/project/project.feature5
-rw-r--r--features/project/source/browse_files.feature2
-rw-r--r--features/project/source/multiselect_blob.feature85
-rw-r--r--features/project/wiki.feature5
-rw-r--r--features/snippets/snippets.feature13
-rw-r--r--features/steps/admin/applications.rb22
-rw-r--r--features/steps/admin/broadcast_messages.rb10
-rw-r--r--features/steps/admin/deploy_keys.rb17
-rw-r--r--features/steps/admin/groups.rb22
-rw-r--r--features/steps/admin/logs.rb6
-rw-r--r--features/steps/admin/projects.rb12
-rw-r--r--features/steps/admin/settings.rb18
-rw-r--r--features/steps/admin/users.rb69
-rw-r--r--features/steps/dashboard/archived_projects.rb6
-rw-r--r--features/steps/dashboard/dashboard.rb28
-rw-r--r--features/steps/dashboard/event_filters.rb14
-rw-r--r--features/steps/dashboard/group.rb22
-rw-r--r--features/steps/dashboard/help.rb2
-rw-r--r--features/steps/dashboard/issues.rb4
-rw-r--r--features/steps/dashboard/merge_requests.rb4
-rw-r--r--features/steps/dashboard/new_project.rb12
-rw-r--r--features/steps/dashboard/starred_projects.rb4
-rw-r--r--features/steps/explore/groups.rb12
-rw-r--r--features/steps/explore/projects.rb51
-rw-r--r--features/steps/groups.rb80
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/profile/emails.rb14
-rw-r--r--features/steps/profile/notifications.rb2
-rw-r--r--features/steps/profile/profile.rb125
-rw-r--r--features/steps/profile/ssh_keys.rb10
-rw-r--r--features/steps/project/active_tab.rb2
-rw-r--r--features/steps/project/archived.rb4
-rw-r--r--features/steps/project/commits/branches.rb22
-rw-r--r--features/steps/project/commits/commits.rb64
-rw-r--r--features/steps/project/commits/tags.rb26
-rw-r--r--features/steps/project/commits/user_lookup.rb6
-rw-r--r--features/steps/project/create.rb20
-rw-r--r--features/steps/project/deploy_keys.rb24
-rw-r--r--features/steps/project/fork.rb10
-rw-r--r--features/steps/project/forked_merge_requests.rb82
-rw-r--r--features/steps/project/graph.rb6
-rw-r--r--features/steps/project/hooks.rb16
-rw-r--r--features/steps/project/issues/filter_labels.rb18
-rw-r--r--features/steps/project/issues/issues.rb73
-rw-r--r--features/steps/project/issues/labels.rb38
-rw-r--r--features/steps/project/issues/milestones.rb14
-rw-r--r--features/steps/project/merge_requests.rb159
-rw-r--r--features/steps/project/network_graph.rb34
-rw-r--r--features/steps/project/project.rb40
-rw-r--r--features/steps/project/redirects.rb12
-rw-r--r--features/steps/project/services.rb66
-rw-r--r--features/steps/project/snippets.rb26
-rw-r--r--features/steps/project/source/browse_files.rb52
-rw-r--r--features/steps/project/source/git_blame.rb6
-rw-r--r--features/steps/project/source/markdown_render.rb116
-rw-r--r--features/steps/project/source/multiselect_blob.rb58
-rw-r--r--features/steps/project/source/search_code.rb6
-rw-r--r--features/steps/project/star.rb4
-rw-r--r--features/steps/project/team_management.rb40
-rw-r--r--features/steps/project/wiki.rb62
-rw-r--r--features/steps/search.rb24
-rw-r--r--features/steps/shared/active_tab.rb12
-rw-r--r--features/steps/shared/admin.rb1
-rw-r--r--features/steps/shared/authentication.rb6
-rw-r--r--features/steps/shared/diff_note.rb136
-rw-r--r--features/steps/shared/group.rb4
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--features/steps/shared/markdown.rb10
-rw-r--r--features/steps/shared/note.rb66
-rw-r--r--features/steps/shared/paths.rb61
-rw-r--r--features/steps/shared/project.rb25
-rw-r--r--features/steps/shared/project_tab.rb8
-rw-r--r--features/steps/shared/user.rb6
-rw-r--r--features/steps/snippet_search.rb18
-rw-r--r--features/steps/snippets/discover.rb6
-rw-r--r--features/steps/snippets/public_snippets.rb4
-rw-r--r--features/steps/snippets/snippets.rb40
-rw-r--r--features/steps/snippets/user.rb18
-rw-r--r--features/steps/user.rb6
-rw-r--r--features/support/env.rb2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/files.rb50
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb12
-rw-r--r--lib/api/namespaces.rb11
-rw-r--r--lib/api/project_snippets.rb6
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/backup/manager.rb3
-rw-r--r--lib/backup/uploads.rb2
-rw-r--r--lib/gitlab/backend/grack_auth.rb1
-rw-r--r--lib/gitlab/backend/rack_attack_helpers.rb31
-rw-r--r--lib/gitlab/backend/shell.rb14
-rw-r--r--lib/gitlab/backend/shell_env.rb4
-rw-r--r--lib/gitlab/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/current_settings.rb3
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/github_import/importer.rb4
-rw-r--r--lib/gitlab/gitorious_import.rb5
-rw-r--r--lib/gitlab/gitorious_import/client.rb2
-rw-r--r--lib/gitlab/gitorious_import/repository.rb2
-rw-r--r--lib/gitlab/markdown.rb9
-rw-r--r--lib/gitlab/markdown/commit_range_reference_filter.rb9
-rw-r--r--lib/gitlab/markdown/commit_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/cross_project_reference.rb3
-rw-r--r--lib/gitlab/markdown/external_issue_reference_filter.rb9
-rw-r--r--lib/gitlab/markdown/external_link_filter.rb33
-rw-r--r--lib/gitlab/markdown/issue_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/label_reference_filter.rb24
-rw-r--r--lib/gitlab/markdown/merge_request_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/reference_filter.rb16
-rw-r--r--lib/gitlab/markdown/sanitization_filter.rb59
-rw-r--r--lib/gitlab/markdown/snippet_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/user_reference_filter.rb16
-rw-r--r--lib/gitlab/o_auth/provider.rb19
-rw-r--r--lib/gitlab/o_auth/user.rb64
-rw-r--r--lib/gitlab/push_data_builder.rb7
-rw-r--r--lib/gitlab/reference_extractor.rb54
-rw-r--r--lib/gitlab/satellite/action.rb6
-rw-r--r--lib/gitlab/satellite/files/delete_file_action.rb50
-rw-r--r--lib/gitlab/satellite/files/edit_file_action.rb68
-rw-r--r--lib/gitlab/satellite/files/file_action.rb25
-rw-r--r--lib/gitlab/satellite/files/new_file_action.rb67
-rw-r--r--lib/gitlab/theme.rb50
-rw-r--r--lib/gitlab/themes.rb67
-rw-r--r--lib/gitlab/upgrader.rb11
-rw-r--r--lib/redcarpet/render/gitlab_html.rb2
-rwxr-xr-xlib/support/init.d/gitlab3
-rwxr-xr-xlib/support/init.d/gitlab.default.example5
-rw-r--r--lib/support/nginx/gitlab12
-rw-r--r--lib/support/nginx/gitlab-ssl14
-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.rake96
-rw-r--r--lib/tasks/gitlab/cleanup.rake8
-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.rake9
-rw-r--r--lib/tasks/gitlab/task_helpers.rake4
-rw-r--r--lib/tasks/gitlab/test.rake2
-rw-r--r--lib/tasks/gitlab/web_hook.rake10
-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.rake4
-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-xscripts/prepare_build.sh24
-rw-r--r--spec/controllers/admin/users_controller_spec.rb24
-rw-r--r--spec/controllers/application_controller_spec.rb40
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb16
-rw-r--r--spec/controllers/blob_controller_spec.rb12
-rw-r--r--spec/controllers/branches_controller_spec.rb4
-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.rb88
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb15
-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/refs_controller_spec.rb17
-rw-r--r--spec/controllers/projects_controller_spec.rb16
-rw-r--r--spec/controllers/root_controller_spec.rb32
-rw-r--r--spec/controllers/tree_controller_spec.rb12
-rw-r--r--spec/factories.rb20
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb4
-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.rb35
-rw-r--r--spec/features/groups_spec.rb36
-rw-r--r--spec/features/issues_spec.rb14
-rw-r--r--spec/features/markdown_spec.rb37
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb51
-rw-r--r--spec/features/profile_spec.rb6
-rw-r--r--spec/features/profiles/preferences_spec.rb82
-rw-r--r--spec/features/projects_spec.rb55
-rw-r--r--spec/features/search_spec.rb3
-rw-r--r--spec/features/security/profile_access_spec.rb6
-rw-r--r--spec/features/task_lists_spec.rb6
-rw-r--r--spec/features/users_spec.rb21
-rw-r--r--spec/finders/issues_finder_spec.rb16
-rw-r--r--spec/finders/merge_requests_finder_spec.rb4
-rw-r--r--spec/fixtures/markdown.md.erb73
-rw-r--r--spec/helpers/application_helper_spec.rb300
-rw-r--r--spec/helpers/blob_helper_spec.rb33
-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.rb23
-rw-r--r--spec/helpers/groups_helper.rb4
-rw-r--r--spec/helpers/labels_helper_spec.rb68
-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.rb72
-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/javascripts/behaviors/requires_input_spec.js.coffee49
-rw-r--r--spec/javascripts/extensions/array_spec.js.coffee12
-rw-r--r--spec/javascripts/extensions/jquery_spec.js.coffee34
-rw-r--r--spec/javascripts/fixtures/behaviors/requires_input.html.haml18
-rw-r--r--spec/javascripts/fixtures/issuable.html.haml2
-rw-r--r--spec/javascripts/fixtures/issue_note.html.haml12
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml9
-rw-r--r--spec/javascripts/fixtures/merge_request_tabs.html.haml22
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/zen_mode.html.haml9
-rw-r--r--spec/javascripts/issue_spec.js.coffee30
-rw-r--r--spec/javascripts/line_highlighter_spec.js.coffee150
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee29
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js.coffee82
-rw-r--r--spec/javascripts/notes_spec.js.coffee19
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee7
-rw-r--r--spec/javascripts/spec_helper.coffee46
-rw-r--r--spec/javascripts/stat_graph_contributors_util_spec.js8
-rw-r--r--spec/javascripts/support/jasmine.yml15
-rw-r--r--spec/javascripts/support/jasmine_helper.rb15
-rw-r--r--spec/javascripts/zen_mode_spec.js.coffee52
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb4
-rw-r--r--spec/lib/extracts_path_spec.rb9
-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.rb14
-rw-r--r--spec/lib/gitlab/backend/rack_attack_helpers_spec.rb35
-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/closing_issue_extractor_spec.rb70
-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.rb36
-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.rb10
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb38
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb18
-rw-r--r--spec/lib/gitlab/markdown/emoji_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb18
-rw-r--r--spec/lib/gitlab/markdown/external_link_filter_spec.rb31
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb21
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb74
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb15
-rw-r--r--spec/lib/gitlab/markdown/sanitization_filter_spec.rb33
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb13
-rw-r--r--spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/task_list_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb47
-rw-r--r--spec/lib/gitlab/note_data_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/o_auth/auth_hash_spec.rb34
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb199
-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/reference_extractor_spec.rb28
-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/themes_spec.rb51
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb19
-rw-r--r--spec/lib/gitlab/version_info_spec.rb1
-rw-r--r--spec/lib/repository_cache_spec.rb1
-rw-r--r--spec/lib/votes_spec.rb7
-rw-r--r--spec/mailers/notify_spec.rb9
-rw-r--r--spec/models/application_setting_spec.rb1
-rw-r--r--spec/models/commit_range_spec.rb29
-rw-r--r--spec/models/commit_spec.rb32
-rw-r--r--spec/models/concerns/issuable_spec.rb7
-rw-r--r--spec/models/concerns/mentionable_spec.rb21
-rw-r--r--spec/models/deploy_keys_project_spec.rb12
-rw-r--r--spec/models/external_issue_spec.rb24
-rw-r--r--spec/models/external_wiki_service_spec.rb2
-rw-r--r--spec/models/forked_project_link_spec.rb12
-rw-r--r--spec/models/group_spec.rb26
-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.rb30
-rw-r--r--spec/models/key_spec.rb4
-rw-r--r--spec/models/label_spec.rb67
-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.rb57
-rw-r--r--spec/models/milestone_spec.rb16
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/models/note_spec.rb8
-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.rb28
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb43
-rw-r--r--spec/models/project_services/irker_service_spec.rb16
-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.rb31
-rw-r--r--spec/models/project_team_spec.rb1
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/service_spec.rb25
-rw-r--r--spec/models/snippet_spec.rb39
-rw-r--r--spec/models/user_spec.rb129
-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/commits_spec.rb3
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb10
-rw-r--r--spec/requests/api/files_spec.rb64
-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/issues_spec.rb8
-rw-r--r--spec/requests/api/merge_requests_spec.rb30
-rw-r--r--spec/requests/api/milestones_spec.rb6
-rw-r--r--spec/requests/api/namespaces_spec.rb29
-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.rb58
-rw-r--r--spec/requests/api/system_hooks_spec.rb12
-rw-r--r--spec/requests/api/users_spec.rb115
-rw-r--r--spec/routing/admin_routing_spec.rb1
-rw-r--r--spec/routing/project_routing_spec.rb34
-rw-r--r--spec/routing/routing_spec.rb26
-rw-r--r--spec/services/archive_repository_service_spec.rb5
-rw-r--r--spec/services/destroy_group_service_spec.rb44
-rw-r--r--spec/services/git_push_service_spec.rb61
-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/issues/close_service_spec.rb10
-rw-r--r--spec/services/issues/update_service_spec.rb25
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb6
-rw-r--r--spec/services/merge_requests/update_service_spec.rb36
-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/destroy_service_spec.rb34
-rw-r--r--spec/services/projects/fork_service_spec.rb6
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/services/projects/update_service_spec.rb2
-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/system_note_service_spec.rb54
-rw-r--r--spec/services/test_hook_service_spec.rb6
-rw-r--r--spec/spec_helper.rb22
-rw-r--r--spec/support/api_helpers.rb2
-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/filter_spec_helper.rb77
-rw-r--r--spec/support/login_helpers.rb29
-rw-r--r--spec/support/matchers.rb71
-rw-r--r--spec/support/mentionable_shared_examples.rb51
-rw-r--r--spec/support/reference_filter_spec_helper.rb50
-rw-r--r--spec/support/select2_helper.rb4
-rw-r--r--spec/support/stub_configuration.rb14
-rw-r--r--spec/support/test_env.rb6
-rw-r--r--spec/support/webmock.rb4
-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/teaspoon_env.rb178
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/repository_archive_worker_spec.rb1
-rwxr-xr-xvendor/assets/javascripts/jasmine-fixture.js433
877 files changed, 11870 insertions, 7544 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000000..ddf4e31204a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,67 @@
+# This file is generated by GitLab CI
+before_script:
+ - ./scripts/prepare_build.sh
+ - ruby -v
+ - which ruby
+ - gem install bundler --no-ri --no-rdoc
+ - cp config/gitlab.yml.example config/gitlab.yml
+ - touch log/application.log
+ - touch log/test.log
+ - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
+ - bundle exec rake db:create RAILS_ENV=test
+
+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 spinach:project
+ tags:
+ - ruby
+ - mysql
+
+spinach:other:
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
+ tags:
+ - ruby
+ - mysql
+
+teaspoon:
+ script:
+ - RAILS_ENV=test bundle exec teaspoon
+ tags:
+ - ruby
+ - mysql
+
+rubocop:
+ script:
+ - bundle exec rubocop
+ tags:
+ - ruby
+ - mysql
+
+brakeman:
+ script:
+ - bundle exec rake brakeman
+ tags:
+ - ruby
+ - mysql
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 ed9ffefb67f..04625b01d61 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,13 +1,98 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 7.12.0 (unreleased)
+v 7.13.0 (unreleased)
+ - Fix order of issues imported form 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 error when deleting a user who has projects (Stan Hu)
+ - Fix downloading of patches on public merge requests when user logged out (Stan Hu)
+ - The password for the default administrator (root) account has been changed from "5iveL!fe" to "password".
+ - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (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)
+ - 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
+
+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.
+ - Disable changing of the source branch in merge request update API (Stan Hu)
+ - Shorten merge request WIP text.
+ - Add option to disallow users from registering any application to use GitLab as an OAuth provider
+ - Support editing target branch of merge request (Stan Hu)
+ - Refactor permission checks with issues and merge requests project settings (Stan Hu)
+ - Fix Markdown preview not working in Edit Milestone page (Stan Hu)
+ - Fix Zen Mode not closing with ESC key (Stan Hu)
+ - Allow HipChat API version to be blank and default to v2 (Stan Hu)
+ - Add file attachment support in Milestone description (Stan Hu)
+ - Fix milestone "Browse Issues" button.
+ - Set milestone on new issue when creating issue from index with milestone filter active.
+ - Make namespace API available to all users (Stan Hu)
- Add web hook support for note events (Stan Hu)
- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
+ - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
+ - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
+ - Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
+ - Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
- Disabled expansion of top/bottom blobs for new file diffs
- Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
- Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka)
- Use the user list from the target project in a merge request (Stan Hu)
+ - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen)
+ - 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 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.
+ - Move user avatar and logout button to sidebar
+ - You can not remove user if he/she is an only owner of group
+ - User should be able to leave group. If not - show him proper message
+ - User has ability to leave project
+ - Add SAML support as an omniauth provider
+ - 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.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
+ - Add option to automatically link omniauth and LDAP identities
+
+v 7.11.4
+ - Fix missing bullets when creating lists
+ - Set rel="nofollow" on external links
+
+v 7.11.3
+ - no changes
+ - Fix upgrader script (Martins Polakovs)
v 7.11.2
- no changes
@@ -15,9 +100,12 @@ v 7.11.2
v 7.11.1
- no changes
-v 7.11.0
+v 7.11.0
- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
- Get editing comments to work in Chrome 43 again.
+ - Allow special character in users bio. I.e.: I <3 GitLab
+
+v 7.11.0
- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
- Don't show duplicate deploy keys
- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
@@ -89,6 +177,7 @@ v 7.10.4
- Fix DB error when trying to tag a repository (Stan Hu)
- Fix Error 500 when searching Wiki pages (Stan Hu)
- Unescape branch names in compare commit (Stan Hu)
+ - Order commit comments chronologically in API.
v 7.10.2
- Fix CI links on MR page
@@ -170,12 +259,12 @@ v 7.10.0
- Ability to skip some items from backup (database, respositories or uploads)
- Archive repositories in background worker.
- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
- - Project labels are now available over the API under the "tag_list" field (Cristian Medina)
+ - Project labels are now available over the API under the "tag_list" field (Cristian Medina)
- Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz)
- Fix and improve help rendering (Sullivan Sénéchal)
- Fix final line in EmailsOnPush email diff being rendered as error.
- Prevent duplicate Buildkite service creation.
- - Fix git over ssh errors 'fatal: protocol error: bad line length character'
+ - Fix git over ssh errors 'fatal: protocol error: bad line length character'
- Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled
- Bust group page project list cache when namespace name or path changes.
- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded
@@ -184,7 +273,7 @@ v 7.10.0
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
- Fix merge request comments on files with multiple commits
- Fix Resource Owner Password Authentication Flow
-
+
v 7.9.4
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
- Fixed issue where only 25 commits would load in file listings
@@ -489,6 +578,12 @@ v 7.5.0
- Use secret token with GitLab internal API.
- Add missing timestamps to 'members' table
+v 7.4.5
+ - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2)
+
+v 7.4.4
+ - No changes
+
v 7.4.3
- Fix raw snippets view
- Fix security issue for member api
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8059b95609a..a9dcf67b1e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -29,11 +29,9 @@ You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq
## Issue tracker
-To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/).
+To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/).
-The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
-
-Issues can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) or [github.com](https://github.com/gitlabhq/gitlabhq/issues).
+The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
@@ -169,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 6a3d4dd0c06..ec63c7eef84 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,17 +1,6 @@
source "https://rubygems.org"
-def darwin_only(require_as)
- RUBY_PLATFORM.include?('darwin') && require_as
-end
-
-def linux_only(require_as)
- RUBY_PLATFORM.include?('linux') && require_as
-end
-
-gem "rails", "~> 4.1.0"
-
-# Make links from text
-gem 'rails_autolink', '~> 1.1'
+gem 'rails', '4.1.11'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -31,6 +20,7 @@ gem 'omniauth-shibboleth'
gem 'omniauth-kerberos', group: :kerberos
gem 'omniauth-gitlab'
gem 'omniauth-bitbucket'
+gem 'omniauth-saml'
gem 'doorkeeper', '2.1.3'
gem "rack-oauth2", "~> 1.0.5"
@@ -40,22 +30,30 @@ gem 'rqrcode-rails3'
gem 'attr_encrypted', '1.3.4'
# Browser detection
-gem "browser"
+gem "browser", '~> 0.8.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.1.13'
+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)
+# For full list of changes see https://github.com/SaitoWu/grack/compare/master...gitlabhq:master
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
+# 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"
# Git Wiki
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
+# 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"
# API
@@ -83,7 +81,7 @@ gem "carrierwave"
gem 'dropzonejs-rails'
# for aws storage
-gem "fog", "~> 1.14"
+gem "fog", "~> 1.25.0"
gem "unf"
# Authorization
@@ -96,7 +94,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.2.3'
+gem 'redcarpet', '~> 3.3.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.12'
@@ -172,7 +170,7 @@ gem "underscore-rails", "~> 1.4.4"
gem "sanitize", '~> 2.0'
# Protect against bruteforcing
-gem "rack-attack"
+gem "rack-attack", '~> 4.3.0'
# Ace editor
gem 'ace-rails-ap'
@@ -186,23 +184,23 @@ gem 'charlock_holmes'
gem "sass-rails", '~> 4.0.2'
gem "coffee-rails"
gem "uglifier"
-gem 'turbolinks'
+gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks'
-gem 'select2-rails'
+gem 'addressable'
+gem 'bootstrap-sass', '~> 3.0'
+gem 'font-awesome-rails', '~> 4.2'
+gem 'gitlab_emoji', '~> 0.1'
+gem 'gon', '~> 5.0.0'
gem 'jquery-atwho-rails', '~> 1.0.0'
-gem "jquery-rails"
-gem "jquery-ui-rails"
-gem "jquery-scrollto-rails"
-gem "raphael-rails", "~> 2.1.2"
-gem 'bootstrap-sass', '~> 3.0'
-gem "font-awesome-rails", '~> 4.2'
-gem "gitlab_emoji", "~> 0.1"
-gem "gon", '~> 5.0.0'
+gem 'jquery-rails', '3.1.3'
+gem 'jquery-scrollto-rails'
+gem 'jquery-ui-rails'
gem 'nprogress-rails'
+gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
-gem "virtus"
-gem 'addressable'
+gem 'select2-rails'
+gem 'virtus'
group :development do
gem 'brakeman', require: false
@@ -224,49 +222,42 @@ group :development do
end
group :development, :test do
+ gem 'awesome_print'
+ gem 'byebug'
+ gem 'fuubar', '~> 2.0.0'
+ gem 'pry-rails'
+
gem 'coveralls', 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'
- 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 'factory_girl_rails'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0'
# Generate Fake data
- gem "ffaker"
+ gem 'ffaker', '~> 2.0.0'
- # Guard
- gem 'guard-rspec'
- gem 'guard-spinach'
-
- # Notification
- gem 'rb-fsevent', require: darwin_only('rb-fsevent')
- gem 'growl', require: darwin_only('growl')
- gem 'rb-inotify', require: linux_only('rb-inotify')
-
- # PhantomJS driver for Capybara
- gem 'poltergeist', '~> 1.5.1'
-
- gem 'jasmine-rails'
+ gem 'capybara', '~> 2.3.0'
+ gem 'capybara-screenshot', '~> 1.0.0'
+ gem 'poltergeist', '~> 1.6.0'
- gem "spring", '~> 1.3.1'
- gem "spring-commands-rspec", '1.0.4'
- gem "spring-commands-spinach", '1.0.0'
+ gem 'teaspoon', '~> 1.0.0'
+ gem 'teaspoon-jasmine'
- gem "byebug"
+ gem 'spring', '~> 1.3.1'
+ gem 'spring-commands-rspec', '~> 1.0.0'
+ gem 'spring-commands-spinach', '~> 1.0.0'
+ gem 'spring-commands-teaspoon', '~> 0.0.2'
end
group :test do
- gem "simplecov", require: false
- gem "shoulda-matchers", "~> 2.7.0"
- gem 'email_spec'
- gem "webmock"
+ gem 'simplecov', require: false
+ gem 'shoulda-matchers', '~> 2.8.0', require: false
+ gem 'email_spec', '~> 1.6.0'
+ gem 'webmock', '~> 1.21.0'
gem 'test_after_commit'
end
@@ -277,4 +268,4 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
-gem "rugments"
+gem "rugments", "~> 1.0.0.beta8"
diff --git a/Gemfile.lock b/Gemfile.lock
index 529131f09b0..718236ec39c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,33 +1,34 @@
GEM
remote: https://rubygems.org/
specs:
+ CFPropertyList (2.3.1)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.1.9)
- actionpack (= 4.1.9)
- actionview (= 4.1.9)
+ actionmailer (4.1.11)
+ actionpack (= 4.1.11)
+ actionview (= 4.1.11)
mail (~> 2.5, >= 2.5.4)
- actionpack (4.1.9)
- actionview (= 4.1.9)
- activesupport (= 4.1.9)
+ actionpack (4.1.11)
+ actionview (= 4.1.11)
+ activesupport (= 4.1.11)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
- actionview (4.1.9)
- activesupport (= 4.1.9)
+ actionview (4.1.11)
+ activesupport (= 4.1.11)
builder (~> 3.1)
erubis (~> 2.7.0)
- activemodel (4.1.9)
- activesupport (= 4.1.9)
+ activemodel (4.1.11)
+ activesupport (= 4.1.11)
builder (~> 3.1)
- activerecord (4.1.9)
- activemodel (= 4.1.9)
- activesupport (= 4.1.9)
+ activerecord (4.1.11)
+ activemodel (= 4.1.11)
+ activesupport (= 4.1.11)
arel (~> 5.0.0)
activeresource (4.0.0)
activemodel (~> 4.0)
activesupport (~> 4.0)
rails-observers (~> 0.1.1)
- activesupport (4.1.9)
+ activesupport (4.1.11)
i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -35,7 +36,7 @@ GEM
tzinfo (~> 1.1)
acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5)
- addressable (2.3.5)
+ addressable (2.3.8)
annotate (2.6.0)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
@@ -75,13 +76,13 @@ GEM
ruby_parser (~> 3.5.0)
sass (~> 3.0)
terminal-table (~> 1.4)
- browser (0.7.2)
+ browser (0.8.0)
builder (3.2.2)
byebug (3.2.0)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1)
- capybara (2.2.1)
+ capybara (2.3.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -101,13 +102,13 @@ GEM
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- coffee-rails (4.0.1)
+ coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
- coffee-script (2.2.0)
+ coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.6.3)
+ coffee-script-source (1.9.1.1)
colored (1.2)
colorize (0.5.8)
columnize (0.9.0)
@@ -118,13 +119,13 @@ GEM
simplecov (>= 0.7)
term-ansicolor
thor
- crack (0.4.1)
- safe_yaml (~> 0.9.0)
+ 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)
@@ -153,7 +154,7 @@ GEM
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)
@@ -163,7 +164,7 @@ GEM
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (1.0.4)
- excon (0.32.1)
+ excon (0.45.3)
execjs (2.5.2)
expression_parser (0.9.0)
factory_girl (4.3.0)
@@ -176,31 +177,74 @@ GEM
faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9)
fastercsv (1.5.5)
- ffaker (1.22.1)
+ ffaker (2.0.0)
ffi (1.9.8)
- fog (1.21.0)
- fog-brightbox
- fog-core (~> 1.21, >= 1.21.1)
+ fission (0.5.0)
+ CFPropertyList (~> 2.2)
+ fog (1.25.0)
+ fog-brightbox (~> 0.4)
+ fog-core (~> 1.25)
fog-json
+ fog-profitbricks
+ fog-radosgw (>= 0.0.2)
+ fog-sakuracloud (>= 0.0.4)
+ fog-softlayer
+ fog-terremark
+ fog-vmfusion
+ fog-voxel
+ fog-xml (~> 0.1.1)
+ ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
- fog-brightbox (0.0.1)
- fog-core
+ opennebula
+ fog-brightbox (0.7.1)
+ fog-core (~> 1.22)
fog-json
- fog-core (1.21.1)
+ inflecto (~> 0.0.2)
+ fog-core (1.30.0)
builder
- excon (~> 0.32)
- formatador (~> 0.2.0)
+ excon (~> 0.45)
+ formatador (~> 0.2)
mime-types
net-scp (~> 1.1)
net-ssh (>= 2.1.3)
- fog-json (1.0.0)
- multi_json (~> 1.0)
+ fog-json (1.0.2)
+ fog-core (~> 1.0)
+ multi_json (~> 1.10)
+ fog-profitbricks (0.0.3)
+ fog-core
+ fog-xml
+ nokogiri
+ fog-radosgw (0.0.4)
+ fog-core (>= 1.21.0)
+ fog-json
+ fog-xml (>= 0.0.1)
+ fog-sakuracloud (1.0.1)
+ fog-core
+ fog-json
+ fog-softlayer (0.4.6)
+ fog-core
+ fog-json
+ fog-terremark (0.1.0)
+ fog-core
+ fog-xml
+ fog-vmfusion (0.1.0)
+ fission
+ fog-core
+ fog-voxel (0.1.0)
+ fog-core
+ fog-xml
+ fog-xml (0.1.2)
+ fog-core
+ nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
- formatador (0.2.4)
+ 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)
@@ -225,7 +269,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.1.13)
+ gitlab_git (7.2.5)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -261,19 +305,6 @@ GEM
grape-entity (0.4.2)
activesupport
multi_json (>= 1.3.2)
- growl (1.0.3)
- guard (2.2.4)
- formatador (>= 0.2.4)
- listen (~> 2.1)
- lumberjack (~> 1.0)
- pry (>= 0.9.12)
- thor (>= 0.18.1)
- guard-rspec (4.2.0)
- guard (>= 2.1.1)
- rspec (>= 2.14, < 4.0)
- guard-spinach (0.0.2)
- guard (>= 1.1)
- spinach
haml (4.0.5)
tilt
haml-rails (0.5.3)
@@ -300,14 +331,10 @@ GEM
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.10.0)
- jasmine-core (2.2.0)
- jasmine-rails (0.10.8)
- jasmine-core (>= 1.3, < 3.0)
- phantomjs (>= 1.9)
- railties (>= 3.2.0)
- sprockets-rails
+ inflecto (0.0.2)
+ ipaddress (0.8.0)
jquery-atwho-rails (1.0.1)
- jquery-rails (3.1.0)
+ jquery-rails (3.1.3)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -317,14 +344,14 @@ GEM
turbolinks
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
- json (1.8.2)
+ json (1.8.3)
jwt (0.1.13)
multi_json (>= 1.5)
kaminari (0.15.1)
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)
@@ -332,7 +359,8 @@ GEM
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
- lumberjack (1.0.4)
+ macaddr (1.7.1)
+ systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
method_source (0.8.2)
@@ -341,14 +369,14 @@ GEM
mini_portile (0.6.2)
minitest (5.3.5)
mousetrap-rails (1.4.6)
- multi_json (1.10.1)
+ multi_json (1.11.1)
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
net-ldap (0.11)
- net-scp (1.1.2)
+ net-scp (1.2.1)
net-ssh (>= 2.6.5)
- net-ssh (2.8.0)
+ net-ssh (2.9.2)
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
@@ -389,19 +417,25 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
+ omniauth-saml (1.3.1)
+ omniauth (~> 1.1)
+ ruby-saml (~> 0.8.1)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
+ opennebula (4.12.1)
+ json
+ nokogiri
+ rbvmomi
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
- pg (0.15.1)
- phantomjs (1.9.8.0)
- poltergeist (1.5.1)
+ pg (0.18.2)
+ poltergeist (1.6.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -418,10 +452,10 @@ GEM
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
racc (1.4.10)
- rack (1.5.2)
+ rack (1.5.5)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (4.2.0)
+ rack-attack (4.3.0)
rack
rack-cors (0.2.9)
rack-mini-profiler (0.9.0)
@@ -438,23 +472,21 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.1.9)
- actionmailer (= 4.1.9)
- actionpack (= 4.1.9)
- actionview (= 4.1.9)
- activemodel (= 4.1.9)
- activerecord (= 4.1.9)
- activesupport (= 4.1.9)
+ rails (4.1.11)
+ actionmailer (= 4.1.11)
+ actionpack (= 4.1.11)
+ actionview (= 4.1.11)
+ activemodel (= 4.1.11)
+ activerecord (= 4.1.11)
+ activesupport (= 4.1.11)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.1.9)
+ railties (= 4.1.11)
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
- rails_autolink (1.1.6)
- rails (> 3.1)
- railties (4.1.9)
- actionpack (= 4.1.9)
- activesupport (= 4.1.9)
+ railties (4.1.11)
+ actionpack (= 4.1.11)
+ activesupport (= 4.1.11)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
@@ -464,9 +496,13 @@ GEM
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
+ rbvmomi (1.8.2)
+ builder
+ nokogiri (>= 1.4.1)
+ trollop
rdoc (3.12.2)
json (~> 1.4)
- redcarpet (3.2.3)
+ redcarpet (3.3.2)
redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
@@ -497,25 +533,27 @@ GEM
rqrcode (0.4.2)
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (2.99.0)
- rspec-core (~> 2.99.0)
- rspec-expectations (~> 2.99.0)
- rspec-mocks (~> 2.99.0)
- rspec-collection_matchers (1.1.2)
- rspec-expectations (>= 2.99.0.beta1)
- rspec-core (2.99.2)
- rspec-expectations (2.99.2)
- diff-lcs (>= 1.1.3, < 2.0)
- rspec-mocks (2.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)
@@ -523,6 +561,9 @@ GEM
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
ruby-progressbar (1.7.1)
+ ruby-saml (0.8.2)
+ nokogiri (>= 1.5.0)
+ uuid (~> 2.3)
ruby2ruby (2.1.3)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
@@ -531,8 +572,8 @@ GEM
rubyntlm (0.5.0)
rubypants (0.2.0)
rugged (0.22.2)
- rugments (1.0.0.beta6)
- safe_yaml (0.9.7)
+ rugments (1.0.0.beta8)
+ safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.2.19)
@@ -547,14 +588,14 @@ GEM
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
- seed-fu (2.3.1)
- activerecord (>= 3.1, < 4.2)
- activesupport (>= 3.1, < 4.2)
+ seed-fu (2.3.5)
+ activerecord (>= 3.1, < 4.3)
+ activesupport (>= 3.1, < 4.3)
select2-rails (3.5.2)
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
- shoulda-matchers (2.7.0)
+ shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
celluloid (>= 0.16.0)
@@ -589,25 +630,32 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
- spring (1.3.3)
+ spring (1.3.6)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
spring (>= 0.9.1)
+ spring-commands-teaspoon (0.0.2)
+ spring (>= 0.9.1)
sprockets (2.11.0)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.2.4)
+ sprockets-rails (2.3.1)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
stamp (0.5.0)
state_machine (1.2.0)
stringex (2.5.2)
+ systemu (2.6.5)
task_list (1.0.2)
html-pipeline
+ teaspoon (1.0.2)
+ railties (>= 3.2.5, < 5)
+ teaspoon-jasmine (2.2.0)
+ teaspoon (>= 1.0.0)
temple (0.6.7)
term-ansicolor (1.2.2)
tins (~> 0.8)
@@ -633,7 +681,8 @@ GEM
multi_json (~> 1.7)
twitter-stream (~> 0.1)
tins (0.13.1)
- turbolinks (2.0.0)
+ trollop (2.1.2)
+ turbolinks (2.5.3)
coffee-rails
twitter-stream (0.1.16)
eventmachine (>= 0.12.8)
@@ -654,6 +703,8 @@ GEM
raindrops (~> 0.7)
unicorn-worker-killer (0.4.2)
unicorn (~> 4)
+ uuid (2.3.7)
+ macaddr (~> 1.0)
version_sorter (2.0.0)
virtus (1.0.1)
axiom-types (~> 0.0.5)
@@ -662,10 +713,12 @@ GEM
equalizer (~> 0.0.7)
warden (1.2.3)
rack (>= 1.0)
- webmock (1.16.0)
- addressable (>= 2.2.7)
+ 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
@@ -690,10 +743,10 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 3.0)
brakeman
- browser
+ browser (~> 0.8.0)
byebug
cal-heatmap-rails (~> 0.0.1)
- capybara (~> 2.2.1)
+ capybara (~> 2.3.0)
capybara-screenshot (~> 1.0.0)
carrierwave
charlock_holmes
@@ -702,7 +755,7 @@ DEPENDENCIES
coveralls
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)
@@ -710,36 +763,33 @@ DEPENDENCIES
diffy (~> 3.0.3)
doorkeeper (= 2.1.3)
dropzonejs-rails
- email_spec
+ email_spec (~> 1.6.0)
enumerize
factory_girl_rails
- ffaker
- fog (~> 1.14)
+ 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.1.13)
+ gitlab_git (~> 7.2.5)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
- growl
- guard-rspec
- guard-spinach
haml-rails
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty
- jasmine-rails
jquery-atwho-rails (~> 1.0.0)
- jquery-rails
+ jquery-rails (= 3.1.3)
jquery-scrollto-rails
jquery-turbolinks
jquery-ui-rails
@@ -757,38 +807,36 @@ DEPENDENCIES
omniauth-gitlab
omniauth-google-oauth2
omniauth-kerberos
+ omniauth-saml
omniauth-shibboleth
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
+ rack-attack (~> 4.3.0)
rack-cors
rack-mini-profiler
rack-oauth2 (~> 1.0.5)
- rails (~> 4.1.0)
- rails_autolink (~> 1.1)
+ rails (= 4.1.11)
raphael-rails (~> 2.1.2)
- rb-fsevent
- rb-inotify
rdoc (~> 3.6)
- redcarpet (~> 3.2.3)
+ 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
+ rugments (~> 1.0.0.beta8)
sanitize (~> 2.0)
sass-rails (~> 4.0.2)
sdoc
seed-fu
select2-rails
settingslogic
- shoulda-matchers (~> 2.7.0)
+ shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov
@@ -798,15 +846,18 @@ DEPENDENCIES
slim
spinach-rails
spring (~> 1.3.1)
- spring-commands-rspec (= 1.0.4)
- spring-commands-spinach (= 1.0.0)
+ spring-commands-rspec (~> 1.0.0)
+ spring-commands-spinach (~> 1.0.0)
+ spring-commands-teaspoon (~> 0.0.2)
stamp
state_machine
task_list (= 1.0.2)
+ teaspoon (~> 1.0.0)
+ teaspoon-jasmine
test_after_commit
thin
tinder (~> 1.9.2)
- turbolinks
+ turbolinks (~> 2.5.0)
uglifier
underscore-rails (~> 1.4.4)
unf
@@ -814,5 +865,8 @@ DEPENDENCIES
unicorn-worker-killer
version_sorter
virtus
- webmock
+ webmock (~> 1.21.0)
wikicloth (= 0.8.1)
+
+BUNDLED WITH
+ 1.10.4
diff --git a/Guardfile b/Guardfile
deleted file mode 100644
index 68ac3232b09..00000000000
--- a/Guardfile
+++ /dev/null
@@ -1,27 +0,0 @@
-# A sample Guardfile
-# More info at https://github.com/guard/guard#readme
-
-guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
- watch(%r{^spec/.+_spec\.rb$})
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
- watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
- watch('spec/spec_helper.rb') { "spec" }
-
- # Rails example
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
- watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
- watch('config/routes.rb') { "spec/routing" }
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
-
- # Capybara request specs
- watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
-end
-
-guard 'spinach', command_prefix: 'spring' do
- watch(%r|^features/(.*)\.feature|)
- watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
- "features/#{m[1]}#{m[2]}.feature"
- end
-end
diff --git a/README.md b/README.md
index 85ea5c876af..37badd448c1 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ The recommended way to install GitLab is using the provided [Omnibus packages](h
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.
+You can access a new installation with the login **`root`** and password **`password`**, after login you are required to set a unique password.
## Third-party applications
@@ -101,4 +101,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlab/favorites) seem to like it. \ No newline at end of file
+[These people](https://twitter.com/gitlab/favorites) seem to like it.
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/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png
index 94a0e089c6e..fb64f8bee68 100644
--- a/app/assets/images/authbuttons/google_64.png
+++ b/app/assets/images/authbuttons/google_64.png
Binary files differ
diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png
index 5c9f14cb077..e3bd9169a34 100644
--- a/app/assets/images/authbuttons/twitter_64.png
+++ b/app/assets/images/authbuttons/twitter_64.png
Binary files differ
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/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index caf18c0d860..c18ea929506 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -49,8 +49,6 @@ window.slugify = (text) ->
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
-window.showAndHide = (selector) ->
-
window.split = (val) ->
return val.split( /,\s*/ )
@@ -92,15 +90,7 @@ window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
window.sanitize = (str) ->
return str.replace(/<(?:.|\n)*?>/gm, '')
-window.linkify = (str) ->
- exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig
- return str.replace(exp,"<a href='$1'>$1</a>")
-
-window.simpleFormat = (str) ->
- linkify(sanitize(str).replace(/\n/g, '<br />'))
-
window.unbindEvents = ->
- $(document).unbind('scroll')
$(document).off('scroll')
window.shiftWindow = ->
@@ -116,7 +106,10 @@ window.addEventListener "hashchange", shiftWindow
$ ->
# Click a .js-select-on-focus field, select the contents
- $(".js-select-on-focus").on "focusin", -> $(this).select()
+ $(".js-select-on-focus").on "focusin", ->
+ # Prevent a mouseup event from deselecting the input
+ $(this).select().one 'mouseup', (e) ->
+ e.preventDefault()
$('.remove-row').bind 'ajax:success', ->
$(this).closest('li').fadeOut()
@@ -148,8 +141,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
@@ -174,6 +166,10 @@ $ ->
$(@).next('table').show()
$(@).remove()
+ $('.navbar-toggle').on 'click', ->
+ $('.header-content .title').toggle()
+ $('.header-content .navbar-collapse').toggle()
+
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active')
@@ -189,14 +185,3 @@ $ ->
new ConfirmDangerModal(form, text)
new Aside()
-
-(($) ->
- # Disable an element and add the 'disabled' Bootstrap class
- $.fn.extend disable: ->
- $(@).attr('disabled', 'disabled').addClass('disabled')
-
- # Enable an element and remove the 'disabled' Bootstrap class
- $.fn.extend enable: ->
- $(@).removeAttr('disabled').removeClass('disabled')
-
-)(jQuery)
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/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee
index 44d75bd694f..4c4bc3d66ed 100644
--- a/app/assets/javascripts/calendar.js.coffee
+++ b/app/assets/javascripts/calendar.js.coffee
@@ -25,6 +25,7 @@ class @Calendar
30
]
legendCellPadding: 3
+ cellSize: $('.user-calendar').width() / 80
onClick: (date, count) ->
formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
$.ajax
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 2baaf430d98..a8ec0abc264 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -27,23 +27,18 @@ class Dispatcher
new Milestone()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
+ new DropzoneInput($('.milestone-form'))
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()
@@ -54,7 +49,7 @@ class Dispatcher
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
- when 'dashboard:show'
+ when 'dashboard:show', 'root:show'
new Dashboard()
new Activities()
when 'dashboard:projects:starred'
@@ -86,7 +81,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()
@@ -112,13 +107,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'
@@ -134,8 +122,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 fca2a290e2d..a7476146010 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -10,12 +10,17 @@ class @DropzoneInput
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
+ markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
- form_textarea.bind 'paste', (event) =>
+ form_textarea.on 'paste', (event) =>
handlePaste(event)
+ form_textarea.on "input", ->
+ hideReferencedUsers()
+ form_textarea.on "blur", ->
+ renderMarkdown()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -45,16 +50,7 @@ class @DropzoneInput
form.find(".md-write-holder").hide()
form.find(".md-preview-holder").show()
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- else
- preview.text "Loading..."
- $.post($(this).data("url"),
- md_text: mdText
- ).success (previewData) ->
- preview.html previewData
+ renderMarkdown()
# Write button
$(document).off "click", ".js-md-write-button"
@@ -133,6 +129,40 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
+ hideReferencedUsers = ->
+ referencedUsers = form.find(".referenced-users")
+ referencedUsers.hide()
+
+ renderReferencedUsers = (users) ->
+ referencedUsers = form.find(".referenced-users")
+
+ if referencedUsers.length
+ if users.length >= 10
+ referencedUsers.show()
+ referencedUsers.find(".js-referenced-users-count").text users.length
+ else
+ referencedUsers.hide()
+
+ renderMarkdown = ->
+ preview = form.find(".js-md-preview")
+ mdText = form.find(".markdown-area").val()
+ if mdText.trim().length is 0
+ preview.text "Nothing to preview."
+ hideReferencedUsers()
+ else
+ preview.text "Loading..."
+ $.ajax(
+ type: "POST",
+ url: markdown_preview_path,
+ data: {
+ text: mdText
+ },
+ dataType: "json"
+ ).success (data) ->
+ preview.html data.body
+
+ renderReferencedUsers data.references.users
+
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
index 40fb6cb9fc3..0a9db8eb5ef 100644
--- a/app/assets/javascripts/extensions/jquery.js.coffee
+++ b/app/assets/javascripts/extensions/jquery.js.coffee
@@ -1,13 +1,11 @@
-$.fn.showAndHide = ->
- $(@).show().
- delay(3000).
- fadeOut()
-
-$.fn.enableButton = ->
- $(@).removeAttr('disabled').
- removeClass('disabled')
-
-$.fn.disableButton = ->
- $(@).attr('disabled', 'disabled').
- addClass('disabled')
+# Disable an element and add the 'disabled' Bootstrap class
+$.fn.extend disable: ->
+ $(@)
+ .attr('disabled', 'disabled')
+ .addClass('disabled')
+# Enable an element and remove the 'disabled' Bootstrap class
+$.fn.extend enable: ->
+ $(@)
+ .removeAttr('disabled')
+ .removeClass('disabled')
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4eb3f3c03f3..7967892f856 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -10,7 +10,7 @@ GitLab.GfmAutoComplete =
# Team Members
Members:
- template: '<li>${username} <small>${name}</small></li>'
+ template: '<li>${username} <small>${title}</small></li>'
# Issues and MergeRequests
Issues:
@@ -34,7 +34,13 @@ GitLab.GfmAutoComplete =
searchKey: 'search'
callbacks:
beforeSave: (members) ->
- $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}"
+ $.map members, (m) ->
+ title = m.name
+ title += " (#{m.count})" if m.count
+
+ username: m.username
+ title: sanitize(title)
+ search: sanitize("#{m.username} #{m.name}")
input.atwho
at: '#'
@@ -44,7 +50,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (issues) ->
- $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ $.map issues, (i) ->
+ id: i.iid
+ title: sanitize(i.title)
+ search: "#{i.iid} #{i.title}"
input.atwho
at: '!'
@@ -54,7 +63,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (merges) ->
- $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
+ $.map merges, (m) ->
+ id: m.iid
+ title: sanitize(m.title)
+ search: "#{m.iid} #{m.title}"
input.one 'focus', =>
$.getJSON(@dataSource).done (data) ->
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 86ad3d03bac..603a16da1ce 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,32 +1,14 @@
-#= require jquery
#= require jquery.waitforimages
#= require task_list
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
@@ -43,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..a8b3c1fa33e
--- /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).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 7c1e2b822d7..7462975bd3d 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,25 +1,23 @@
-#= require jquery
-#= require bootstrap
+#= require jquery.waitforimages
#= require task_list
+#= require merge_request_tabs
+
class @MergeRequest
+ # Initialize MergeRequest behavior
+ #
+ # Options:
+ # action - String, current controller action
+ #
constructor: (@opts) ->
- @initContextWidget()
this.$el = $('.merge-request')
- @diffs_loaded = if @opts.action == 'diffs' then true else false
- @commits_loaded = false
-
- this.activateTab(@opts.action)
-
- this.bindEvents()
- this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
- modal = $('#modal_merge_info').modal(show: false)
-
- disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
+ # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
+ unless @opts.action == 'new'
+ new MergeRequestTabs(@opts)
# Prevent duplicate event bindings
@disableTaskList()
@@ -27,140 +25,14 @@ class @MergeRequest
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()
-
- initMergeWidget: ->
- this.showState( @opts.current_status )
-
- if this.$('.automerge_widget').length and @opts.check_enable
- $.get @opts.url_to_automerge_check, (data) =>
- this.showState( data.merge_status )
- , 'json'
-
- if @opts.ci_enable
- $.get @opts.url_to_ci_check, (data) =>
- this.showCiState data.status
- if data.coverage
- this.showCiCoverage data.coverage
- , 'json'
-
- bindEvents: ->
- this.$('.merge-request-tabs').on 'click', 'li', (event) =>
- this.activateTab($(event.currentTarget).data('action'))
-
- this.$('.accept_merge_request').on 'click', ->
- $('.automerge_widget.can_be_merged').hide()
- $('.merge-in-progress').show()
-
- this.$('.remove_source_branch').on 'click', ->
- $('.remove_source_branch_widget').hide()
- $('.remove_source_branch_in_progress').show()
-
- this.$(".remove_source_branch").on "ajax:success", (e, data, status, xhr) ->
- location.reload()
-
- this.$(".remove_source_branch").on "ajax:error", (e, data, status, xhr) =>
- this.$('.remove_source_branch_widget').hide()
- this.$('.remove_source_branch_in_progress').hide()
- this.$('.remove_source_branch_widget.failed').show()
-
- activateTab: (action) ->
- this.$('.merge-request-tabs li').removeClass 'active'
- this.$('.tab-content').hide()
- switch action
- when 'diffs'
- this.$('.merge-request-tabs .diffs-tab').addClass 'active'
- this.loadDiff() unless @diffs_loaded
- this.$('.diffs').show()
- $(".diff-header").trigger("sticky_kit:recalc")
- when 'commits'
- this.$('.merge-request-tabs .commits-tab').addClass 'active'
- this.$('.commits').show()
- else
- this.$('.merge-request-tabs .notes-tab').addClass 'active'
- this.$('.notes').show()
-
- showState: (state) ->
- $('.automerge_widget').hide()
- $('.automerge_widget.' + state).show()
-
- showCiState: (state) ->
- $('.ci_widget').hide()
- allowed_states = ["failed", "canceled", "running", "pending", "success"]
- if state in allowed_states
- $('.ci_widget.ci-' + state).show()
- switch state
- when "failed", "canceled"
- @setMergeButtonClass('btn-danger')
- when "running", "pending"
- @setMergeButtonClass('btn-warning')
- else
- $('.ci_widget.ci-error').show()
- @setMergeButtonClass('btn-danger')
-
- showCiCoverage: (coverage) ->
- cov_html = $('<span>')
- cov_html.addClass('ci-coverage')
- cov_html.text('Coverage ' + coverage + '%')
- $('.ci_widget:visible').append(cov_html)
-
- loadDiff: (event) ->
- $.ajax
- type: 'GET'
- url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".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'
-
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
- alreadyOrCannotBeMerged: ->
- this.$('.automerge_widget').hide()
- this.$('.merge-in-progress').hide()
- this.$('.automerge_widget.already_cannot_be_merged').show()
-
- setMergeButtonClass: (css_class) ->
- $('.accept_merge_request').removeClass("btn-create").addClass(css_class)
-
- mergeInProgress: ->
- $.ajax
- type: 'GET'
- url: $('.merge-request').data('url')
- success: (data) =>
- switch data.state
- when 'merged'
- location.reload()
- else
- setTimeout(merge_request.mergeInProgress, 3000)
- dataType: 'json'
-
initTaskList: ->
$('.merge-request-details .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList
@@ -177,5 +49,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..5dc441f64b5
--- /dev/null
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -0,0 +1,159 @@
+# 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'))
+ @stickyDiffHeaders()
+
+ @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
+ @stickyDiffHeaders()
+ @diffsLoaded = true
+
+ toggleLoading: ->
+ $('.mr-loading-status .loading').toggle()
+
+ stickyDiffHeaders: ->
+ $('.diff-header').trigger('sticky_kit:recalc')
+
+ _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
new file mode 100644
index 00000000000..e4d815bb4e4
--- /dev/null
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -0,0 +1,58 @@
+class @MergeRequestWidget
+ # Initialize MergeRequestWidget behavior
+ #
+ # check_enable - Boolean, whether to check automerge status
+ # url_to_automerge_check - String, URL to use to check automerge status
+ # current_status - String, current automerge status
+ # ci_enable - Boolean, whether a CI service is enabled
+ # url_to_ci_check - String, URL to use to check CI status
+ #
+ constructor: (@opts) ->
+ modal = $('#modal_merge_info').modal(show: false)
+
+ mergeInProgress: ->
+ $.ajax
+ type: 'GET'
+ url: $('.merge-request').data('url')
+ success: (data) =>
+ switch data.state
+ when 'merged'
+ location.reload()
+ else
+ setTimeout(merge_request_widget.mergeInProgress, 3000)
+ dataType: 'json'
+
+ getMergeStatus: ->
+ $.get @opts.url_to_automerge_check, (data) ->
+ $('.mr-state-widget').replaceWith(data)
+
+ getCiStatus: ->
+ if @opts.ci_enable
+ $.get @opts.url_to_ci_check, (data) =>
+ this.showCiState data.status
+ if data.coverage
+ this.showCiCoverage data.coverage
+ , 'json'
+
+ showCiState: (state) ->
+ $('.ci_widget').hide()
+ allowed_states = ["failed", "canceled", "running", "pending", "success", "not_found"]
+ if state in allowed_states
+ $('.ci_widget.ci-' + state).show()
+ switch state
+ when "failed", "canceled", "not_found"
+ @setMergeButtonClass('btn-danger')
+ when "running", "pending"
+ @setMergeButtonClass('btn-warning')
+ else
+ $('.ci_widget.ci-error').show()
+ @setMergeButtonClass('btn-danger')
+
+ showCiCoverage: (coverage) ->
+ cov_html = $('<span>')
+ cov_html.addClass('ci-coverage')
+ cov_html.text('Coverage ' + coverage + '%')
+ $('.ci_widget:visible').append(cov_html)
+
+ setMergeButtonClass: (css_class) ->
+ $('.accept_merge_request').removeClass("btn-create").addClass(css_class)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index f186fec2a0c..1c05a2b9fe8 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -1,6 +1,4 @@
-#= require jquery
#= require autosave
-#= require bootstrap
#= require dropzone
#= require dropzone_input
#= require gfm_auto_complete
@@ -10,11 +8,12 @@
class @Notes
@interval: null
- constructor: (notes_url, note_ids, last_fetched_at) ->
+ constructor: (notes_url, note_ids, last_fetched_at, view) ->
@notes_url = notes_url
@notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids
@last_fetched_at = last_fetched_at
+ @view = view
@noteable_url = document.URL
@initRefresh()
@setupMainTargetNoteForm()
@@ -65,13 +64,11 @@ class @Notes
# fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange
- @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
# Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
- $(document).on('keydown', @notes_forms, (e) ->
+ $(document).on 'keydown', '.js-note-text', (e) ->
return if e.originalEvent.repeat
if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
- $(@).parents('form').submit()
- )
+ $(@).closest('form').submit()
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
@@ -86,7 +83,7 @@ class @Notes
$(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange"
- $(document).off "keydown", @notes_forms
+ $(document).off "keydown", ".js-note-text"
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
@@ -135,6 +132,8 @@ class @Notes
isNewNote: (note) ->
$.inArray(note.id, @note_ids) == -1
+ isParallelView: ->
+ @view == 'parallel'
###
Render note in discussion area.
@@ -395,6 +394,7 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
form.attr "rel", dataHolder.data("discussionId")
+ form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode")
form.find("#note_noteable_type").val dataHolder.data("noteableType")
@@ -415,19 +415,40 @@ class @Notes
form = $(".js-new-note-form")
row = $(link).closest("tr")
nextRow = row.next()
-
- # does it already have notes?
- if nextRow.is(".notes_holder")
- replyButton = nextRow.find(".js-discussion-reply-button")
- if replyButton.length > 0
- $.proxy(@replyToDiscussionNote, replyButton).call()
+ hasNotes = nextRow.is(".notes_holder")
+ addForm = false
+ targetContent = ".notes_content"
+ rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>"
+
+ # In parallel view, look inside the correct left/right pane
+ if @isParallelView()
+ lineType = $(link).data("lineType")
+ targetContent += "." + lineType
+ rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
+
+ if hasNotes
+ notesContent = nextRow.find(targetContent)
+ if notesContent.length
+ replyButton = notesContent.find(".js-discussion-reply-button:visible")
+ if replyButton.length
+ e.target = replyButton[0]
+ $.proxy(@replyToDiscussionNote, replyButton[0], e).call()
+ else
+ # In parallel view, the form may not be present in one of the panes
+ noteForm = notesContent.find(".js-discussion-note-form")
+ if noteForm.length == 0
+ addForm = true
else
# add a notes row and insert the form
- row.after "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>"
- form.clone().appendTo row.next().find(".notes_content")
+ row.after rowCssToAdd
+ addForm = true
+
+ if addForm
+ newForm = form.clone()
+ newForm.appendTo row.next().find(targetContent)
# show the form
- @setupDiscussionNoteForm $(link), row.next().find("form")
+ @setupDiscussionNoteForm $(link), newForm
###
Called in response to "cancel" on a diff note form.
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index de356fbec77..bb0b66b86e1 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -1,10 +1,8 @@
class @Profile
constructor: ->
- $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
- # Submit the form
- $('.edit_user').submit()
-
- new Flash("Appearance settings saved", "notice")
+ # Automatically submit the Preferences form when any of its radio buttons change
+ $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
+ $(this).parents('form').submit()
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
@@ -12,12 +10,11 @@ class @Profile
$(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
+ $(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
-
+ $(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
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/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index 6b534f29218..bb532194682 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -1,6 +1,4 @@
-#= require jquery
#= require mousetrap
-
#= require shortcuts_navigation
class @ShortcutsIssuable extends ShortcutsNavigation
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index ed12bdcef22..3be14cb43dd 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,5 +1,4 @@
#= require d3
-#= require jquery
#= require stat_graph_contributors_util
class @ContributorsStatGraph
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 0e6fbdef3bc..b7a0e073766 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -50,7 +50,7 @@ class @ContributorsGraph
class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
- @width = $('.container').width() - 345
+ @width = $('.content').width() - 70
@height = 200
@x = null
@y = null
@@ -123,7 +123,7 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
- @width = $('.container').width()/2 - 225
+ @width = $('.content').width()/2 - 100
@height = 200
@x = null
@y = null
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
index 1670f5c7bc1..cfe5508290f 100644
--- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
@@ -2,11 +2,15 @@ window.ContributorsStatGraphUtil =
parse_log: (log) ->
total = {}
by_author = {}
+ by_email = {}
for entry in log
@add_date(entry.date, total) unless total[entry.date]?
- @add_author(entry, by_author) unless by_author[entry.author_name]?
- @add_date(entry.date, by_author[entry.author_name]) unless by_author[entry.author_name][entry.date]
- @store_data(entry, total[entry.date], by_author[entry.author_name][entry.date])
+
+ data = by_author[entry.author_name] #|| by_email[entry.author_email]
+ data ?= @add_author(entry, by_author, by_email)
+
+ @add_date(entry.date, data) unless data[entry.date]
+ @store_data(entry, total[entry.date], data[entry.date])
total = _.toArray(total)
by_author = _.toArray(by_author)
total: total, by_author: by_author
@@ -15,10 +19,12 @@ window.ContributorsStatGraphUtil =
collection[date] = {}
collection[date].date = date
- add_author: (author, by_author) ->
- by_author[author.author_name] = {}
- by_author[author.author_name].author_name = author.author_name
- by_author[author.author_name].author_email = author.author_email
+ add_author: (author, by_author, by_email) ->
+ data = {}
+ data.author_name = author.author_name
+ data.author_email = author.author_email
+ by_author[author.author_name] = data
+ by_email[author.author_email] = data
store_data: (entry, total, by_author) ->
@store_commits(total, by_author)
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 66757565d3a..81cfc37b956 100644
--- a/app/assets/javascripts/wikis.js.coffee
+++ b/app/assets/javascripts/wikis.js.coffee
@@ -1,9 +1,17 @@
class @Wikis
constructor: ->
- $('.build-new-wiki').bind "click", ->
+ $('.build-new-wiki').bind "click", (e) ->
+ $('[data-error~=slug]').addClass("hidden")
+ $('p.hint').show()
field = $('#new_wiki_path')
- slug = field.val()
- path = field.attr('data-wikis-path')
+ valid_slug_pattern = /^[\w\/-]+$/
- if(slug.length > 0)
- location.href = path + "/" + slug
+ slug = field.val()
+ if slug.match valid_slug_pattern
+ path = field.attr('data-wikis-path')
+ if(slug.length > 0)
+ location.href = path + "/" + slug
+ else
+ e.preventDefault()
+ $('p.hint').hide()
+ $('[data-error~=slug]').removeClass("hidden")
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index 0fb8f7ed75f..8a0564a9098 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -1,6 +1,8 @@
-class @ZenMode
- @fullscreen_prefix = 'fullscreen_'
+#= require dropzone
+#= require mousetrap
+#= require mousetrap/pause
+class @ZenMode
constructor: ->
@active_zen_area = null
@active_checkbox = null
@@ -12,34 +14,31 @@ class @ZenMode
$('body').on 'click', '.zen-enter-link', (e) =>
e.preventDefault()
- $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true)
+ $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true).change()
$('body').on 'click', '.zen-leave-link', (e) =>
e.preventDefault()
- $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false)
+ $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false).change()
$('body').on 'change', '.zen-toggle-comment', (e) =>
checkbox = e.currentTarget
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause()
- @udpateActiveZenArea(checkbox)
+ @updateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
- if e.keyCode is $.ui.keyCode.ESCAPE
+ if e.keyCode is 27 # Esc
@exitZenMode()
e.preventDefault()
- $(window).on 'hashchange', @updateZenModeFromLocationHash
-
- udpateActiveZenArea: (checkbox) =>
+ updateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus()
- window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
exitZenMode: =>
if @active_zen_area isnt null
@@ -47,21 +46,9 @@ class @ZenMode
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
- window.location.hash = ''
- window.scrollTo(window.pageXOffset, @scroll_position)
+ @restoreScroll(@scroll_position)
# Enable dropzone when leaving ZEN mode
Dropzone.forElement('.div-dropzone').enable()
- checkboxFromLocationHash: (e) ->
- id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
- if id
- return $('.zennable input[type=checkbox]#' + id)[0]
- else
- return null
-
- updateZenModeFromLocationHash: (e) =>
- checkbox = @checkboxFromLocationHash()
- if checkbox
- @udpateActiveZenArea(checkbox)
- else
- @exitZenMode()
+ restoreScroll: (y) ->
+ window.scrollTo(window.pageXOffset, y)
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 015ff2ce4ec..1a5f11df7d1 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -36,25 +36,25 @@
@import "font-awesome";
/**
+ * UI themes:
+ */
+@import "themes/**/*";
+
+/**
* Generic css (forms, nav etc):
*/
-@import "generic/*";
+@import "generic/**/*";
/**
* Page specific styles (issues, projects etc):
*/
-@import "pages/*";
+@import "pages/**/*";
/**
* Code highlight
*/
-@import "highlight/*";
-
-/**
- * UI themes:
- */
-@import "themes/*";
+@import "highlight/**/*";
/**
* Styles for JS behaviors.
diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss
index 62c11b06368..690d89a5c16 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -4,7 +4,7 @@ html {
&.touch .tooltip { display: none !important; }
body {
- padding-top: 46px;
+ padding-top: $header-height;
}
}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index 596376c3970..08f153dfbc9 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -1,16 +1,19 @@
$style_color: #474D57;
-$hover: #FFF3EB;
+$hover: #FFFAF1;
$gl-text-color: #222222;
$gl-link-color: #446e9b;
$nprogress-color: #c0392b;
$gl-font-size: 14px;
$list-font-size: 15px;
+$sidebar_collapsed_width: 52px;
$sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
$border-color: #E5E5E5;
$background-color: #f5f5f5;
+$header-height: 50px;
+
/*
* State colors:
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 1e569978cc8..961ac793de2 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -307,7 +307,7 @@ table {
}
.btn-sign-in {
- margin-top: 5px;
+ margin-top: 7px;
text-shadow: none;
}
@@ -342,9 +342,8 @@ table {
}
#nprogress .spinner {
- top: auto !important;
- bottom: 20px !important;
- left: 20px !important;
+ top: 15px !important;
+ right: 10px !important;
}
.header-with-avatar {
@@ -365,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/forms.scss b/app/assets/stylesheets/generic/forms.scss
index 7e070b4f386..4282832e2bf 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -49,14 +49,6 @@ label {
width: 250px;
}
-.input-mx-250 {
- max-width: 250px;
-}
-
-.input-mn-300 {
- min-width: 300px;
-}
-
.custom-form-control {
width: 150px;
}
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 362b217a444..31e2ad86691 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -3,173 +3,142 @@
*
*/
header {
+ transition-duration: .3s;
+
+ &.navbar-empty {
+ background: #FFF;
+ border-bottom: 1px solid #EEE;
+
+ .center-logo {
+ margin: 8px 0;
+ text-align: center;
+
+ img {
+ height: 32px;
+ }
+ }
+ }
+
&.navbar-gitlab {
z-index: 100;
margin-bottom: 0;
- min-height: 40px;
+ min-height: $header-height;
border: none;
width: 100%;
.container {
+ background: #FFF;
width: 100% !important;
padding: 0;
-
- background: #FFF;
- border-bottom: 1px solid #EEE;
filter: none;
- .title {
- position: relative;
- float: left;
- margin: 0;
- margin-left: 25px;
- font-size: 18px;
- line-height: 44px;
- font-weight: bold;
- color: #444;
-
- @include str-truncated(37%);
-
- a {
- color: #444;
- &:hover {
- text-decoration: underline;
- }
- }
- }
-
- .app_logo {
- border-bottom: 1px solid transparent;
- margin-bottom: -1px;
-
- a {
- padding: 5px 8px;
-
- img {
- float: left;
- }
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 20px;
- font-size: 18px;
- line-height: 34px;
- font-weight: normal;
- }
- }
- }
-
.nav > li > a {
- color: #666;
+ color: #888;
font-size: 14px;
- line-height: 32px;
- padding: 6px 10px;
+ padding: 0;
+ background-color: #f5f5f5;
+ margin: ($header-height - 28) / 2 0;
+ margin-left: 10px;
+ border-radius: 40px;
+ height: 28px;
+ width: 28px;
+ line-height: 28px;
+ text-align: center;
&:hover, &:focus, &:active {
- background: none;
+ background-color: #EEE;
}
}
- /** NAV block with links and profile **/
- .nav {
- float: right;
- margin-right: 0;
- }
-
.navbar-toggle {
color: #666;
margin: 0;
border-radius: 0;
+ position: absolute;
+ right: 2px;
&:hover {
background-color: #EEE;
}
}
}
-
- .turbolink-spinner {
- font-size: 20px;
- margin-right: 10px;
- }
-
- @media (max-width: $screen-xs-max) {
- border-width: 0;
- font-size: 18px;
-
- .title {
- @include str-truncated(70%);
- }
-
- .navbar-collapse {
- margin-top: 47px;
- }
-
- .navbar-nav {
- margin: 5px 0;
-
- .visible-xs, .visable-sm {
- display: table-cell !important;
- }
- }
-
- li {
- display: table-cell;
- width: 1%;
-
- a {
- text-align: center;
- font-size: 18px !important;
- }
- }
- }
}
- /**
- *
- * Logo holder
- *
- */
- .app_logo {
+ .header-logo {
+ border-bottom: 1px solid transparent;
float: left;
- margin-right: 9px;
+ height: $header-height;
+ width: $sidebar_width;
+ overflow: hidden;
+ transition-duration: .3s;
a {
float: left;
- height: 46px;
+ height: $header-height;
width: 100%;
+ padding: ($header-height - 36 ) / 2 8px;
+ 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 {
background-color: #EEE;
}
}
- .profile-pic {
- padding: 0px !important;
- width: 46px;
- height: 46px;
- margin-left: 5px;
- img {
- width: 46px;
- height: 46px;
+ .header-content {
+ border-bottom: 1px solid #EEE;
+ padding-right: 35px;
+ height: $header-height;
+
+ .title {
+ margin: 0;
+ padding: 0 15px 0 35px;
+ overflow: hidden;
+ font-size: 18px;
+ line-height: $header-height;
+ font-weight: bold;
+ color: #444;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ white-space: nowrap;
+
+ a {
+ color: #444;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .navbar-collapse {
+ float: right;
}
}
- /**
- *
- * Search box
- *
- */
.search {
margin-right: 10px;
margin-left: 10px;
- margin-top: 8px;
+ margin-top: ($header-height - 28) / 2;
form {
margin: 0;
@@ -177,6 +146,7 @@ header {
}
.search-input {
+ width: 220px;
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
@@ -184,57 +154,73 @@ header {
padding: 4px 6px;
padding-left: 25px;
font-size: 13px;
- @include border-radius(3px);
- border: 1px solid #DDD;
- box-shadow: none;
- @include transition(all 0.15s ease-in 0s);
- background-color: #f9f9f9;
+ background-color: #f5f5f5;
+ border-color: #f5f5f5;
+
+ &:focus {
+ @include box-shadow(none);
+ outline: none;
+ border-color: #DDD;
+ background-color: #FFF;
+ }
}
}
}
-.search .search-input {
- width: 300px;
- &:focus {
- width: 330px;
- background-color: #FFF;
+@mixin collapsed-header {
+ .header-logo {
+ width: $sidebar_collapsed_width;
}
-}
-@media (max-width: 1200px) {
- .search .search-input {
- width: 200px;
- &:focus {
- width: 230px;
+ .header-content {
+ .title {
+ margin-left: 30px;
}
}
}
-@media (max-width: $screen-xs-max) {
- #nprogress .spinner {
- right: 35px !important;
- }
-}
-
@media (max-width: $screen-md-max) {
.header-collapsed, .header-expanded {
- width: 52px;
-
- h3 {
- display: none;
- }
+ @include collapsed-header;
}
}
@media(min-width: $screen-md-max) {
.header-collapsed {
- width: 52px;
-
- h3 {
- display: none;
- }
+ @include collapsed-header;
}
.header-expanded {
}
}
+
+@media (max-width: $screen-xs-max) {
+ header .container {
+ font-size: 18px;
+
+ .title {
+ }
+
+ .navbar-nav {
+ margin: 0px;
+ float: none !important;
+
+ .visible-xs, .visable-sm {
+ display: table-cell !important;
+ }
+ }
+
+ .navbar-collapse {
+ padding-left: 5px;
+
+ li {
+ display: table-cell;
+ width: 1%;
+
+ a {
+ margin-left: 8px !important;
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index 08bf6e943d2..c502d953c75 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -39,7 +39,6 @@
&:hover {
background: $hover;
- border-bottom: 1px solid darken($hover, 10%);
}
&:last-child {
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index eb39b6bb7e9..f94677d1925 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -52,6 +52,22 @@
transition: opacity 200ms ease-in-out;
}
+.md-area {
+ position: relative;
+}
+
+.md-header ul {
+ float: left;
+}
+
+.referenced-users {
+ padding: 10px 0;
+ color: #999;
+ margin-left: 10px;
+ margin-top: 1px;
+ margin-right: 130px;
+}
+
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index b7f6fac5223..a49775daf8b 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -19,6 +19,10 @@
}
}
+ .referenced-users {
+ margin-right: 0;
+ }
+
.issues-filters,
.dash-projects-filters,
.check-all-holder {
@@ -57,8 +61,22 @@
}
.container .title {
- margin-left: 6px !important;
- max-width: 70% !important;
+ padding-left: 15px !important;
+ }
+
+ .issue-info, .merge-request-info {
+ display: none;
+ }
+
+ .issue-details {
+ .creator,
+ .page-title .btn-close {
+ display: none;
+ }
+ }
+
+ %ul.notes .note-role, .note-actions {
+ display: none;
}
}
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 754c5b53020..00e0534b81e 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -1,71 +1,72 @@
.page-with-sidebar {
- background: $background-color;
-
.sidebar-wrapper {
position: fixed;
top: 0;
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 {
+ transition-duration: .3s;
margin: 0;
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 {
+ color: $gray;
+ display: block;
+ text-decoration: none;
+ padding: 8px 15px;
+ font-size: 14px;
+ line-height: 20px;
+ 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);
+ }
}
}
}
@@ -81,6 +82,7 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
+ transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
@@ -88,51 +90,51 @@
.nav-sidebar {
margin-top: 29px;
position: fixed;
- top: 45px;
+ top: $header-height;
width: $sidebar_width;
}
- }
- .content-wrapper {
- padding: 20px;
+ .nav-sidebar li a{
+ width: 230px;
+ }
}
}
@mixin folded-sidebar {
padding-left: 50px;
+ transition-duration: .3s;
.sidebar-wrapper {
- width: 52px;
+ width: $sidebar_collapsed_width;
.nav-sidebar {
margin-top: 29px;
position: fixed;
- top: 45px;
- width: 52px;
+ top: $header-height;
+ width: $sidebar_collapsed_width;
li a {
- padding-left: 18px;
font-size: 14px;
padding: 8px 15px;
- text-align: center;
-
-
- & > span {
- display: none;
- }
+ text-align: left;
+ padding-left: 16px;
}
}
.collapse-nav a {
left: 0px;
- width: 52px;
+ width: $sidebar_collapsed_width;
+ }
+
+ .sidebar-user {
+ width: $sidebar_collapsed_width;
}
}
}
.collapse-nav a {
position: fixed;
- top: 46px;
+ top: $header-height;
left: 198px;
font-size: 13px;
background: transparent;
@@ -140,6 +142,7 @@
height: 28px;
text-align: center;
line-height: 28px;
+ transition-duration: .3s;
}
.collapse-nav a:hover {
@@ -170,3 +173,17 @@
@include expanded-sidebar;
}
}
+
+.sidebar-user {
+ position: absolute;
+ bottom: 0;
+ 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 e5590897947..66767cb13cb 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -23,6 +23,13 @@ pre {
font-family: $monospace_font;
}
+code {
+ &.key-fingerprint {
+ background: $body-bg;
+ color: $text-color;
+ }
+}
+
/**
* Wiki typography
*
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index 26afc21a6ab..7e86a0fe4b9 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -1,15 +1,14 @@
.zennable {
- position: relative;
-
- input {
+ .zen-toggle-comment {
display: none;
}
.zen-enter-link {
color: #888;
position: absolute;
- top: -26px;
+ top: 0px;
right: 4px;
+ line-height: 40px;
}
.zen-leave-link {
@@ -26,10 +25,12 @@
}
}
+ // Hide the Enter link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-enter-link {
display: none;
}
+ // Show the Leave link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-leave-link {
display: block;
position: absolute;
@@ -62,37 +63,24 @@
}
}
- .zen-backdrop textarea::-webkit-input-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea:-moz-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea::-moz-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea:-ms-input-placeholder {
- color: white;
- }
+ // Make the color of the placeholder text in the Zenned-out textarea darker,
+ // so it becomes visible
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
- color: #999;
+ color: #A8A8A8;
}
input:checked ~ .zen-backdrop textarea:-moz-placeholder {
- color: #999;
+ color: #A8A8A8;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
- color: #999;
+ color: #A8A8A8;
opacity: 1;
}
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
- color: #999;
+ color: #A8A8A8;
}
}
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index af9c83e5dc8..9a3b543ad10 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -28,10 +28,6 @@
font-size: 14px;
line-height: 24px;
- .str-truncated {
- max-width: 72%;
- }
-
a {
display: block;
padding: 8px 15px;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3165396a94d..61071320973 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -3,7 +3,7 @@
* MR -> show: Automerge widget
*
*/
-.automerge_widget {
+.mr-state-widget {
form {
margin-bottom: 0;
.clearfix {
@@ -123,38 +123,31 @@
.mr-state-widget {
font-size: 13px;
- background: #F9F9F9;
+ background: #FAFAFA;
margin-bottom: 20px;
color: #666;
- border: 1px solid #EEE;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+ border: 1px solid #e5e5e5;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
+ @include border-radius(3px);
.ci_widget {
padding: 10px 15px;
font-size: 15px;
- border-bottom: 1px solid #BBB;
- color: #777;
- background-color: $background-color;
+ border-bottom: 1px solid #EEE;
&.ci-success {
color: $gl-success;
- border-color: $gl-success;
- background-color: #F1FAF1;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
- border-color: $gl-warning;
- background-color: #FAF5F1;
}
&.ci-failed,
&.ci-canceled,
&.ci-error {
color: $gl-danger;
- border-color: $gl-danger;
- background-color: #FAF1F1;
}
}
@@ -162,7 +155,8 @@
padding: 10px 15px;
h4 {
- font-weight: normal;
+ font-weight: bold;
+ margin: 5px 0;
}
p:last-child {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index a0522030785..203f9374cee 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -39,11 +39,8 @@
.new_note, .edit_note {
.buttons {
- float: left;
margin-top: 8px;
- }
- .clearfix {
- margin-bottom: 0;
+ margin-bottom: 3px;
}
.note-preview-holder {
@@ -82,7 +79,6 @@
.note-form-actions {
background: #F9F9F9;
- height: 45px;
.note-form-option {
margin-top: 8px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 280e8b57174..8e4f0eb2b25 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -17,75 +17,15 @@
}
}
-/*
- * Appearance settings
- *
- */
-.themes_opts {
- label {
- margin-right: 20px;
- text-align: center;
-
- .prev {
- height: 80px;
- width: 160px;
- margin-bottom: 10px;
- @include border-radius(4px);
-
- &.classic {
- background: #31363e;
- }
-
- &.default {
- background: #888888;
- }
-
- &.modern {
- background: #009871;
- }
-
- &.gray {
- background: #373737;
- }
-
- &.violet {
- background: #548;
- }
-
- &.blue {
- background: #2980b9;
- }
- }
- }
-}
-
-.code_highlight_opts {
- margin-top: 10px;
-
- label {
- margin-right: 20px;
- text-align: center;
-
- .prev {
- width: 160px;
- margin-bottom: 10px;
-
- img {
- max-width: 100%;
- @include border-radius(4px);
- }
- }
- }
-}
-
.oauth-buttons {
.btn-group {
margin-right: 10px;
}
.btn {
- line-height: 36px;
- height: 56px;
+ line-height: 40px;
+ height: 42px;
+ padding: 0px 12px;
img {
width: 32px;
@@ -93,3 +33,17 @@
}
}
}
+
+// Profile > Account > Two Factor Authentication
+.two-factor-new {
+ .manual-instructions {
+ h3 {
+ margin-top: 0;
+ }
+
+ // Slightly increase the size of the details so they're easier to read
+ dl {
+ font-size: 1.1em;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
new file mode 100644
index 00000000000..e5859fe7384
--- /dev/null
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -0,0 +1,56 @@
+.application-theme {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .preview {
+ @include border-radius(4px);
+
+ height: 80px;
+ margin-bottom: 10px;
+ width: 160px;
+
+ &.ui_blue {
+ background: $theme-blue;
+ }
+
+ &.ui_charcoal {
+ background: $theme-charcoal;
+ }
+
+ &.ui_graphite {
+ background: $theme-graphite;
+ }
+
+ &.ui_gray {
+ background: $theme-gray;
+ }
+
+ &.ui_green {
+ background: $theme-green;
+ }
+
+ &.ui_violet {
+ background: $theme-violet;
+ }
+ }
+ }
+}
+
+.syntax-theme {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .preview {
+ margin-bottom: 10px;
+ width: 160px;
+
+ img {
+ @include border-radius(4px);
+
+ max-width: 100%;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 83771480cbd..e19b2eafa43 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -15,18 +15,16 @@
}
.project-home-panel {
- margin-bottom: 20px;
+ margin-top: 10px;
+ margin-bottom: 15px;
position: relative;
padding-left: 65px;
- border-bottom: 1px solid #DDD;
- padding-bottom: 10px;
- padding-top: 5px;
min-height: 50px;
.project-identicon-holder {
position: absolute;
left: 0;
- top: -10px;
+ top: -14px;
.avatar {
width: 50px;
@@ -48,14 +46,16 @@
}
.project-home-desc {
+ color: $gray;
+ float: left;
font-size: 16px;
line-height: 1.3;
margin-right: 250px;
- }
- .project-home-desc {
- float: left;
- color: $gray;
+ // Render Markdown-generated HTML inline for this block
+ p {
+ display: inline;
+ }
}
}
@@ -209,13 +209,14 @@ ul.nav.nav-projects-tabs {
line-height: 1.5;
}
- .well {
- padding: 14px;
+ .panel {
+ @include border-radius(3px);
- h4 {
+ .panel-heading, .panel-footer {
font-weight: normal;
- margin: 0;
- color: #555;
+ background-color: transparent;
+ color: #666;
+ border-color: #EEE;
}
.actions {
@@ -224,10 +225,12 @@ ul.nav.nav-projects-tabs {
.nav-pills a {
padding: 10px;
+ font-weight: bold;
+ color: $gl-link-color;
}
.nav {
- margin: 10px 0;
+ margin-bottom: 15px;
}
}
diff --git a/app/assets/stylesheets/pages/themes.scss b/app/assets/stylesheets/pages/themes.scss
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/assets/stylesheets/pages/themes.scss
+++ /dev/null
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 139b3cc1ac4..dc47b108100 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -1,8 +1,17 @@
+/**
+ * Styles the GitLab application with a specific color theme
+ *
+ * $color-light -
+ * $color -
+ * $color-darker -
+ * $color-dark -
+ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
- .app_logo {
+ .header-logo {
background-color: $color-darker;
+ border-color: $color-darker;
a {
color: $color-light;
@@ -19,8 +28,6 @@
}
.page-with-sidebar {
- background: $color-darker;
-
.collapse-nav a {
color: #FFF;
background: $color;
@@ -29,6 +36,16 @@
.sidebar-wrapper {
background: $color-darker;
border-right: 1px solid $color-darker;
+
+ .sidebar-user {
+ color: $color-light;
+
+ &:hover {
+ background-color: $color-dark;
+ color: #FFF;
+ text-decoration: none;
+ }
+ }
}
.nav-sidebar li {
@@ -55,7 +72,7 @@
&.active a {
color: #FFF;
- font-weight: bold;
+ background: $color-dark;
&.no-highlight {
border: none;
@@ -68,3 +85,36 @@
}
}
}
+
+$theme-blue: #2980B9;
+$theme-charcoal: #474D57;
+$theme-graphite: #888888;
+$theme-gray: #373737;
+$theme-green: #019875;
+$theme-violet: #554488;
+
+body {
+ &.ui_blue {
+ @include gitlab-theme(#BECDE9, $theme-blue, #1970A9, #096099);
+ }
+
+ &.ui_charcoal {
+ @include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D);
+ }
+
+ &.ui_graphite {
+ @include gitlab-theme(#CCCCCC, $theme-graphite, #777777, #666666);
+ }
+
+ &.ui_gray {
+ @include gitlab-theme(#979797, $theme-gray, #272727, #222222);
+ }
+
+ &.ui_green {
+ @include gitlab-theme(#AADDCC, $theme-green, #018865, #017855);
+ }
+
+ &.ui_violet {
+ @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
+ }
+}
diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss
deleted file mode 100644
index 63e8dce1e92..00000000000
--- a/app/assets/stylesheets/themes/ui_basic.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- */
-.ui_basic {
- @include gitlab-theme(#CCCCCC, #888888, #777777, #666666);
-}
diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss
deleted file mode 100644
index cf995622b6b..00000000000
--- a/app/assets/stylesheets/themes/ui_blue.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Blue GitLab UI theme
- */
-.ui_blue {
- @include gitlab-theme(#BECDE9, #2980b9, #1970a9, #096099);
-}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
deleted file mode 100644
index 6babccec0da..00000000000
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Violet GitLab UI theme
- */
-.ui_color {
- @include gitlab-theme(#98C, #548, #436, #325);
-}
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
deleted file mode 100644
index f8e4a6ea7da..00000000000
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Gray GitLab UI theme
- */
-.ui_gray {
- @include gitlab-theme(#979797, #373737, #272727, #222222);
-}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
deleted file mode 100644
index fda96b64cd9..00000000000
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Classic GitLab UI theme
- */
-.ui_mars {
- @include gitlab-theme(#979DA7, #474D57, #373D47, #24272D);
-}
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
deleted file mode 100644
index 8261e80b35f..00000000000
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Modern GitLab UI theme
- */
-.ui_modern {
- @include gitlab-theme(#ADC, #019875, #018865, #017855);
-}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4c35622fff1..c7c643db401 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -38,11 +38,14 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:twitter_sharing_enabled,
:sign_in_text,
:home_page_url,
+ :after_sign_out_path,
:max_attachment_size,
+ :session_expire_delay,
:default_project_visibility,
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
+ :user_oauth_applications,
restricted_visibility_levels: [],
)
end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index c301e61d1c7..285e8495342 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -1,13 +1,8 @@
class Admin::DeployKeysController < Admin::ApplicationController
before_action :deploy_keys, only: [:index]
- before_action :deploy_key, only: [:show, :destroy]
+ before_action :deploy_key, only: [:destroy]
def index
-
- end
-
- def show
-
end
def new
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 2dfae13ac5c..4d3e48f7f81 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -47,7 +47,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
- @group.destroy
+ DestroyGroupService.new(@group, current_user).execute
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
end
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 ee449badf59..f616ccf5684 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController
def index
@projects = Project.all
- @projects = @projects.where(namespace_id: params[:namespace_id]) if params[:namespace_id].present?
+ @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index d36e359934c..ec29c320654 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
@@ -86,11 +95,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def destroy
- # 1. Remove groups where user is the only owner
- user.solo_owned_groups.map(&:destroy)
-
- # 2. Remove user with all authored content including personal projects
- user.destroy
+ 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 8ce881c7414..a657d3c54ee 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -89,7 +89,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- new_user_session_path
+ current_application_settings.after_sign_out_path || new_user_session_path
end
def abilities
@@ -140,11 +140,6 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, action, project)
end
- def authorize_labels!
- # Labels should be accessible for issues and/or merge requests
- authorize_read_issue! || authorize_read_merge_request!
- end
-
def access_denied!
render "errors/access_denied", layout: "errors", status: 404
end
@@ -289,14 +284,14 @@ class ApplicationController < ActionController::Base
def get_issues_collection
set_filters_params
- issues = IssuesFinder.new.execute(current_user, @filter_params)
- issues
+ @issuable_finder = IssuesFinder.new(current_user, @filter_params)
+ @issuable_finder.execute
end
def get_merge_requests_collection
set_filters_params
- merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params)
- merge_requests
+ @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params)
+ @issuable_finder.execute
end
def github_import_enabled?
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/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index a11c554a2af..040255f08e6 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -66,7 +66,11 @@ class Groups::GroupMembersController < Groups::ApplicationController
@group_member.destroy
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
else
- return render_403
+ if @group.last_owner?(current_user)
+ redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.")
+ else
+ return render_403
+ end
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 34f0b257db3..2e381822e42 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -82,7 +82,7 @@ class GroupsController < Groups::ApplicationController
end
def destroy
- @group.destroy
+ DestroyGroupService.new(@group, current_user).execute
redirect_to root_path, notice: 'Group was removed.'
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 507b8290a2b..fc31118124b 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,6 +1,8 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
+ include Gitlab::CurrentSettings
include PageLayoutHelper
+ before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
layout 'profile'
@@ -32,6 +34,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
private
+ def verify_user_oauth_applications_enabled
+ return if current_application_settings.user_oauth_applications?
+
+ redirect_to applications_profile_url
+ end
+
def set_application
@application = current_user.oauth_applications.find(params[:id])
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index dcd949a71de..765adaf2128 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,4 +1,7 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
+
+ protect_from_forgery except: [:kerberos, :saml]
+
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
handle_omniauth
@@ -21,7 +24,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
@user = Gitlab::LDAP::User.new(oauth)
@user.save if @user.changed? # will also save new users
gl_user = @user.gl_user
- gl_user.remember_me = true if @user.persisted?
+ gl_user.remember_me = params[:remember_me] if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
if @user.allowed?
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
new file mode 100644
index 00000000000..538b09ca54d
--- /dev/null
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -0,0 +1,38 @@
+class Profiles::PreferencesController < Profiles::ApplicationController
+ before_action :user
+
+ def show
+ end
+
+ def update
+ begin
+ if @user.update_attributes(preferences_params)
+ flash[:notice] = 'Preferences saved.'
+ else
+ flash[:alert] = 'Failed to save preferences.'
+ end
+ rescue ArgumentError => e
+ # Raised when `dashboard` is given an invalid value.
+ flash[:alert] = "Failed to save preferences (#{e.message})."
+ end
+
+ respond_to do |format|
+ format.html { redirect_to profile_preferences_path }
+ format.js
+ end
+ end
+
+ private
+
+ def user
+ @user = current_user
+ end
+
+ def preferences_params
+ params.require(:user).permit(
+ :color_scheme_id,
+ :dashboard,
+ :theme_id
+ )
+ end
+end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 30ee6891733..03845f1e1ec 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -1,7 +1,7 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def new
unless current_user.otp_secret
- current_user.otp_secret = User.generate_otp_secret
+ current_user.otp_secret = User.generate_otp_secret(32)
current_user.save!
end
@@ -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!
@@ -18,6 +18,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
else
@error = 'Invalid pin code'
@qr_code = build_qr_code
+
render 'new'
end
end
@@ -29,7 +30,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def destroy
current_user.update_attributes({
- otp_required_for_login: false,
+ two_factor_enabled: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
@@ -42,8 +43,12 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
private
def build_qr_code
- issuer = "GitLab | #{current_user.email}"
+ issuer = "#{issuer_host} | #{current_user.email}"
uri = current_user.otp_provisioning_uri(current_user.email, issuer: issuer)
RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
end
+
+ def issuer_host
+ Gitlab.config.gitlab.host
+ end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index f4366c18e7b..b4af9e490ed 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -8,9 +8,6 @@ class ProfilesController < Profiles::ApplicationController
def show
end
- def design
- end
-
def applications
@applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens
@@ -29,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController
respond_to do |format|
format.html { redirect_to :back }
- format.js
end
end
@@ -65,10 +61,21 @@ class ProfilesController < Profiles::ApplicationController
def user_params
params.require(:user).permit(
- :email, :password, :password_confirmation, :bio, :name,
- :username, :skype, :linkedin, :twitter, :website_url,
- :color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key,
- :hide_no_password, :location, :public_email
+ :avatar,
+ :bio,
+ :email,
+ :hide_no_password,
+ :hide_no_ssh_key,
+ :linkedin,
+ :location,
+ :name,
+ :password,
+ :password_confirmation,
+ :public_email,
+ :skype,
+ :twitter,
+ :username,
+ :website_url
)
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index b762518d377..100d3d3b317 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update]
- before_action :after_edit_path, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
+ before_action :editor_variables, except: [:show, :preview, :diff]
+ before_action :after_edit_path, only: [:edit, :update]
def new
commit unless @repository.empty?
end
def create
- file_path = File.join(@path, File.basename(params[:file_name]))
- result = Files::CreateService.new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- file_path
- ).execute
+ result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- ref = sanitized_new_branch_name.presence || @ref
- redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
+ redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
else
flash[:alert] = result[:message]
render :new
@@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
- result = Files::UpdateService.
- new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- @path
- ).execute
+ result = Files::UpdateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
-
- if from_merge_request
- from_merge_request.reload_code
- end
-
redirect_to after_edit_path
else
flash[:alert] = result[:message]
@@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
+ result = Files::DeleteService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_tree_path(@project.namespace, @project,
- @ref)
+ redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
else
flash[:alert] = result[:message]
render :show
@@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id]
@ref, @path = extract_ref(@id)
-
rescue InvalidPathError
not_found!
end
@@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
- elsif sanitized_new_branch_name.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
+ elsif @target_branch.present?
+ namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
end
@@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
end
+
+ def editor_variables
+ @current_branch = @ref
+ @target_branch = (sanitized_new_branch_name || @ref)
+
+ @file_path =
+ if action_name.to_s == 'create'
+ File.join(@path, File.basename(params[:file_name]))
+ else
+ @path
+ end
+
+ @commit_params = {
+ file_path: @file_path,
+ current_branch: @current_branch,
+ target_branch: @target_branch,
+ commit_message: params[:commit_message],
+ file_content: params[:content],
+ file_content_encoding: params[:encoding]
+ }
+ end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 8c1bbf76917..40e2b37912b 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -18,10 +18,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
@available_public_keys -= @available_project_keys
end
- def show
- @key = @project.deploy_keys.find(params[:id])
- end
-
def new
@key = @project.deploy_keys.new
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/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 2f8cb203cf9..86d6e3e0f6b 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -1,7 +1,7 @@
class Projects::LabelsController < Projects::ApplicationController
before_action :module_enabled
before_action :label, only: [:edit, :update, :destroy]
- before_action :authorize_labels!
+ before_action :authorize_read_label!
before_action :authorize_admin_labels!, except: [:index]
respond_to :js, :html
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index c7467e9b2f5..d1265198318 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,19 +2,22 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
- before_action :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
- before_action :closes_issues, only: [:edit, :update, :show, :diffs]
- before_action :validates_merge_request, only: [:show, :diffs]
- before_action :define_show_vars, only: [:show, :diffs]
+ before_action :merge_request, only: [
+ :edit, :update, :show, :diffs, :commits, :automerge, :automerge_check,
+ :ci_status, :toggle_subscription
+ ]
+ before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
+ before_action :validates_merge_request, only: [:show, :diffs, :commits]
+ before_action :define_show_vars, only: [:show, :diffs, :commits]
# Allow read any merge_request
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']
@@ -27,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.full_search(terms)
end
end
-
+
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
respond_to do |format|
@@ -67,6 +70,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def commits
+ 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
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@@ -132,11 +142,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.check_if_can_be_merged
end
- render json: { merge_status: @merge_request.automerge_status }
+ closes_issues
+
+ render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
def automerge
- return access_denied! unless allowed_to_merge?
+ return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.automergeable?
AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params)
@@ -206,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!
@@ -234,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
@@ -245,8 +259,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
- @allowed_to_merge = allowed_to_merge?
- @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
if @merge_request.locked_long_ago?
@@ -255,19 +267,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
- def allowed_to_merge?
- allowed_to_push_code?(project, @merge_request.target_branch)
- end
-
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
end
- def allowed_to_push_code?(project, branch)
- ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch)
- end
-
def merge_request_params
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 496b85cb46d..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]
@@ -77,11 +77,24 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_html(note)
+ if params[:view] == 'parallel'
+ template = "projects/notes/_diff_notes_with_reply_parallel"
+ locals =
+ if params[:line_type] == 'old'
+ { notes_left: [note], notes_right: [] }
+ else
+ { notes_left: [], notes_right: [note] }
+ end
+ else
+ template = "projects/notes/_diff_notes_with_reply"
+ locals = { notes: [note] }
+ end
+
render_to_string(
- "projects/notes/_diff_notes_with_reply",
+ template,
layout: false,
formats: [:html],
- locals: { notes: [note] }
+ locals: locals
)
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d7fbc979067..b82b6f45d59 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -2,8 +2,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!, except: :leave
- layout "project_settings"
-
def index
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
@@ -73,10 +71,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def leave
+ if @project.namespace == current_user.namespace
+ return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.')
+ end
+
@project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_to dashboard_path }
format.js { render nothing: true }
end
end
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 dc430351551..be5968cd7b0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -97,18 +97,15 @@ class ProjectsController < ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
+ flash[:alert] = 'Project deleted.'
- respond_to do |format|
- format.html do
- flash[:alert] = 'Project deleted.'
-
- if request.referer.include?('/admin')
- redirect_to admin_namespaces_projects_path
- else
- redirect_to dashboard_path
- end
- end
+ if request.referer.include?('/admin')
+ redirect_to admin_namespaces_projects_path
+ else
+ redirect_to dashboard_path
end
+ rescue Projects::DestroyService::DestroyError => ex
+ redirect_to edit_project_path(@project), alert: ex.message
end
def autocomplete_sources
@@ -154,7 +151,17 @@ class ProjectsController < ApplicationController
end
def markdown_preview
- render text: view_context.markdown(params[:md_text])
+ text = params[:text]
+
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext.analyze(text)
+
+ render json: {
+ body: view_context.markdown(text),
+ references: {
+ users: ext.users.map(&:username)
+ }
+ }
end
private
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 830751a989f..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
- current_user.destroy
+ 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/root_controller.rb b/app/controllers/root_controller.rb
new file mode 100644
index 00000000000..fdfe00dc135
--- /dev/null
+++ b/app/controllers/root_controller.rb
@@ -0,0 +1,28 @@
+# RootController
+#
+# This controller exists solely to handle requests to `root_url`. When a user is
+# logged in and has customized their `dashboard` setting, they will be
+# redirected to their preferred location.
+#
+# For users who haven't customized the setting, we simply delegate to
+# `DashboardController#show`, which is the default.
+class RootController < DashboardController
+ before_action :redirect_to_custom_dashboard, only: [:show]
+
+ def show
+ super
+ end
+
+ private
+
+ def redirect_to_custom_dashboard
+ return unless current_user
+
+ case current_user.dashboard
+ when 'stars'
+ redirect_to starred_dashboard_projects_path
+ else
+ return
+ end
+ end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index b89b4c27350..7577fc96d6d 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -2,6 +2,7 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
prepend_before_action :authenticate_with_two_factor, only: [:create]
+ before_action :auto_sign_in_with_provider, only: [:new]
def new
redirect_path =
@@ -56,7 +57,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)
@@ -75,6 +76,21 @@ class SessionsController < Devise::SessionsController
end
end
+ def auto_sign_in_with_provider
+ provider = Gitlab.config.omniauth.auto_sign_in_with_provider
+ return unless provider.present?
+
+ # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
+ # registered or no alert at all. In case of another alert (such as a blocked user), it is safer
+ # to do nothing to prevent redirection loops with certain Omniauth providers.
+ return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated')
+
+ # Prevent alert from popping up on the first page shown after authentication.
+ flash[:alert] = nil
+
+ redirect_to omniauth_authorize_path(:user, provider.to_sym)
+ end
+
def valid_otp_attempt?(user)
user.valid_otp?(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
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/README.md b/app/finders/README.md
index 1f46518d230..1a1c69dea38 100644
--- a/app/finders/README.md
+++ b/app/finders/README.md
@@ -16,7 +16,7 @@ issues = project.issues_for_user_filtered_by(user, params)
Better use this:
```ruby
-issues = IssuesFinder.new.execute(project, user, filter)
+issues = IssuesFinder.new(project, user, filter).execute
```
It will help keep models thiner.
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index b8f367c6339..2eccc0ee31f 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -23,10 +23,12 @@ class IssuableFinder
attr_accessor :current_user, :params
- def execute(current_user, params)
+ def initialize(current_user, params)
@current_user = current_user
@params = params
+ end
+ def execute
items = init_collection
items = by_scope(items)
items = by_state(items)
@@ -40,6 +42,77 @@ class IssuableFinder
items = sort(items)
end
+ def group
+ return @group if defined?(@group)
+
+ @group =
+ if params[:group_id].present?
+ Group.find(params[:group_id])
+ else
+ nil
+ end
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ @project =
+ if params[:project_id].present?
+ Project.find(params[:project_id])
+ else
+ nil
+ end
+ end
+
+ def search
+ params[:search].presence
+ end
+
+ def milestones?
+ params[:milestone_title].present?
+ end
+
+ def milestones
+ return @milestones if defined?(@milestones)
+
+ @milestones =
+ if milestones? && params[:milestone_title] != NONE
+ Milestone.where(title: params[:milestone_title])
+ else
+ nil
+ end
+ end
+
+ def assignee?
+ params[:assignee_id].present?
+ end
+
+ def assignee
+ return @assignee if defined?(@assignee)
+
+ @assignee =
+ if assignee? && params[:assignee_id] != NONE
+ User.find(params[:assignee_id])
+ else
+ nil
+ end
+ end
+
+ def author?
+ params[:author_id].present?
+ end
+
+ def author
+ return @author if defined?(@author)
+
+ @author =
+ if author? && params[:author_id] != NONE
+ User.find(params[:author_id])
+ else
+ nil
+ end
+ end
+
private
def init_collection
@@ -75,6 +148,8 @@ class IssuableFinder
case params[:state]
when 'closed'
items.closed
+ when 'merged'
+ items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
items
when 'opened'
@@ -85,25 +160,19 @@ class IssuableFinder
end
def by_group(items)
- if params[:group_id].present?
- items = items.of_group(Group.find(params[:group_id]))
- end
+ items = items.of_group(group) if group
items
end
def by_project(items)
- if params[:project_id].present?
- items = items.of_projects(params[:project_id])
- end
+ items = items.of_projects(project.id) if project
items
end
def by_search(items)
- if params[:search].present?
- items = items.search(params[:search])
- end
+ items = items.search(search) if search
items
end
@@ -113,25 +182,24 @@ class IssuableFinder
end
def by_milestone(items)
- if params[:milestone_title].present?
- milestone_ids = (params[:milestone_title] == NONE ? nil : Milestone.where(title: params[:milestone_title]).pluck(:id))
- items = items.where(milestone_id: milestone_ids)
+ if milestones?
+ items = items.where(milestone_id: milestones.try(:pluck, :id))
end
items
end
def by_assignee(items)
- if params[:assignee_id].present?
- items = items.where(assignee_id: (params[:assignee_id] == NONE ? nil : params[:assignee_id]))
+ if assignee?
+ items = items.where(assignee_id: assignee.try(:id))
end
items
end
def by_author(items)
- if params[:author_id].present?
- items = items.where(author_id: (params[:author_id] == NONE ? nil : params[:author_id]))
+ if author?
+ items = items.where(author_id: author.try(:id))
end
items
@@ -151,10 +219,6 @@ class IssuableFinder
items
end
- def project
- Project.where(id: params[:project_id]).first if params[:project_id].present?
- end
-
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
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 bcd400b7e7b..0b46db4b1c3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -2,26 +2,6 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
- COLOR_SCHEMES = {
- 1 => 'white',
- 2 => 'dark',
- 3 => 'solarized-light',
- 4 => 'solarized-dark',
- 5 => 'monokai',
- }
- COLOR_SCHEMES.default = 'white'
-
- # Helper method to access the COLOR_SCHEMES
- #
- # The keys are the `color_scheme_ids`
- # The values are the `name` of the scheme.
- #
- # The preview images are `name-scheme-preview.png`
- # The stylesheets should use the css class `.name`
- def color_schemes
- COLOR_SCHEMES.freeze
- end
-
# Check if a particular controller is the current one
#
# args - One or more controller names to check
@@ -138,18 +118,6 @@ module ApplicationHelper
Emoji.names.to_s
end
- def app_theme
- Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
- end
-
- def theme_type
- Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
- end
-
- def user_color_scheme_class
- COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
- end
-
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
@@ -211,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 }
+ # 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
- haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
- end.html_safe
+ element
end
def render_markup(file_name, file_content)
@@ -246,43 +233,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 escaped_autolink(text)
- auto_link ERB::Util.html_escape(text), link: :urls
- end
-
def promo_host
'about.gitlab.com'
end
@@ -330,7 +280,11 @@ module ApplicationHelper
end
def state_filters_text_for(entity, project)
- entity_title = entity.to_s.humanize
+ titles = {
+ opened: "Open"
+ }
+
+ entity_title = titles[entity] || entity.to_s.humanize
count =
if project.nil?
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 241d6075c9f..63c3ff5674d 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -19,6 +19,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text
end
+ def user_oauth_applications?
+ current_application_settings.user_oauth_applications
+ end
+
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 9fe5f82f02f..50df3801703 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,6 +1,6 @@
module BlobHelper
- def highlight(blob_name, blob_content, nowrap = false)
- formatter = Rugments::Formatters::HTML.new(
+ def highlight(blob_name, blob_content, nowrap: false, continue: false)
+ @formatter ||= Rugments::Formatters::HTML.new(
nowrap: nowrap,
cssclass: 'code highlight',
lineanchors: true,
@@ -8,11 +8,11 @@ module BlobHelper
)
begin
- lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content)
- result = formatter.format(lexer.lex(blob_content)).html_safe
+ @lexer ||= Rugments::Lexer.guess(filename: blob_name, source: blob_content).new
+ result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
lexer = Rugments::Lexers::PlainText
- result = formatter.format(lexer.lex(blob_content)).html_safe
+ result = @formatter.format(lexer.lex(blob_content)).html_safe
end
result
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 7bcc011fd5f..a801d3b10aa 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -1,5 +1,8 @@
+require 'nokogiri'
+
module GitlabMarkdownHelper
include Gitlab::Markdown
+ include PreferencesHelper
# Use this in places where you would normally use link_to(gfm(...), ...).
#
@@ -21,36 +24,44 @@ module GitlabMarkdownHelper
gfm_body = gfm(escaped_body, {}, html_options)
- gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
- "</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".length +1
+ fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
+ if fragment.children.size == 1 && fragment.children[0].name == 'a'
+ # Fragment has only one node, and it's a link generated by `gfm`.
+ # Replace it with our requested link.
+ text = fragment.children[0].text
+ fragment.children[0].replace(link_to(text, url, html_options))
+ else
+ # Traverse the fragment's first generation of children looking for pure
+ # text, wrapping anything found in the requested link
+ fragment.children.each do |node|
+ next unless node.text?
+ node.replace(link_to(node.text, url, html_options))
+ end
end
- link_to(gfm_body.html_safe, url, html_options)
+ fragment.to_html.html_safe
end
+ MARKDOWN_OPTIONS = {
+ no_intra_emphasis: true,
+ tables: true,
+ fenced_code_blocks: true,
+ strikethrough: true,
+ lax_spacing: true,
+ space_after_headers: true,
+ superscript: true,
+ footnotes: true
+ }.freeze
+
def markdown(text, options={})
unless @markdown && options == @options
@options = options
- options.merge!(
- # Handled further down the line by Gitlab::Markdown::SanitizationFilter
- escape_html: false
- )
-
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch
rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options)
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
- @markdown = Redcarpet::Markdown.new(rend,
- no_intra_emphasis: true,
- tables: true,
- fenced_code_blocks: true,
- strikethrough: true,
- lax_spacing: true,
- space_after_headers: true,
- superscript: true,
- footnotes: true
- )
+ @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS)
end
@markdown.render(text).html_safe
@@ -87,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
+ "Tip: #{MARKDOWN_TIPS.sample}"
+ end
+
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
@@ -135,15 +169,25 @@ module GitlabMarkdownHelper
end
end
+ # Returns the text necessary to reference `entity` across projects
+ #
+ # project - Project to reference
+ # entity - Object that responds to `to_reference`
+ #
+ # Examples:
+ #
+ # cross_project_reference(project, project.issues.first)
+ # # => 'namespace1/project1#123'
+ #
+ # cross_project_reference(project, project.merge_requests.first)
+ # # => 'namespace1/project1!345'
+ #
+ # Returns a String
def cross_project_reference(project, entity)
- path = project.path_with_namespace
-
- if entity.kind_of?(Issue)
- [path, entity.iid].join('#')
- elsif entity.kind_of?(MergeRequest)
- [path, entity.iid].join('!')
+ if entity.respond_to?(:to_reference)
+ "#{project.to_reference}#{entity.to_reference}"
else
- raise 'Not supported type'
+ ''
end
end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 9703c8d9e9c..9d072f81092 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -52,4 +52,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/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/labels_helper.rb b/app/helpers/labels_helper.rb
index 8272c177d59..8036303851b 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,6 +1,44 @@
module LabelsHelper
include ActionView::Helpers::TagHelper
+ # Link to a Label
+ #
+ # label - Label object to link to
+ # project - Project object which will be used as the context for the label's
+ # link. If omitted, defaults to `@project`, or the label's own
+ # project.
+ # block - An optional block that will be passed to `link_to`, forming the
+ # body of the link element. If omitted, defaults to
+ # `render_colored_label`.
+ #
+ # Examples:
+ #
+ # # Allow the generated link to use the label's own project
+ # link_to_label(label)
+ #
+ # # Force the generated link to use @project
+ # @project = Project.first
+ # link_to_label(label)
+ #
+ # # Force the generated link to use a provided project
+ # link_to_label(label, project: Project.last)
+ #
+ # # Customize link body with a block
+ # link_to_label(label) { "My Custom Label Text" }
+ #
+ # Returns a String
+ def link_to_label(label, project: nil, &block)
+ project ||= @project || label.project
+ link = namespace_project_issues_path(project.namespace, project,
+ label_name: label.name)
+
+ if block_given?
+ link_to link, &block
+ else
+ link_to render_colored_label(label), link
+ end
+ end
+
def project_label_names
@project.labels.pluck(:title)
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 271b53aa2b6..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
@@ -47,7 +47,7 @@ module NotesHelper
}.to_json
end
- def link_to_new_diff_note(line_code)
+ def link_to_new_diff_note(line_code, line_type = nil)
discussion_id = Note.build_discussion_id(
@comments_target[:noteable_type],
@comments_target[:noteable_id] || @comments_target[:commit_id],
@@ -59,7 +59,8 @@ module NotesHelper
noteable_id: @comments_target[:noteable_id],
commit_id: @comments_target[:commit_id],
line_code: line_code,
- discussion_id: discussion_id
+ discussion_id: discussion_id,
+ line_type: line_type
}
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
@@ -69,7 +70,7 @@ module NotesHelper
end
end
- def link_to_reply_diff(note)
+ def link_to_reply_diff(note, line_type = nil)
return unless current_user
data = {
@@ -77,7 +78,8 @@ module NotesHelper
noteable_id: note.noteable_id,
commit_id: note.commit_id,
line_code: note.line_code,
- discussion_id: note.discussion_id
+ discussion_id: note.discussion_id,
+ line_type: line_type
}
button_tag class: 'btn reply-btn js-discussion-reply-button',
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
new file mode 100644
index 00000000000..bceff4fd52e
--- /dev/null
+++ b/app/helpers/preferences_helper.rb
@@ -0,0 +1,53 @@
+# Helper methods for per-User preferences
+module PreferencesHelper
+ COLOR_SCHEMES = {
+ 1 => 'white',
+ 2 => 'dark',
+ 3 => 'solarized-light',
+ 4 => 'solarized-dark',
+ 5 => 'monokai',
+ }
+ COLOR_SCHEMES.default = 'white'
+
+ # Helper method to access the COLOR_SCHEMES
+ #
+ # The keys are the `color_scheme_ids`
+ # The values are the `name` of the scheme.
+ #
+ # The preview images are `name-scheme-preview.png`
+ # The stylesheets should use the css class `.name`
+ def color_schemes
+ COLOR_SCHEMES.freeze
+ end
+
+ # Maps `dashboard` values to more user-friendly option text
+ DASHBOARD_CHOICES = {
+ projects: 'Your Projects (default)',
+ stars: 'Starred Projects'
+ }.with_indifferent_access.freeze
+
+ # Returns an Array usable by a select field for more user-friendly option text
+ def dashboard_choices
+ defined = User.dashboards
+
+ if defined.size != DASHBOARD_CHOICES.size
+ # Ensure that anyone adding new options updates this method too
+ raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
+ " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
+ else
+ defined.map do |key, _|
+ # Use `fetch` so `KeyError` gets raised when a key is missing
+ [DASHBOARD_CHOICES.fetch(key), key]
+ end
+ end
+ end
+
+ def user_application_theme
+ theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
+ theme.css_class
+ end
+
+ def user_color_scheme_class
+ COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 96d2606f1a1..ec65e473919 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -148,7 +148,7 @@ module ProjectsHelper
nav_tabs << [:files, :commits, :network, :graphs]
end
- if project.repo_exists? && project.merge_requests_enabled
+ if project.repo_exists? && can?(current_user, :read_merge_request, project)
nav_tabs << :merge_requests
end
@@ -156,11 +156,19 @@ module ProjectsHelper
nav_tabs << :settings
end
- [:issues, :wiki, :snippets].each do |feature|
- nav_tabs << feature if project.send :"#{feature}_enabled"
+ if can?(current_user, :read_issue, project)
+ nav_tabs << :issues
end
- if project.issues_enabled || project.merge_requests_enabled
+ if can?(current_user, :read_wiki, project)
+ nav_tabs << :wiki
+ end
+
+ if can?(current_user, :read_project_snippet, project)
+ nav_tabs << :snippets
+ end
+
+ if can?(current_user, :read_milestone, project)
nav_tabs << [:milestones, :labels]
end
@@ -203,7 +211,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
@@ -286,4 +294,16 @@ module ProjectsHelper
nil
end
end
+
+ def user_max_access_in_project(user, project)
+ level = project.team.max_member_access(user)
+
+ if level
+ Gitlab::Access.options_with_owner.key(level)
+ end
+ end
+
+ def leave_project_message(project)
+ "Are you sure you want to leave \"#{project.name}\" project?"
+ 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/tab_helper.rb b/app/helpers/tab_helper.rb
index a1d263d9d3a..77727337f07 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -89,7 +89,7 @@ module TabHelper
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
- if ['services', 'hooks', 'deploy_keys', 'project_members', 'protected_branches'].include? controller.controller_name
+ if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
"active"
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 9cb7077e59d..4a6e18e6a74 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -93,7 +93,8 @@ module Emails
"pushed to"
end
- @subject = "[#{@project.path_with_namespace}]"
+ @subject = "[Git]"
+ @subject << "[#{@project.path_with_namespace}]"
@subject << "[#{@ref_name}]" if action == :push
@subject << " "
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 04d9dccf916..3ee3a7857ee 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -68,6 +68,7 @@ class Ability
def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}"
+
RequestStore.store[key] ||= begin
team = project.team
@@ -109,8 +110,13 @@ class Ability
rules -= named_abilities('merge_request')
end
+ unless project.issues_enabled or project.merge_requests_enabled
+ rules -= named_abilities('label')
+ rules -= named_abilities('milestone')
+ end
+
unless project.snippets_enabled
- rules -= named_abilities('snippet')
+ rules -= named_abilities('project_snippet')
end
unless project.wiki_enabled
@@ -133,14 +139,15 @@ class Ability
:read_project,
:read_wiki,
:read_issue,
+ :read_label,
:read_milestone,
:read_project_snippet,
:read_project_member,
:read_merge_request,
:read_note,
- :write_project,
- :write_issue,
- :write_note
+ :create_project,
+ :create_issue,
+ :create_note
]
end
@@ -148,15 +155,15 @@ class Ability
project_guest_rules + [
:download_code,
:fork_project,
- :write_project_snippet
+ :create_project_snippet
]
end
def project_dev_rules
project_report_rules + [
- :write_merge_request,
- :write_wiki,
- :modify_issue,
+ :create_merge_request,
+ :create_wiki,
+ :update_issue,
:admin_issue,
:admin_label,
:push_code
@@ -165,10 +172,10 @@ class Ability
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
@@ -176,10 +183,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,
@@ -239,30 +244,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
@@ -271,13 +286,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
@@ -294,8 +312,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/application_setting.rb b/app/models/application_setting.rb
index d5123249c53..fee52694099 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -15,9 +15,12 @@
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# max_attachment_size :integer default(10), not null
+# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
+# user_oauth_applications :bool default(TRUE)
+# after_sign_out_path :string(255)
#
class ApplicationSetting < ActiveRecord::Base
@@ -25,11 +28,19 @@ class ApplicationSetting < ActiveRecord::Base
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
+ validates :session_expire_delay,
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :home_page_url,
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
if: :home_page_url_column_exist
+ validates :after_sign_out_path,
+ allow_blank: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -55,6 +66,7 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
+ session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains']
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be5a118bfec..aff329d71fa 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -1,9 +1,11 @@
class Commit
- include ActiveModel::Conversion
- include StaticModel
extend ActiveModel::Naming
+
+ include ActiveModel::Conversion
include Mentionable
include Participable
+ include Referable
+ include StaticModel
attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users
@@ -56,6 +58,34 @@ class Commit
@raw.id
end
+ def ==(other)
+ (self.class === other) && (raw == other.raw)
+ end
+
+ def self.reference_prefix
+ '@'
+ end
+
+ # Pattern used to extract commit references from text
+ #
+ # The SHA can be between 6 and 40 hex characters.
+ #
+ # This pattern supports cross-project references.
+ def self.reference_pattern
+ %r{
+ (?:#{Project.reference_pattern}#{reference_prefix})?
+ (?<commit>\h{6,40})
+ }x
+ end
+
+ def to_reference(from_project = nil)
+ if cross_project_reference?(from_project)
+ "#{project.to_reference}@#{id}"
+ else
+ id
+ end
+ end
+
def diff_line_count
@diff_line_count ||= Commit::diff_line_count(self.diffs)
@diff_line_count
@@ -126,17 +156,12 @@ class Commit
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
end
- # Mentionable override.
- def gfm_reference
- "commit #{id}"
- 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
@@ -147,10 +172,8 @@ class Commit
@raw.send(m, *args, &block)
end
- def respond_to?(method)
- return true if @raw.respond_to?(method)
-
- super
+ def respond_to_missing?(method, include_private = false)
+ @raw.respond_to?(method, include_private) || super
end
# Truncate sha to 8 characters
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index e6456198264..86fc9eb01a3 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -19,6 +19,7 @@
#
class CommitRange
include ActiveModel::Conversion
+ include Referable
attr_reader :sha_from, :notation, :sha_to
@@ -28,10 +29,24 @@ class CommitRange
# See `exclude_start?`
attr_reader :exclude_start
- # The beginning and ending SHA sums can be between 6 and 40 hex characters,
- # and the range selection can be double- or triple-dot.
+ # The beginning and ending SHAs can be between 6 and 40 hex characters, and
+ # the range notation can be double- or triple-dot.
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+ def self.reference_prefix
+ '@'
+ end
+
+ # Pattern used to extract commit range references from text
+ #
+ # This pattern supports cross-project references.
+ def self.reference_pattern
+ %r{
+ (?:#{Project.reference_pattern}#{reference_prefix})?
+ (?<commit_range>#{PATTERN})
+ }x
+ end
+
# Initialize a CommitRange
#
# range_string - The String commit range.
@@ -59,6 +74,17 @@ class CommitRange
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
end
+ def to_reference(from_project = nil)
+ # Not using to_s because we want the full SHAs
+ reference = sha_from + notation + sha_to
+
+ if cross_project_reference?(from_project)
+ reference = project.to_reference + '@' + reference
+ end
+
+ reference
+ end
+
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}"
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index b7c39df885d..56849f28ff0 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -20,10 +20,15 @@ module Mentionable
end
end
- # Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must
- # be overridden if this model object can be referenced directly by GFM notation.
- def gfm_reference
- raise NotImplementedError.new("#{self.class} does not implement #gfm_reference")
+ # Returns the text used as the body of a Note when this object is referenced
+ #
+ # By default this will be the class name and the result of calling
+ # `to_reference` on the object.
+ def gfm_reference(from_project = nil)
+ # "MergeRequest" > "merge_request" > "Merge request" > "merge request"
+ friendly_name = self.class.to_s.underscore.humanize.downcase
+
+ "#{friendly_name} #{to_reference(from_project)}"
end
# Construct a String that contains possible GFM references.
@@ -62,9 +67,15 @@ module Mentionable
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(p = project, a = author, without = [])
- refs = references(p) - without
+ refs = references(p)
+
+ # We're using this method instead of Array diffing because that requires
+ # both of the object's `hash` values to be the same, which may not be the
+ # case for otherwise identical Commit objects.
+ 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
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/concerns/referable.rb b/app/models/concerns/referable.rb
new file mode 100644
index 00000000000..cced66cc1e4
--- /dev/null
+++ b/app/models/concerns/referable.rb
@@ -0,0 +1,61 @@
+# == Referable concern
+#
+# Contains functionality related to making a model referable in Markdown, such
+# as "#1", "!2", "~3", etc.
+module Referable
+ extend ActiveSupport::Concern
+
+ # Returns the String necessary to reference this object in Markdown
+ #
+ # from_project - Refering Project object
+ #
+ # This should be overridden by the including class.
+ #
+ # Examples:
+ #
+ # Issue.first.to_reference # => "#1"
+ # Issue.last.to_reference(other_project) # => "cross-project#1"
+ #
+ # Returns a String
+ def to_reference(_from_project = nil)
+ ''
+ end
+
+ module ClassMethods
+ # The character that prefixes the actual reference identifier
+ #
+ # This should be overridden by the including class.
+ #
+ # Examples:
+ #
+ # Issue.reference_prefix # => '#'
+ # MergeRequest.reference_prefix # => '!'
+ #
+ # Returns a String
+ def reference_prefix
+ ''
+ end
+
+ # Regexp pattern used to match references to this object
+ #
+ # This must be overridden by the including class.
+ #
+ # Returns a Regexp
+ def reference_pattern
+ raise NotImplementedError, "#{self} does not implement #{__method__}"
+ end
+ end
+
+ private
+
+ # Check if a reference is being done cross-project
+ #
+ # from_project - Refering Project object
+ def cross_project_reference?(from_project)
+ if self.is_a?(Project)
+ self != from_project
+ else
+ from_project && self.project && self.project != from_project
+ end
+ end
+end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 33b4814d7ec..660e58b876d 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -1,4 +1,5 @@
require 'task_list'
+require 'task_list/filter'
# Contains functionality for objects that can have task lists in their
# descriptions. Task list items can be added with Markdown like "* [x] Fix
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 85fdb12bfdc..49f6c95e045 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -1,4 +1,6 @@
class ExternalIssue
+ include Referable
+
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
@@ -26,4 +28,13 @@ class ExternalIssue
def project
@project
end
+
+ # Pattern used to extract `JIRA-123` issue references from text
+ def self.reference_pattern
+ %r{(?<issue>([A-Z\-]+-)\d+)}
+ end
+
+ def to_reference(_from_project = nil)
+ id
+ end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 687458adac4..051c672cb33 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -17,6 +17,8 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Group < Namespace
+ include Referable
+
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members
@@ -36,6 +38,18 @@ class Group < Namespace
def sort(method)
order_by(method)
end
+
+ def reference_prefix
+ User.reference_prefix
+ end
+
+ def reference_pattern
+ User.reference_pattern
+ end
+ end
+
+ def to_reference(_from_project = nil)
+ "#{self.class.reference_prefix}#{name}"
end
def human_name
@@ -87,10 +101,14 @@ class Group < Namespace
end
def post_create_hook
+ Gitlab::AppLogger.info("Group \"#{name}\" was created")
+
system_hook_service.execute_hooks_for(self, :create)
end
def post_destroy_hook
+ Gitlab::AppLogger.info("Group \"#{name}\" was removed")
+
system_hook_service.execute_hooks_for(self, :destroy)
end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 7e4f16ebf16..ab055f6b80b 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -44,7 +44,7 @@ class GroupMilestone
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
- 100
+ 0
end
def state
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/issue.rb b/app/models/issue.rb
index 6e102051387..2456b7d0dc1 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -21,10 +21,11 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Issue < ActiveRecord::Base
- include Issuable
include InternalId
- include Taskable
+ include Issuable
+ include Referable
include Sortable
+ include Taskable
ActsAsTaggableOn.strict_case_match = true
@@ -53,10 +54,28 @@ class Issue < ActiveRecord::Base
attributes
end
- # Mentionable overrides.
+ def self.reference_prefix
+ '#'
+ end
+
+ # Pattern used to extract `#123` issue references from text
+ #
+ # This pattern supports cross-project references.
+ def self.reference_pattern
+ %r{
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}(?<issue>\d+)
+ }x
+ end
+
+ def to_reference(from_project = nil)
+ reference = "#{self.class.reference_prefix}#{iid}"
+
+ if cross_project_reference?(from_project)
+ reference = project.to_reference + reference
+ end
- def gfm_reference
- "issue ##{iid}"
+ reference
end
# Reset issue events cache
diff --git a/app/models/label.rb b/app/models/label.rb
index eee28acefc1..230631b5180 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -11,6 +11,8 @@
#
class Label < ActiveRecord::Base
+ include Referable
+
DEFAULT_COLOR = '#428BCA'
default_value_for :color, DEFAULT_COLOR
@@ -34,6 +36,45 @@ class Label < ActiveRecord::Base
alias_attribute :name, :title
+ def self.reference_prefix
+ '~'
+ end
+
+ # Pattern used to extract label references from text
+ def self.reference_pattern
+ %r{
+ #{reference_prefix}
+ (?:
+ (?<label_id>\d+) | # Integer-based label ID, or
+ (?<label_name>
+ [A-Za-z0-9_-]+ | # String-based single-word label title, or
+ "[^&\?,]+" # String-based multi-word label surrounded in quotes
+ )
+ )
+ }x
+ end
+
+ # Returns the String necessary to reference this Label in Markdown
+ #
+ # format - Symbol format to use (default: :id, optional: :name)
+ #
+ # Note that its argument differs from other objects implementing Referable. If
+ # a non-Symbol argument is given (such as a Project), it will default to :id.
+ #
+ # Examples:
+ #
+ # Label.first.to_reference # => "~1"
+ # Label.first.to_reference(:name) # => "~\"bug\""
+ #
+ # Returns a String
+ def to_reference(format = :id)
+ if format == :name && !name.include?('"')
+ %(#{self.class.reference_prefix}"#{name}")
+ else
+ "#{self.class.reference_prefix}#{id}"
+ end
+ end
+
def open_issues_count
issues.opened.count
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 64f3c39f131..7ecdaf6b2e0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -25,10 +25,11 @@ require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
- include Issuable
- include Taskable
include InternalId
+ include Issuable
+ include Referable
include Sortable
+ include Taskable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
@@ -124,16 +125,38 @@ 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 :declined, -> { 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
+ '!'
+ end
+
+ # Pattern used to extract `!123` merge request references from text
+ #
+ # This pattern supports cross-project references.
+ def self.reference_pattern
+ %r{
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
+ }x
+ end
+
+ def to_reference(from_project = nil)
+ reference = "#{self.class.reference_prefix}#{iid}"
+
+ if cross_project_reference?(from_project)
+ reference = project.to_reference + reference
+ end
+
+ reference
+ end
def validate_branches
if target_project == source_project && target_branch == source_branch
@@ -172,7 +195,6 @@ class MergeRequest < ActiveRecord::Base
def update_merge_request_diff
if source_branch_changed? || target_branch_changed?
reload_code
- mark_as_unchecked
end
end
@@ -289,11 +311,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- # Mentionable override.
- def gfm_reference
- "merge request !#{iid}"
- end
-
def target_project_path
if target_project
target_project.path_with_namespace
@@ -386,4 +403,26 @@ class MergeRequest < ActiveRecord::Base
locked_at.nil? || locked_at < (Time.now - 1.day)
end
+
+ def has_ci?
+ source_project.ci_service && commits.any?
+ end
+
+ def branch_missing?
+ !source_branch_exists? || !target_branch_exists?
+ end
+
+ 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 9bbb2bafb98..e0c5fec97b7 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -56,7 +56,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
@@ -66,7 +66,7 @@ class Milestone < ActiveRecord::Base
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
- 100
+ 0
end
def expires_at
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 211dfa76b81..03d2ab165ea 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -72,7 +72,7 @@ class Namespace < ActiveRecord::Base
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
- # Work around that by setting their username to "blank", followed by a counter.
+ # Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
counter = 0
@@ -99,7 +99,18 @@ class Namespace < ActiveRecord::Base
end
def rm_dir
- gitlab_shell.rm_namespace(path)
+ # Move namespace directory into trash.
+ # We will remove it later async
+ new_path = "#{path}+#{id}+deleted"
+
+ if gitlab_shell.mv_namespace(path, new_path)
+ message = "Namespace directory \"#{path}\" moved to \"#{new_path}\""
+ Gitlab::AppLogger.info message
+
+ # Remove namespace directroy async with delay so
+ # GitLab has time to remove all projects first
+ GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path)
+ end
end
def move_dir
diff --git a/app/models/note.rb b/app/models/note.rb
index 6939a7e73a0..68b9d433ae0 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -31,7 +31,7 @@ class Note < ActiveRecord::Base
participant :author, :mentioned_users
belongs_to :project
- belongs_to :noteable, polymorphic: true
+ belongs_to :noteable, polymorphic: true, touch: true
belongs_to :author, class_name: "User"
delegate :name, to: :project, prefix: true
@@ -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 = []
@@ -326,8 +321,8 @@ class Note < ActiveRecord::Base
end
# Mentionable override.
- def gfm_reference
- noteable.gfm_reference
+ def gfm_reference(from_project = nil)
+ noteable.gfm_reference(from_project)
end
# Mentionable override.
diff --git a/app/models/project.rb b/app/models/project.rb
index 09d3ffd22fe..b161cbe86b9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -33,11 +33,12 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Project < ActiveRecord::Base
- include Sortable
+ include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
- include Gitlab::ConfigHelper
include Rails.application.routes.url_helpers
+ include Referable
+ include Sortable
extend Gitlab::ConfigHelper
extend Enumerize
@@ -157,7 +158,7 @@ class Project < ActiveRecord::Base
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
- scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
+ scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
@@ -247,6 +248,11 @@ class Project < ActiveRecord::Base
order_by(method)
end
end
+
+ def reference_pattern
+ name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+ %r{(?<project>#{name_pattern}/#{name_pattern})}
+ end
end
def team
@@ -305,6 +311,10 @@ class Project < ActiveRecord::Base
path
end
+ def to_reference(_from_project = nil)
+ path_with_namespace
+ end
+
def web_url
[gitlab_config.url, path_with_namespace].join('/')
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 949a4d7111b..19b5859d5c9 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -40,6 +40,16 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
+ sha = data[:checkout_sha]
+
+ if sha.present?
+ file = ci_yaml_file(sha)
+
+ if file && file.data
+ data.merge!(ci_yaml_file: file.data)
+ end
+ end
+
service_hook.execute(data)
end
@@ -123,7 +133,15 @@ class GitlabCiService < CiService
private
+ def ci_yaml_file(sha)
+ repository.blob_at(sha, '.gitlab-ci.yml')
+ end
+
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
end
+
+ def repository
+ project.repository
+ end
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 38cb64f8c48..6761f00183e 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -63,7 +63,7 @@ class HipchatService < Service
private
def gate
- options = { api_version: api_version || 'v2' }
+ options = { api_version: api_version.present? ? api_version : 'v2' }
options[:server_url] = server unless server.blank?
@gate ||= HipChat::Client.new(token, options)
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index c8ab9d63b74..936e574cccd 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -81,18 +81,13 @@ class IssueTrackerService < Service
result = false
begin
- url = URI.parse(self.project_url)
+ response = HTTParty.head(self.project_url, verify: true)
- if url.host && url.port
- http = Net::HTTP.start(url.host, url.port, { open_timeout: 5, read_timeout: 5 })
- response = http.head("/")
-
- if response
- message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
- result = true
- end
+ if response
+ message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
+ result = true
end
- rescue Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error
+ rescue HTTParty::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
Rails.logger.info(message)
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 0706a1ca0d1..231973fa543 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -2,7 +2,7 @@ class ProjectWiki
include Gitlab::ShellAdapter
MARKUPS = {
- 'Markdown' => :markdown,
+ 'Markdown' => :md,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
} unless defined?(MARKUPS)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1b8c74028d9..b32e8847bb5 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
@@ -163,10 +168,8 @@ class Repository
end
end
- def respond_to?(method)
- return true if raw_repository.respond_to?(method)
-
- super
+ def respond_to_missing?(method, include_private = false)
+ raw_repository.respond_to?(method, include_private) || super
end
def blob_at(sha, path)
@@ -370,8 +373,55 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
+ def commit_file(user, path, content, message, ref)
+ path[0] = '' if path[0] == '/'
+
+ committer = user_to_comitter(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+ options[:commit] = {
+ message: message,
+ branch: ref
+ }
+
+ options[:file] = {
+ content: content,
+ path: path
+ }
+
+ Gitlab::Git::Blob.commit(raw_repository, options)
+ end
+
+ def remove_file(user, path, message, ref)
+ path[0] = '' if path[0] == '/'
+
+ committer = user_to_comitter(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+ options[:commit] = {
+ message: message,
+ branch: ref
+ }
+
+ options[:file] = {
+ path: path
+ }
+
+ Gitlab::Git::Blob.remove(raw_repository, options)
+ end
+
private
+ def user_to_comitter(user)
+ {
+ email: user.email,
+ name: user.name,
+ time: Time.now
+ }
+ end
+
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index d2af26539b6..b0831982aa7 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -16,14 +16,16 @@
#
class Snippet < ActiveRecord::Base
- include Sortable
- include Linguist::BlobHelper
include Gitlab::VisibilityLevel
+ include Linguist::BlobHelper
include Participable
+ include Referable
+ include Sortable
default_value_for :visibility_level, Snippet::PRIVATE
- belongs_to :author, class_name: "User"
+ belongs_to :author, class_name: 'User'
+ belongs_to :project
has_many :notes, as: :noteable, dependent: :destroy
@@ -32,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 }
@@ -50,6 +51,30 @@ class Snippet < ActiveRecord::Base
participant :author, :notes
+ def self.reference_prefix
+ '$'
+ end
+
+ # Pattern used to extract `$123` snippet references from text
+ #
+ # This pattern supports cross-project references.
+ def self.reference_pattern
+ %r{
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}(?<snippet>\d+)
+ }x
+ end
+
+ def to_reference(from_project = nil)
+ reference = "#{self.class.reference_prefix}#{id}"
+
+ if cross_project_reference?(from_project)
+ reference = project.to_reference + reference
+ end
+
+ reference
+ end
+
def self.content_types
[
".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
diff --git a/app/models/user.rb b/app/models/user.rb
index 4dd37e73564..dc84f5141d8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -53,20 +53,23 @@
# 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)
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
- include Sortable
- include Gitlab::ConfigHelper
- include TokenAuthenticatable
extend Gitlab::ConfigHelper
+
+ include Gitlab::ConfigHelper
include Gitlab::CurrentSettings
+ include Referable
+ include Sortable
+ include TokenAuthenticatable
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
@@ -77,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
@@ -134,7 +138,9 @@ class User < ActiveRecord::Base
# Validations
#
validates :name, presence: true
- validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
+ # Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to
+ # duplicate that here as the validation framework will have duplicate errors in the event of a failure.
+ validates :email, presence: true, email: { strict_mode: true }
validates :notification_email, presence: true, email: { strict_mode: true }
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
@@ -167,6 +173,9 @@ 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]
alias_attribute :private_token, :authentication_token
@@ -185,11 +194,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
@@ -214,18 +225,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
@@ -247,6 +277,18 @@ class User < ActiveRecord::Base
def build_user(attrs = {})
User.new(attrs)
end
+
+ def reference_prefix
+ '@'
+ end
+
+ # Pattern used to extract `@user` user references from text
+ def reference_pattern
+ %r{
+ #{Regexp.escape(reference_prefix)}
+ (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
+ }x
+ end
end
#
@@ -257,6 +299,10 @@ class User < ActiveRecord::Base
username
end
+ def to_reference(_from_project = nil)
+ "#{self.class.reference_prefix}#{username}"
+ end
+
def notification
@notification ||= Notification.new(self)
end
@@ -301,6 +347,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
@@ -334,9 +382,11 @@ class User < ActiveRecord::Base
end
def owned_projects
- @owned_projects ||= begin
- Project.where(namespace_id: owned_groups.pluck(:id).push(namespace.id)).joins(:namespace)
- end
+ @owned_projects ||=
+ begin
+ namespace_ids = owned_groups.pluck(:id).push(namespace.id)
+ Project.in_namespace(namespace_ids).joins(:namespace)
+ end
end
# Team membership in authorized projects
@@ -465,7 +515,7 @@ class User < ActiveRecord::Base
end
def sanitize_attrs
- %w(name username skype linkedin twitter bio).each do |attr|
+ %w(name username skype linkedin twitter).each do |attr|
value = self.send(attr)
self.send("#{attr}=", Sanitize.clean(value)) if value.present?
end
@@ -479,7 +529,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
@@ -637,6 +687,12 @@ class User < ActiveRecord::Base
end
end
+ def namespaces
+ namespace_ids = groups.pluck(:id)
+ namespace_ids.push(namespace.id)
+ Namespace.where(id: namespace_ids)
+ end
+
def oauth_authorized_tokens
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end
@@ -671,4 +727,8 @@ class User < ActiveRecord::Base
true
end
+
+ def can_be_removed?
+ !solo_owned_groups.present?
+ end
end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
new file mode 100644
index 00000000000..e622fd5ea5d
--- /dev/null
+++ b/app/services/delete_user_service.rb
@@ -0,0 +1,22 @@
+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'
+ user
+ else
+ user.personal_projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
+ user.destroy
+ end
+ end
+end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
new file mode 100644
index 00000000000..d929a676293
--- /dev/null
+++ b/app/services/destroy_group_service.rb
@@ -0,0 +1,17 @@
+class DestroyGroupService
+ attr_accessor :group, :current_user
+
+ def initialize(group, user)
+ @group, @current_user = group, user
+ end
+
+ def execute
+ @group.projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
+ @group.destroy
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index bd245100955..f587ee266da 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,11 +1,34 @@
module Files
class BaseService < ::BaseService
- attr_reader :ref, :path
+ class ValidationError < StandardError; end
- def initialize(project, user, params, ref, path = nil)
- @project, @current_user, @params = project, user, params.dup
- @ref = ref
- @path = path
+ def execute
+ @current_branch = params[:current_branch]
+ @target_branch = params[:target_branch]
+ @commit_message = params[:commit_message]
+ @file_path = params[:file_path]
+ @file_content = if params[:file_content_encoding] == 'base64'
+ Base64.decode64(params[:file_content])
+ else
+ params[:file_content]
+ end
+
+ # Validate parameters
+ validate
+
+ # Create new branch if it different from current_branch
+ if @target_branch != @current_branch
+ create_target_branch
+ end
+
+ if sha = commit
+ after_commit(sha, @target_branch)
+ success
+ else
+ error("Something went wrong. Your changes were not committed")
+ end
+ rescue ValidationError => ex
+ error(ex.message)
end
private
@@ -13,5 +36,52 @@ module Files
def repository
project.repository
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 current_branch
+ @current_branch ||= params[:current_branch]
+ end
+
+ def target_branch
+ @target_branch ||= params[:target_branch]
+ end
+
+ def raise_error(message)
+ raise ValidationError.new(message)
+ end
+
+ def validate
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise_error("You are not allowed to push into this branch")
+ end
+
+ unless project.empty_repo?
+ unless repository.branch_names.include?(@current_branch)
+ raise_error("You can only create files if you are on top of a branch")
+ end
+
+ if @current_branch != @target_branch
+ if repository.branch_names.include?(@target_branch)
+ raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
+ end
+ end
+ end
+ end
+
+ def create_target_branch
+ result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
+
+ unless result[:status] == :success
+ raise_error("Something went wrong when we tried to create #{@target_branch} for you")
+ end
+ end
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 23833aa78ec..91d715b2d63 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,52 +1,30 @@
require_relative "base_service"
module Files
- class CreateService < BaseService
- def execute
- allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+ class CreateService < Files::BaseService
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ end
- unless allowed
- return error("You are not allowed to create file in this branch")
- end
+ def validate
+ super
- file_name = File.basename(path)
- file_path = path
+ file_name = File.basename(@file_path)
unless file_name =~ Gitlab::Regex.file_name_regex
- return error(
+ raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message
)
end
- if project.empty_repo?
- # everything is ok because repo does not have a commits yet
- else
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, file_path)
+ unless project.empty_repo?
+ blob = repository.blob_at_branch(@current_branch, @file_path)
if blob
- return error("Your changes could not be committed, because file with such name exists")
+ raise_error("Your changes could not be committed, because file with such name exists")
end
end
-
-
- new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
- created_successfully = new_file_action.commit!(
- params[:content],
- params[:commit_message],
- params[:encoding],
- params[:new_branch]
- )
-
- if created_successfully
- success
- else
- error("Your changes could not be committed, because the file has been changed")
- end
end
end
end
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 1497a0f883b..27c881c3430 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,36 +1,9 @@
require_relative "base_service"
module Files
- class DeleteService < BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
-
- deleted_successfully = delete_file_action.commit!(
- nil,
- params[:commit_message]
- )
-
- if deleted_successfully
- success
- else
- error("Your changes could not be committed, because the file has been changed")
- end
+ class DeleteService < Files::BaseService
+ def commit
+ repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 0724d3ae634..a20903c6f02 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,39 +1,9 @@
require_relative "base_service"
module Files
- class UpdateService < BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
- edit_file_action.commit!(
- params[:content],
- params[:commit_message],
- params[:encoding],
- params[:new_branch]
- )
-
- success
- rescue Gitlab::Satellite::CheckoutFailed => ex
- error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
- rescue Gitlab::Satellite::CommitFailed => ex
- error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
- rescue Gitlab::Satellite::PushFailed => ex
- error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
+ class UpdateService < Files::BaseService
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index bdf36af02fd..6135ae65007 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -88,18 +88,24 @@ class GitPushService
end
end
- # Create cross-reference notes for any other references. Omit any issues that were referenced in an
- # issue-closing phrase, or have already been mentioned from this commit (probably from this commit
- # being pushed to a different branch).
- refs = commit.references(project, user) - issues_to_close
- refs.reject! { |r| commit.has_mentioned?(r) }
+ if project.default_issues_tracker?
+ create_cross_reference_notes(commit, issues_to_close)
+ end
+ end
+ end
- if refs.present?
- author ||= commit_user(commit)
+ def create_cross_reference_notes(commit, issues_to_close)
+ # Create cross-reference notes for any other references. Omit any issues that were referenced in an
+ # issue-closing phrase, or have already been mentioned from this commit (probably from this commit
+ # being pushed to a different branch).
+ refs = commit.references(project, user) - issues_to_close
+ refs.reject! { |r| commit.has_mentioned?(r) }
- refs.each do |r|
- Note.create_cross_reference_note(r, commit, author)
- end
+ if refs.present?
+ author ||= commit_user(commit)
+
+ refs.each do |r|
+ SystemNoteService.cross_reference(r, commit, author)
end
end
end
@@ -127,7 +133,8 @@ class GitPushService
end
def is_default_branch?(ref)
- Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
+ Gitlab::Git.branch_ref?(ref) &&
+ (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
end
def commit_user(commit)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 8960235b093..f1ef5ca84fe 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -15,4 +15,23 @@ class IssuableBaseService < BaseService
SystemNoteService.change_label(
issuable, issuable.project, current_user, added_labels, removed_labels)
end
+
+ def create_title_change_note(issuable, old_title)
+ SystemNoteService.change_title(
+ issuable, issuable.project, current_user, old_title)
+ end
+
+ def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
+ SystemNoteService.change_branch(
+ 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/close_service.rb b/app/services/issues/close_service.rb
index 138465859ce..3d85f97b7e5 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,7 +1,7 @@
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
- if issue.close
+ if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit)
notification_service.close_issue(issue, current_user)
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 8f04a69287a..3220facaf7c 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -17,6 +17,7 @@ module Issues
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,
@@ -37,6 +38,10 @@ module Issues
notification_service.reassigned_issue(issue, current_user)
end
+ if issue.previous_changes.include?('title')
+ create_title_change_note(issue, issue.previous_changes['title'].first)
+ end
+
issue.notice_added_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..cdedf48b0c0 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -14,7 +14,7 @@ module MergeRequests
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
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 23af2656c37..f6570f52241 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -5,10 +5,11 @@ require_relative 'close_service'
module MergeRequests
class UpdateService < MergeRequests::BaseService
def execute(merge_request)
- # We dont allow change of source/target projects
+ # We don't allow change of source/target projects and source branch
# after merge request was created
params.except!(:source_project_id)
params.except!(:target_project_id)
+ params.except!(:source_branch)
state = params[:state_event]
@@ -26,6 +27,7 @@ module MergeRequests
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(
@@ -41,6 +43,12 @@ module MergeRequests
)
end
+ if merge_request.previous_changes.include?('target_branch')
+ create_branch_change_note(merge_request, 'target',
+ merge_request.previous_changes['target_branch'].first,
+ merge_request.target_branch)
+ end
+
if merge_request.previous_changes.include?('milestone_id')
create_milestone_note(merge_request)
end
@@ -50,6 +58,15 @@ module MergeRequests
notification_service.reassigned_merge_request(merge_request, current_user)
end
+ if merge_request.previous_changes.include?('title')
+ create_title_change_note(merge_request, merge_request.previous_changes['title'].first)
+ end
+
+ if merge_request.previous_changes.include?('target_branch') ||
+ merge_request.previous_changes.include?('source_branch')
+ merge_request.mark_as_unchecked
+ end
+
merge_request.notice_added_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/destroy_service.rb b/app/services/projects/destroy_service.rb
index 7e1d753b021..403f419ec50 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -1,28 +1,69 @@
module Projects
class DestroyService < BaseService
+ include Gitlab::ShellAdapter
+
+ class DestroyError < StandardError; end
+
+ DELETED_FLAG = '+deleted'
+
def execute
return false unless can?(current_user, :remove_project, project)
project.team.truncate
project.repository.expire_cache unless project.empty_repo?
- if project.destroy
- GitlabShellWorker.perform_async(
- :remove_repository,
- project.path_with_namespace
- )
+ repo_path = project.path_with_namespace
+ wiki_path = repo_path + '.wiki'
- GitlabShellWorker.perform_async(
- :remove_repository,
- project.path_with_namespace + ".wiki"
- )
+ Project.transaction do
+ project.destroy!
- project.satellite.destroy
+ unless remove_repository(repo_path)
+ raise_error('Failed to remove project repository. Please try again or contact administrator')
+ end
- log_info("Project \"#{project.name}\" was removed")
- system_hook_service.execute_hooks_for(project, :destroy)
- true
+ unless remove_repository(wiki_path)
+ raise_error('Failed to remove wiki repository. Please try again or contact administrator')
+ end
end
+
+ project.satellite.destroy
+ log_info("Project \"#{project.name}\" was removed")
+ system_hook_service.execute_hooks_for(project, :destroy)
+ true
+ end
+
+ private
+
+ def remove_repository(path)
+ # Skip repository removal. We use this flag when remove user or group
+ return true if params[:skip_repo] == true
+
+ # There is a possibility project does not have repository or wiki
+ return true unless gitlab_shell.exists?(path + '.git')
+
+ new_path = removal_path(path)
+
+ if gitlab_shell.mv_repository(path, new_path)
+ log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
+ GitlabShellWorker.perform_in(5.minutes, :remove_repository, new_path)
+ else
+ false
+ end
+ end
+
+ def raise_error(message)
+ raise DestroyError.new(message)
+ end
+
+ # Build a path for removing repositories
+ # We use `+` because its not allowed by GitLab so user can not create
+ # project with name cookies+119+deleted and capture someone stalled repository
+ #
+ # gitlab/cookies.git -> gitlab/cookies+119+deleted.git
+ #
+ def removal_path(path)
+ "#{path}+#{project.id}#{DELETED_FLAG}"
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index b91590a1a90..0004a399f47 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -38,13 +38,13 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: "#{group.name} (#{count})" }
+ { username: group.path, name: group.name, count: count }
end
end
def all_members
count = project.team.members.flatten.count
- [{ username: "all", name: "All Project and Group Members (#{count})" }]
+ [{ username: "all", name: "All Project and Group Members", count: count }]
end
end
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index 0bcc50c81a7..e904cb6c6fc 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -9,7 +9,7 @@ module Search
def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user)
- projects = projects.where(namespace_id: group.id) if group
+ projects = projects.in_namespace(group.id) if group
project_ids = projects.pluck(:id)
Gitlab::SearchResults.new(project_ids, params[:search])
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0614f8689a4..8253c1f780d 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -10,7 +10,7 @@ class SystemNoteService
# author - User performing the change
# new_commits - Array of Commits added since last push
# existing_commits - Array of Commits added in a previous push
- # oldrev - TODO (rspeicher): I have no idea what this actually does
+ # oldrev - Optional String SHA of a previous Commit
#
# See new_commit_summary and existing_commit_summary.
#
@@ -130,6 +130,44 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when the title of a Noteable is changed
+ #
+ # noteable - Noteable object that responds to `title`
+ # project - Project owning noteable
+ # author - User performing the change
+ # old_title - Previous String title
+ #
+ # Example Note text:
+ #
+ # "Title changed from **Old** to **New**"
+ #
+ # Returns the created Note object
+ def self.change_title(noteable, project, author, old_title)
+ return unless noteable.respond_to?(:title)
+
+ body = "Title changed from **#{old_title}** to **#{noteable.title}**"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when a branch in Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # branch_type - 'source' or 'target'
+ # old_branch - old branch name
+ # new_branch - new branch nmae
+ #
+ # Example Note text:
+ #
+ # "Target branch changed from `Old` to `New`"
+ #
+ # Returns the created Note object
+ def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch)
+ body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when a Mentionable references a Noteable
#
# noteable - Noteable object being referenced
@@ -138,11 +176,11 @@ class SystemNoteService
#
# Example Note text:
#
- # "Mentioned in #1"
+ # "mentioned in #1"
#
- # "Mentioned in !2"
+ # "mentioned in !2"
#
- # "Mentioned in 54f7727c"
+ # "mentioned in 54f7727c"
#
# See cross_reference_note_content.
#
@@ -150,7 +188,7 @@ class SystemNoteService
def self.cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner)
- gfm_reference = mentioner_gfm_ref(noteable, mentioner)
+ gfm_reference = mentioner.gfm_reference(noteable.project)
note_options = {
project: noteable.project,
@@ -174,19 +212,30 @@ class SystemNoteService
# Check if a cross-reference is disallowed
#
# This method prevents adding a "mentioned in !1" note on every single commit
- # in a merge request.
+ # in a merge request. Additionally, it prevents the creation of references to
+ # external issues (which would fail).
#
# noteable - Noteable object being referenced
# mentioner - Mentionable object
#
# Returns Boolean
def self.cross_reference_disallowed?(noteable, mentioner)
- return false unless MergeRequest === mentioner
- return false unless Commit === noteable
+ return true if noteable.is_a?(ExternalIssue)
+ return false unless mentioner.is_a?(MergeRequest)
+ return false unless noteable.is_a?(Commit)
mentioner.commits.include?(noteable)
end
+ # Check if a cross reference to a noteable from a mentioner already exists
+ #
+ # This method is used to prevent multiple notes being created for a mention
+ # when a issue is updated, for example.
+ #
+ # noteable - Noteable object being referenced
+ # mentioner - Mentionable object
+ #
+ # Returns Boolean
def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
@@ -198,7 +247,7 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id)
end
- gfm_reference = mentioner_gfm_ref(noteable, mentioner, true)
+ gfm_reference = mentioner.gfm_reference(noteable.project)
notes = notes.where(note: cross_reference_note_content(gfm_reference))
notes.count > 0
@@ -210,39 +259,6 @@ class SystemNoteService
Note.create(args.merge(system: true))
end
- # Prepend the mentioner's namespaced project path to the GFM reference for
- # cross-project references. For same-project references, return the
- # unmodified GFM reference.
- def self.mentioner_gfm_ref(noteable, mentioner, cross_reference = false)
- # FIXME (rspeicher): This was breaking things.
- # if mentioner.is_a?(Commit) && cross_reference
- # return mentioner.gfm_reference.sub('commit ', 'commit %')
- # end
-
- full_gfm_reference(mentioner.project, noteable.project, mentioner)
- end
-
- # Return the +mentioner+ GFM reference. If the mentioner and noteable
- # projects are not the same, add the mentioning project's path to the
- # returned value.
- def self.full_gfm_reference(mentioning_project, noteable_project, mentioner)
- if mentioning_project == noteable_project
- mentioner.gfm_reference
- else
- if mentioner.is_a?(Commit)
- mentioner.gfm_reference.sub(
- /(commit )/,
- "\\1#{mentioning_project.path_with_namespace}@"
- )
- else
- mentioner.gfm_reference.sub(
- /(issue |merge request )/,
- "\\1#{mentioning_project.path_with_namespace}"
- )
- end
- end
- end
-
def self.cross_reference_note_prefix
'mentioned in '
end
@@ -267,7 +283,7 @@ class SystemNoteService
#
# noteable - MergeRequest object
# existing_commits - Array of existing Commit objects
- # oldrev - Optional String SHA of ... TODO (rspeicher): I have no idea what this actually does.
+ # oldrev - Optional String SHA of a previous Commit
#
# Examples:
#
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 4ceae814805..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
@@ -30,59 +47,63 @@
.checkbox
= f.label :twitter_sharing_enabled do
= f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
- %strong Twitter enabled
+ 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
= f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
%span.help-block#home_help_block We will redirect non-logged in users to this page
.form-group
+ = f.label :after_sign_out_path, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block'
+ %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out
+ .form-group
= f.label :sign_in_text, class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :sign_in_text, class: 'form-control', rows: 4
.help-block Markdown enabled
- .form-group
- = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :max_attachment_size, class: 'form-control'
- .form-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-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/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 267c9a52921..17dffebd360 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -22,11 +22,11 @@
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
- = f.color_field :color, value: "#AA33EE", class: "form-control"
+ = f.color_field :color, value: "#eb9532", class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
- = f.color_field :font, value: "#224466", class: "form-control"
+ = f.color_field :font, value: "#FFFFFF", class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 367d25cd6a1..6405a69fad3 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -19,8 +19,7 @@
= link_to admin_deploy_key_path(deploy_key) do
%strong= deploy_key.title
%td
- %span
- (#{deploy_key.fingerprint})
+ %code.key-fingerprint= deploy_key.fingerprint
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml
deleted file mode 100644
index ea361ca4bdb..00000000000
--- a/app/views/admin/deploy_keys/show.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-- page_title @deploy_key.title, "Deploy Keys"
-.row
- .col-md-4
- .panel.panel-default
- .panel-heading
- Deploy Key
- %ul.well-list
- %li
- %span.light Title:
- %strong= @deploy_key.title
- %li
- %span.light Created on:
- %strong= @deploy_key.created_at.stamp("Aug 21, 2011")
-
- .panel.panel-default
- .panel-heading Projects (#{@deploy_key.deploy_keys_projects.count})
- - if @deploy_key.deploy_keys_projects.any?
- %ul.well-list
- - @deploy_key.projects.each do |project|
- %li
- %span
- %strong
- = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
- .pull-right
- = link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do
- %i.fa.fa-times.fa-inverse
-
- .col-md-8
- %p
- %span.light Fingerprint:
- %strong= @deploy_key.fingerprint
- %pre.well-pre
- = @deploy_key.key
- .pull-right
- = link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 9e7751830a4..8de2ba74a79 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -12,8 +12,7 @@
- if @group.new_record?
.form-group
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.alert.alert-info
= render 'shared/group_tips'
.form-actions
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index e00b23ad99f..5ce7cdf2f8d 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -11,7 +11,7 @@
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
= hidden_field_tag :sort, @sort
.form-group
- = text_field_tag :name, params[:name], class: "form-control input-mn-300"
+ = text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary"
.pull-right
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/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 4c2865ac3f2..5260eadf95b 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -92,8 +92,7 @@
= namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large'
.form-group
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
= f.submit 'Transfer', class: 'btn btn-primary'
.col-md-6
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 fe648470233..9c1bec7c84d 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
@@ -79,11 +87,12 @@
%i.fa.fa-envelope
= mail_to user.email, user.email, class: 'light'
&nbsp;
- = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-sm"
+ = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-xs"
- unless user == current_user
- if user.blocked?
- = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-sm success"
+ = 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-sm btn-remove"
- = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-sm btn-remove"
+ = 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.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 7fc85206109..2662b3569ec 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,228 +1,154 @@
- 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
- 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
- %p Deleting a user has the following effects:
- %ul
- %li All user content like authored issues, snippets, comments will be removed
- - rp = @user.personal_projects.count
- - unless rp.zero?
- %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- - if @user.solo_owned_groups.present?
- %li
- Next groups with all content will be removed:
- %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
- %br
- = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
+ 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"
+
+ .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/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index ba49013d834..213b5d65b3c 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -6,7 +6,6 @@
%li.pull-right
= link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
%i.fa.fa-rss
- Activity Feed
= render 'shared/event_filter'
%hr
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 5ecd53cff84..0a354373b9b 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,7 +2,7 @@
%h3.page-title
Group Membership
- if current_user.can_create_group?
- %span.pull-right
+ %span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
@@ -17,18 +17,17 @@
- @group_members.each do |group_member|
- group = group_member.group
%li
- .pull-right
+ .pull-right.hidden-xs
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
Settings
- - if can?(current_user, :destroy_group_member, group_member)
- = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do
- %i.fa.fa-sign-out
- Leave
+ = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
+ %i.fa.fa-sign-out
+ Leave
- = image_tag group_icon(group), class: "avatar s40 avatar-tile"
+ = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
= link_to group, class: 'group-name' do
%strong= group.name
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index dfdf0d68c8f..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'
+ = 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 a7e1b08a0a4..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'
+ = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 21e730bb7ff..d6f3e029a38 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -3,10 +3,10 @@
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
.row
.col-sm-6
- = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
+ = link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
- = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
+ = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 24f0bcb60d5..0d204ced7ea 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -56,6 +56,9 @@
Participants
%span.badge= @dashboard_milestone.participants.count
+ .pull-right
+ = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped"
+
.tab-content
.tab-pane.active#tab-issues
.row
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 812e22373a7..6ec741e4882 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,4 +1,9 @@
= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
+ - if devise_mapping.rememberable?
+ .remember-me.checkbox
+ %label{for: "remember_me"}
+ = check_box_tag :remember_me, '1', false, id: 'remember_me'
+ %span Remember me
= button_tag "#{server['label']} Sign in", class: "btn-save btn"
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
index a5fec2fabdb..98a61ab211b 100644
--- a/app/views/doorkeeper/applications/_form.html.haml
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -1,17 +1,22 @@
= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f|
- if application.errors.any?
- .alert.alert-danger{"data-alert" => ""}
- %p Whoops! Check your form for possible errors
- = content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
- = f.label :name, class: 'col-sm-2 control-label'
+ .alert.alert-danger
+ %ul
+ - application.errors.full_messages.each do |msg|
+ %li= msg
+
+ .form-group
+ = f.label :name, class: 'control-label'
+
.col-sm-10
- = f.text_field :name, class: 'form-control'
- = doorkeeper_errors_for application, :name
- = content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
- = f.label :redirect_uri, class: 'col-sm-2 control-label'
+ = f.text_field :name, class: 'form-control', required: true
+
+ .form-group
+ = f.label :redirect_uri, class: 'control-label'
+
.col-sm-10
- = f.text_area :redirect_uri, class: 'form-control'
- = doorkeeper_errors_for application, :redirect_uri
+ = f.text_area :redirect_uri, class: 'form-control', required: true
+
%span.help-block
Use one line per URI
- if Doorkeeper.configuration.native_redirect_uri
@@ -19,6 +24,7 @@
Use
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
+
.form-actions
- = f.submit 'Submit', class: "btn btn-primary wide"
- = link_to "Cancel", applications_profile_path, class: "btn btn-default"
+ = f.submit 'Submit', class: "btn btn-create"
+ = link_to "Cancel", applications_profile_path, class: "btn btn-cancel"
diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml
index 655845e4af5..fd32a468b45 100644
--- a/app/views/doorkeeper/applications/new.html.haml
+++ b/app/views/doorkeeper/applications/new.html.haml
@@ -1,2 +1,7 @@
-%h3.page-title New application
+- page_title "New Application"
+
+%h3.page-title New Application
+
+%hr
+
= render 'form', application: @application \ No newline at end of file
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 02b1dec753c..b2e5d11279b 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,14 +3,15 @@
.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
= 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/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index d2f0005142a..501412642db 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -4,7 +4,7 @@
%span You pushed to
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
%strong= event.ref_name
- at
+ %span at
%strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 1da702be384..34a7c00dc43 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -4,8 +4,8 @@
- if event.rm_ref?
%strong= event.ref_name
- else
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
- %strong= event.ref_name
+ %strong
+ = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
at
= link_to_project event.project
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index c05d45e0100..f3f0b778539 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -4,7 +4,7 @@
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search"
.form-group
= button_tag 'Search', class: "btn btn-primary wide"
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index b3963a9d901..82622a58ed2 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,7 +1,7 @@
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search"
.form-group
= button_tag 'Search', class: "btn btn-primary wide"
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 5ae2653fede..5e24df76a63 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -13,6 +13,3 @@
.public-projects
%ul.bordered-list
= render @trending_projects
-
- .center.append-bottom-20
- = link_to 'Show all projects', explore_projects_path, class: 'btn btn-primary'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 85179d4c4a2..aa13ed85b53 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -11,8 +11,7 @@
= render 'shared/group_form', f: f
.form-group
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
= image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
%p.light
- if @group.avatar?
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 56b1948a474..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
@@ -40,7 +40,8 @@
&nbsp;
- if current_user == user
= link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
- %i.fa.fa-minus.fa-inverse
+ = icon("sign-out")
+ Leave
- else
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 903ca877218..a70d1ff0697 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -14,7 +14,7 @@
.clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group, @group)
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 6a3da6adacf..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'
+ = 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 268f33d5761..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'
+ = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index 30093d2d05d..ba30e6e07c6 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -9,10 +9,10 @@
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
.row
.col-sm-6
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = link_to issues_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 6c41cd6b9e4..8f2decb851f 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -62,6 +62,9 @@
Participants
%span.badge= @group_milestone.participants.count
+ .pull-right
+ = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped"
+
.tab-content
.tab-pane.active#tab-issues
.row
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index edb882bea19..0665cdf387a 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -13,8 +13,7 @@
= render 'shared/choose_group_avatar_button', f: f
.form-group
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
= render 'shared/group_tips'
.form-actions
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 1678311141e..d31dae7d648 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -11,7 +11,7 @@
@#{@group.path}
- if @group.description.present?
.description
- = escaped_autolink(@group.description)
+ = markdown(@group.description, pipeline: :description)
%hr
= render 'shared/show_aside'
@@ -27,7 +27,6 @@
%li
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss
- Activity Feed
= render 'shared/event_filter'
%hr
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index ae072bacfb1..825acb0ae3e 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -1,4 +1,4 @@
-#modal-shortcuts.modal.hide{tabindex: -1}
+#modal-shortcuts.modal{tabindex: -1}
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/_empty_head_panel.html.haml
deleted file mode 100644
index 358caa3868b..00000000000
--- a/app/views/layouts/_empty_head_panel.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab
- .container
- %h4.center
- = image_tag 'logo-white.png', width: 32, height: 32
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index b1a57d9824e..dbc68c39bf1 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -15,7 +15,7 @@
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
- = yield(:meta_tags)
+ = yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
deleted file mode 100644
index ef685a0434e..00000000000
--- a/app/views/layouts/_head_panel.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
- = brand_header_logo
- %h3 GitLab
- %h1.title
- = title
-
- %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}}
- %span.sr-only Toggle navigation
- = icon('bars')
-
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- = render 'layouts/search'
- %li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('search')
- %li
- = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('question-circle')
- %li
- = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('globe')
- %li
- = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('clipboard')
- - if current_user.is_admin?
- %li
- = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('cogs')
- - if current_user.can_create_project?
- %li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('plus')
- %li
- = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('user')
- %li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('sign-out')
- %li.hidden-xs
- = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'bottom'} do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 5c55bdb5465..f17f6fdd91c 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -7,11 +7,14 @@
= render 'layouts/nav/dashboard'
.collapse-nav
= render partial: 'layouts/collapse_button'
+ - if current_user
+ = link_to current_user, class: 'sidebar-user' do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32'
+ .username
+ = current_user.username
.content-wrapper
.container-fluid
.content
= render "layouts/flash"
.clearfix
= yield
-
-= yield :embedded_scripts
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
deleted file mode 100644
index 8a297566d6c..00000000000
--- a/app/views/layouts/_public_head_panel.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_logo
- = link_to explore_root_path, class: "home" do
- = brand_header_logo
- %h3 GitLab
- %h1.title= title
-
- %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
- %span.sr-only Toggle navigation
- %i.fa.fa-bars
-
- - unless current_controller?('sessions')
- .pull-right.hidden-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10'
-
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.visible-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 04f79846858..e2d2dec7ab8 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,6 +1,6 @@
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input"
+ = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control"
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index a97feeb1ecd..678ed3c2c1f 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,10 +1,15 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{app_theme}", :'data-page' => body_data_page}
+ %body{class: "#{user_application_theme}", 'data-page' => body_data_page}
+ -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
+ = yield :scripts_body_top
+
- if current_user
- = render "layouts/head_panel", title: header_title
+ = render "layouts/header/default", title: header_title
- else
- = render "layouts/public_head_panel", title: header_title
+ = render "layouts/header/public", title: header_title
= render 'layouts/page', sidebar: sidebar
+
+ = yield :scripts_body
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 5a59c9fd59a..1987bf1592a 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,8 +1,8 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_mars.login-page.application
- = render "layouts/empty_head_panel"
+ %body.ui_charcoal.login-page.application
+ = render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index aa0f3f0a819..2af265a2296 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,8 +1,8 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{app_theme} application"}
- = render "layouts/head_panel", title: "" if current_user
+ %body{class: "#{user_application_theme} application"}
+ = render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
.error-page
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
new file mode 100644
index 00000000000..b3cd7b0e37b
--- /dev/null
+++ b/app/views/layouts/header/_default.html.haml
@@ -0,0 +1,46 @@
+%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+ .container
+ .header-logo
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = brand_header_logo
+ .gitlab-text-container
+ %h3 GitLab
+ .header-content
+ %button.navbar-toggle{type: 'button'}
+ %span.sr-only Toggle navigation
+ = icon('bars')
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav.pull-right
+ %li.hidden-sm.hidden-xs
+ = render 'layouts/search'
+ %li.visible-sm.visible-xs
+ = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('search')
+ %li.hidden-xs
+ = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('question-circle fw')
+ %li
+ = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('globe fw')
+ %li
+ = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('clipboard fw')
+ - if current_user.is_admin?
+ %li
+ = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('wrench fw')
+ - if current_user.can_create_project?
+ %li.hidden-xs
+ = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('plus fw')
+ %li
+ = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('cog fw')
+ %li
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('sign-out')
+
+ %h1.title= title
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml
new file mode 100644
index 00000000000..2ed4edb1136
--- /dev/null
+++ b/app/views/layouts/header/_empty.html.haml
@@ -0,0 +1,4 @@
+%header.navbar.navbar-fixed-top.navbar-empty
+ .container
+ .center-logo
+ = brand_header_logo
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
new file mode 100644
index 00000000000..15c2e292be3
--- /dev/null
+++ b/app/views/layouts/header/_public.html.haml
@@ -0,0 +1,15 @@
+%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+ .container
+ .header-logo
+ = link_to explore_root_path, class: "home" do
+ = brand_header_logo
+ .gitlab-text-container
+ %h3 GitLab
+ .header-content
+ - unless current_controller?('sessions')
+ .pull-right
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm'
+
+ %h1.title= title
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index d46dba4a240..687c1fc3dd2 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
- = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
- = link_to root_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+ = nav_link(path: ['dashboard#show', 'root#show'], html_options: {class: 'home'}) do
+ = link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Projects
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 62f0579d48b..9f1654b25b4 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -44,7 +44,7 @@
= link_to edit_group_path(@group), title: 'Group', data: {placement: 'right'} do
= icon('pencil-square-o')
%span
- Group
+ Group Settings
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
= icon('folder')
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index ac37fd4c1c1..914e1b83d1f 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -9,7 +9,7 @@
= icon('gear fw')
%span
Account
- = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
+ = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new', 'applications#create']) do
= link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do
= icon('cloud fw')
%span
@@ -38,11 +38,12 @@
%span
SSH Keys
%span.count= current_user.keys.count
- = nav_link(path: 'profiles#design') do
- = link_to design_profile_path, title: 'Design', data: {placement: 'right'} do
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
+ -# TODO (rspeicher): Better icon?
= icon('image fw')
%span
- Design
+ Preferences
= nav_link(path: 'profiles#history') do
= link_to history_profile_path, title: 'History', data: {placement: 'right'} do
= icon('history fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 172f5197b24..cbcf560d0af 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -56,6 +56,13 @@
Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count
+ - if project_nav_tab? :settings
+ = nav_link(controller: [:project_members, :teams]) do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
+ = icon('users fw')
+ %span
+ Members
+
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 21260302a09..633c6ae6bfb 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -10,32 +10,27 @@
%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do
- = icon('pencil-square-o')
+ = icon('pencil-square-o fw')
%span
- Project
- = nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
- = icon('users')
- %span
- Members
+ Project Settings
= nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
- = icon('key')
+ = icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
- = icon('link')
+ = icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
- = icon('cogs')
+ = icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
- = icon('lock')
+ = icon('lock fw')
%span
Protected branches
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 00c7cedce40..ee1b57278b6 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -27,7 +27,7 @@
}
.file-stats .deleted-file {
color: #B00;
- }}
+ }
%body
%div.content
= yield
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 9799b4cc4d7..3193206fe12 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,5 @@
-- page_title "Profile"
-- header_title "Profile", profile_path
+- page_title "Settings"
+- header_title "Settings", profile_path
- sidebar "profile"
= render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 4aeb9d397d2..44afa33dfe5 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -2,7 +2,13 @@
- header_title project_title(@project)
- sidebar "project" unless sidebar
-- content_for :embedded_scripts do
+- content_for :scripts_body_top do
+ - if current_user
+ :javascript
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
+ window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}";
+
+- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
= render template: "layouts/application"
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 0cc62935498..fc64c98038b 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,5 +1,5 @@
New Issue was created.
Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
-Author: <%= @issue.author_name %>
-Asignee: <%= @issue.assignee_name %>
+Author: <%= @issue.author_name %>
+Assignee: <%= @issue.assignee_name %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index f08039ad045..bdcca6e4ab7 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -3,6 +3,6 @@ New Merge Request #<%= @merge_request.iid %>
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
-Author: <%= @merge_request.author_name %>
-Asignee: <%= @merge_request.assignee_name %>
+Author: <%= @merge_request.author_name %>
+Assignee: <%= @merge_request.assignee_name %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index a374a662333..12f83aae04b 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -35,7 +35,7 @@
= diff.new_path
- elsif diff.new_file
%span.new-file
- &plus;
+ &#43;
= diff.new_path
- else
= diff.new_path
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 06bad7dd84a..378dfa2dce0 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,13 +1,18 @@
- page_title "Account"
+%h3.page-title
+ = page_title
+%p.light
+ Change your username and basic account settings.
+%hr
- if current_user.ldap_user?
.alert.alert-info
Some options are unavailable for LDAP accounts
.account-page
- %fieldset.update-token
- %legend
+ .panel.panel-default.update-token
+ .panel-heading
Reset Private token
- %div
+ .panel-body
= form_for @user, url: reset_private_token_profile_path, method: :put do |f|
.data
%p
@@ -21,21 +26,23 @@
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
- = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
- = f.submit 'Generate', class: "btn success btn-build-token"
+ = f.submit 'Generate', class: "btn btn-default btn-build-token"
- unless current_user.ldap_user?
- %fieldset
- - if current_user.otp_required_for_login
- %legend.text-success
- = icon('check')
- Two-factor Authentication enabled
- %div
+ .panel.panel-default
+ .panel-heading
+ Two-factor Authentication
+ .panel-body
+ - 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?' }
+ %p.text-success
+ %strong
+ Two-factor Authentication is enabled
%p
If you lose your recovery codes you can
%strong
@@ -43,9 +50,7 @@
= link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
invalidating all previous codes.
- - else
- %legend Two-factor Authentication
- %div
+ - else
%p
Increase your account's security by enabling two-factor authentication (2FA).
%p
@@ -55,51 +60,57 @@
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if show_profile_social_tab?
- %fieldset
- %legend Connected Accounts
- .oauth-buttons.append-bottom-10
- %p Click on icon to activate signin with one of the following services
- - enabled_social_providers.each do |provider|
- .btn-group
- = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider),
- method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}"
- - if oauth_active?(provider)
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
- = icon('close')
+ .panel.panel-default
+ .panel-heading
+ Connected Accounts
+ .panel-body
+ .oauth-buttons.append-bottom-10
+ %p Click on icon to activate signin with one of the following services
+ - enabled_social_providers.each do |provider|
+ .btn-group
+ = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider),
+ method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}"
+ - if oauth_active?(provider)
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
+ = icon('close')
- if show_profile_username_tab?
- %fieldset.update-username
- %legend
+ .panel.panel-warning.update-username
+ .panel-heading
Change Username
- = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
- %p
- Changing your username will change path to all personal projects!
- %div
- = f.text_field :username, required: true, class: 'form-control'
- &nbsp;
- .loading-gif.hide
+ .panel-body
+ = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
%p
- = icon('spinner spin')
- Saving new username
- %p.light
- = user_url(@user)
- %div
- = f.submit 'Save username', class: "btn btn-warning"
+ Changing your username will change path to all personal projects!
+ %div
+ = f.text_field :username, required: true, class: 'form-control'
+ &nbsp;
+ .loading-gif.hide
+ %p
+ = icon('spinner spin')
+ Saving new username
+ %p.light
+ = user_url(@user)
+ %div
+ = f.submit 'Save username', class: "btn btn-warning"
- if show_profile_remove_tab?
- %fieldset.remove-account
- %legend
+ .panel.panel-danger.remove-account
+ .panel-heading
Remove account
- %div
- %p Deleting an account has the following effects:
- %ul
- %li All user content like authored issues, snippets, comments will be removed
- - rp = current_user.personal_projects.count
- - unless rp.zero?
- %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- - if current_user.solo_owned_groups.present?
- %li
- The following groups will be abandoned. You should transfer or remove them:
- %strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
- = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
-
+ .panel-body
+ - if @user.can_be_removed?
+ %p Deleting an account has the following effects:
+ %ul
+ %li All user content like authored issues, snippets, comments will be removed
+ - rp = current_user.personal_projects.count
+ - unless rp.zero?
+ %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
+ = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+ - else
+ - if @user.solo_owned_groups.present?
+ %p
+ Your account 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 your account.
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index c4f6f59624b..2c4f0804f0b 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -1,34 +1,44 @@
- page_title "Applications"
%h3.page-title
- Application Settings
+ = page_title
%p.light
- OAuth2 protocol settings below.
+ - if user_oauth_applications?
+ Manage applications that can use GitLab as an OAuth provider,
+ and applications that you've authorized to use your account.
+ - else
+ Manage applications that you've authorized to use your account.
+%hr
-%fieldset.oauth-applications
- %legend Your applications
- %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
- - if @applications.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %th Clients
- %th
- %th
- %tbody
- - @applications.each do |application|
- %tr{:id => "application_#{application.id}"}
- %td= link_to application.name, oauth_application_path(application)
- %td
- - application.redirect_uri.split.each do |uri|
- %div= uri
- %td= application.access_tokens.count
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
- %td= render 'doorkeeper/applications/delete_form', application: application
+- if user_oauth_applications?
+ .oauth-applications
+ %h3
+ Your applications
+ .pull-right
+ = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+ - if @applications.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
-%fieldset.oauth-authorized-applications.prepend-top-20
- %legend Authorized applications
+.oauth-authorized-applications.prepend-top-20
+ - if user_oauth_applications?
+ %h3
+ Authorized applications
- if @authorized_tokens.any?
%table.table.table-striped
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
deleted file mode 100644
index af284f60409..00000000000
--- a/app/views/profiles/design.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-- page_title "Design"
-%h3.page-title
- Design Settings
-%p.light
- Appearance settings will be saved to your profile and made available across all devices.
-%hr
-
-= form_for @user, url: profile_path, remote: true, method: :put do |f|
- %fieldset.application-theme
- %legend
- Application theme
- .themes_opts
- = label_tag do
- .prev.default
- = f.radio_button :theme_id, 1
- Graphite
-
- = label_tag do
- .prev.classic
- = f.radio_button :theme_id, 2
- Charcoal
-
- = label_tag do
- .prev.modern
- = f.radio_button :theme_id, 3
- Green
-
- = label_tag do
- .prev.gray
- = f.radio_button :theme_id, 4
- Gray
-
- = label_tag do
- .prev.violet
- = f.radio_button :theme_id, 5
- Violet
-
- = label_tag do
- .prev.blue
- = f.radio_button :theme_id, 6
- Blue
- %br
- .clearfix
-
- %fieldset.code-preview-theme
- %legend
- Code preview theme
- .code_highlight_opts
- - color_schemes.each do |color_scheme_id, color_scheme|
- = label_tag do
- .prev
- = image_tag "#{color_scheme}-scheme-preview.png"
- = f.radio_button :color_scheme_id, color_scheme_id
- = color_scheme.gsub(/[-_]+/, ' ').humanize
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 2c0d0e10a4c..66812872c41 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,23 +1,27 @@
- page_title "Emails"
%h3.page-title
- Email Settings
+ = page_title
%p.light
- Your
- %b Primary Email
- will be used for avatar detection and web based operations, such as edits and merges.
-%p.light
- Your
- %b Notification Email
- will be used for account notifications.
-%p.light
- Your
- %b Public Email
- will be displayed on your public profile.
-%p.light
- All email addresses will be used to identify your commits.
-
+ Control emails linked to your account
%hr
+
+%ul
+ %li
+ Your
+ %b Primary Email
+ will be used for avatar detection and web based operations, such as edits and merges.
+ %li
+ Your
+ %b Notification Email
+ will be used for account notifications.
+ %li
+ Your
+ %b Public Email
+ will be displayed on your public profile.
+ %li
+ All email addresses will be used to identify your commits.
+
.panel.panel-default
.panel-heading
Emails (#{@emails.count + 1})
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index fe5770f45c3..9bbccbc45ea 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -3,8 +3,7 @@
= link_to path_to_key(key, is_admin) do
%strong= key.title
%td
- %span
- (#{key.fingerprint})
+ %code.key-fingerprint= key.fingerprint
%td
%span.cgray
added #{time_ago_with_tooltip(key.created_at)}
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 8bac22a2e1a..e0ae4d9720f 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -15,7 +15,7 @@
.col-md-8
%p
%span.light Fingerprint:
- %strong= @key.fingerprint
+ %code.key-fingerprint= @key.fingerprint
%pre.well-pre
= @key.key
.pull-right
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e3af0d4e189..06655f7ba3a 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,6 +1,6 @@
- page_title "SSH Keys"
%h3.page-title
- SSH Keys Settings
+ = page_title
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a74d97dac3b..9480a19f5b2 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,6 +1,6 @@
- page_title "Notifications"
%h3.page-title
- Notifications Settings
+ = page_title
%p.light
These are your global notification settings.
%hr
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 21dabbdfe2c..399ae98adf9 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,6 @@
- page_title "Password"
-%h3.page-title Password Settings
+%h3.page-title
+ = page_title
%p.light
- if @user.password_automatically_set?
Set your password.
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
new file mode 100644
index 00000000000..aa99280fde6
--- /dev/null
+++ b/app/views/profiles/preferences/show.html.haml
@@ -0,0 +1,42 @@
+- page_title 'Preferences'
+%h3.page-title
+ = page_title
+%p.light
+ These settings allow you to customize the appearance and behavior of the site.
+ They are saved with your account and will persist to any device you use to
+ access the site.
+%hr
+
+= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
+ .panel.panel-default.application-theme
+ .panel-heading
+ Application theme
+ .panel-body
+ - Gitlab::Themes.each do |theme|
+ = label_tag do
+ .preview{class: theme.css_class}
+ = f.radio_button :theme_id, theme.id
+ = theme.name
+
+ .panel.panel-default.syntax-theme
+ .panel-heading
+ Syntax highlighting theme
+ .panel-body
+ - color_schemes.each do |color_scheme_id, color_scheme|
+ = label_tag do
+ .preview= image_tag "#{color_scheme}-scheme-preview.png"
+ = f.radio_button :color_scheme_id, color_scheme_id
+ = color_scheme.tr('-_', ' ').titleize
+
+ .panel.panel-default
+ .panel-heading
+ Behavior
+ .panel-body
+ .form-group
+ = f.label :dashboard, class: 'control-label' do
+ Default Dashboard
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
+ .col-sm-10
+ = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ .panel-footer
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
new file mode 100644
index 00000000000..6c4b0ce757d
--- /dev/null
+++ b/app/views/profiles/preferences/update.js.erb
@@ -0,0 +1,9 @@
+// Remove body class for any previous theme, re-add current one
+$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
+$('body').addClass('<%= user_application_theme %>')
+
+// Re-enable the "Save" button
+$('input[type=submit]').enable()
+
+// Show the notice flash message
+new Flash('<%= flash.discard(:notice) %>', 'notice')
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 29c30905117..37a3952635e 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,6 +1,6 @@
-- page_title "Settings"
+- page_title "Profile"
%h3.page-title
- Profile Settings
+ = page_title
%p.light
This information will appear on your profile.
- if current_user.ldap_user?
@@ -37,8 +37,11 @@
= f.text_field :email, class: "form-control", required: true
- if @user.unconfirmed_email.present?
%span.help-block
- Please click the link in the confirmation email before continuing, it was sent to
- %strong #{@user.unconfirmed_email}
+ Please click the link in the confirmation email before continuing. It was sent to
+ = succeed "." do
+ %strong #{@user.unconfirmed_email}
+ %p
+ = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
- else
%span.help-block We also use email for avatar detection if no avatar is uploaded.
@@ -106,6 +109,5 @@
.row
.col-md-7
.form-group
- .col-sm-2 &nbsp;
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index fe03a259a12..74268c9bde2 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -2,22 +2,39 @@
%h2.page-title Two-Factor Authentication (2FA)
%p
- Download the Google Authenticator application from App Store for iOS or
- Google Play for Android and scan this code.
+ Download the Google Authenticator application from App Store for iOS or Google
+ Play for Android and scan this code.
+
+ More information is available in the #{link_to('documentation', help_page_path('workflow', 'two_factor_authentication'))}.
%hr
-= form_tag profile_two_factor_auth_path, method: :post, class: 'form-horizontal' do |f|
+= form_tag profile_two_factor_auth_path, method: :post, class: 'form-horizontal two-factor-new' do |f|
- if @error
.alert.alert-danger
= @error
.form-group
- .col-sm-2
- .col-sm-10
+ .col-lg-2.col-lg-offset-2
= raw @qr_code
+ .col-lg-7.col-lg-offset-1.manual-instructions
+ %h3 Can't scan the code?
+
+ %p
+ To add the entry manually, provide the following details to the
+ application on your phone.
+
+ %dl
+ %dt Account
+ %dd= current_user.email
+ %dl
+ %dt Key
+ %dd= current_user.otp_secret.scan(/.{4}/).join(' ')
+ %dl
+ %dt Time based
+ %dd Yes
.form-group
= label_tag :pin_code, nil, class: "control-label"
- .col-sm-10
+ .col-lg-10
= text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
.form-actions
= submit_tag 'Submit', class: 'btn btn-success'
diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb
deleted file mode 100644
index db37619136d..00000000000
--- a/app/views/profiles/update.js.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-// Remove body class for any previous theme, re-add current one
-$('body').removeClass('<%= Gitlab::Theme.body_classes %>')
-$('body').addClass('<%= app_theme %> <%= theme_type %>')
diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
index e90c7b26dd2..72aea8814f5 100644
--- a/app/views/projects/_aside.html.haml
+++ b/app/views/projects/_aside.html.haml
@@ -1,96 +1,106 @@
.clearfix
- unless @project.empty_repo?
- .well
- %h4.visibility-level-label
+ .panel.panel-default
+ .panel-heading
= visibility_level_icon(@project.visibility_level)
= "#{visibility_level_label(@project.visibility_level).capitalize} project"
- - 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
+ .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
+ .actions
+ - if can? current_user, :create_issue, @project
+ = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do
+ 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 can? current_user, :create_merge_request, @project
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
+ New Merge Request
- - if forked_from_project = @project.forked_from_project
- .well
- %h4
- = icon("code-fork fw")
- Forked from
- .pull-right
- = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project)
+ - 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)
- - if version = @repository.version
- .well
- %h4
- = icon("clock-o fw")
- Version
- .pull-right
- = link_to version_url(@project) do
- = @repository.blob_by_oid(version.id).data
- - @project.ci_services.each do |ci_service|
- - if ci_service.active? && ci_service.respond_to?(:builds_path)
- .well
- %h4
- = 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'
+ - @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?
- .well
- %h4
- = icon("archive fw")
+ .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')
- %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
+ .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 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/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
index 07d4d602769..745163e79a7 100644
--- a/app/views/projects/_bitbucket_import_modal.html.haml
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -1,4 +1,4 @@
-%div#bitbucket_import_modal.modal.hide
+%div#bitbucket_import_modal.modal
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml
index e88a0f7d689..de58b27df23 100644
--- a/app/views/projects/_github_import_modal.html.haml
+++ b/app/views/projects/_github_import_modal.html.haml
@@ -1,4 +1,4 @@
-%div#github_import_modal.modal.hide
+%div#github_import_modal.modal
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml
index 52212b6ae02..ae6c25f9371 100644
--- a/app/views/projects/_gitlab_import_modal.html.haml
+++ b/app/views/projects/_gitlab_import_modal.html.haml
@@ -1,4 +1,4 @@
-%div#gitlab_import_modal.modal.hide
+%div#gitlab_import_modal.modal
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index f9cdda4a3ba..076afb11a9d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -5,7 +5,7 @@
.project-home-row.project-home-row-top
.project-home-desc
- if @project.description.present?
- = escaped_autolink(@project.description)
+ = markdown(@project.description, pipeline: :description)
- if can?(current_user, :admin_project, @project)
&ndash;
= link_to 'Edit', edit_namespace_project_path
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
deleted file mode 100644
index 2292aaaa214..00000000000
--- a/app/views/projects/_issuable_form.html.haml
+++ /dev/null
@@ -1,96 +0,0 @@
-- if issuable.errors.any?
- .row
- .col-sm-10.col-sm-offset-2
- .alert.alert-danger
- - issuable.errors.full_messages.each do |msg|
- %span= msg
- %br
-.form-group
- = f.label :title, class: 'control-label' do
- %strong= 'Title *'
- .col-sm-10
- = f.text_field :title, maxlength: 255, autofocus: true,
- class: 'form-control pad js-gfm-input', required: true
-
- - if issuable.is_a?(MergeRequest)
- %p.help-block
- - if issuable.work_in_progress?
- This merge request is marked a <strong>Work In Progress</strong>.
- When it's ready, remove the <code>WIP</code> prefix from the title to allow it to be accepted.
- - else
- To prevent this merge request from being accepted before it's ready,
- mark it a <strong>Work In Progress</strong> by starting the title with <code>[WIP]</code> or <code>WIP:</code>.
-.form-group.issuable-description
- = f.label :description, 'Description', class: 'control-label'
- .col-sm-10
-
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .col-sm-12.hint
- .pull-left
- Parsed with
- #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
- .pull-right
- Attach files by dragging &amp; dropping
- or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
-
- .clearfix
- .error-alert
-%hr
-.form-group
- .issue-assignee
- = f.label :assignee_id, class: 'control-label' do
- %i.fa.fa-user
- Assign to
- .col-sm-10
- = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select a user', class: 'custom-form-control', null_user: true,
- selected: issuable.assignee_id, 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 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
-
-.form-actions
- - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
- %p
- Please review the
- %strong #{link_to 'guidelines for contribution', guide_url}
- to this repository.
- - if issuable.new_record?
- = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- - else
- = f.submit 'Save changes', class: 'btn btn-save'
- - if issuable.new_record?
- - cancel_project = issuable.source_project
- - else
- - cancel_project = issuable.project
- = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index b869fd6e12a..b7bca6dae09 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,13 +1,24 @@
-%ul.nav.nav-tabs
- %li.active
- = link_to '#md-write-holder', class: 'js-md-write-button' do
- Write
- %li
- = link_to '#md-preview-holder', class: 'js-md-preview-button',
- data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do
- Preview
-%div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
+.md-area
+ .md-header.clearfix
+ %ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do
+ Preview
+
+ - if defined?(referenced_users) && referenced_users
+ %span.referenced-users.pull-left.hide
+ = icon('exclamation-triangle')
+ You are about to add
+ %strong
+ %span.js-referenced-users-count 0
+ people
+ to the discussion. Proceed with caution.
+
+ %div
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/_section.html.haml b/app/views/projects/_section.html.haml
index f4f876f3809..d7b06197f67 100644
--- a/app/views/projects/_section.html.haml
+++ b/app/views/projects/_section.html.haml
@@ -18,7 +18,6 @@
%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
- Activity Feed
= render 'shared/event_filter'
%hr
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index cf1c55ecca6..a4e41eeb363 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: 'Leave a comment'
+ = f.text_area attr, class: classes, placeholder: random_markdown_tip
= link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%i.fa.fa-expand
Edit in fullscreen
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 462f5b7afb0..8019c7f4569 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -32,5 +32,5 @@
%code
:erb
<% lines.each do |line| %>
- <%= highlight(@blob.name, line, true).html_safe %>
+ <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %>
<% end %>
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 96f188e4aa7..9c3e1703c89 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -12,8 +12,8 @@
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
- .pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code
%pre.js-edit-mode-pane#editor
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index 09559a4967b..cae5ff01099 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -1,4 +1,4 @@
-#modal-remove-blob.modal.hide
+#modal-remove-blob.modal
.modal-dialog
.modal-content
.modal-header
@@ -9,14 +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-2
- .col-sm-10
+ .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 9b1d03b820e..7c2a4fece94 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,16 +1,17 @@
- 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'
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ - unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
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/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index fc91f71e8d2..60b112e67d4 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,4 +1,4 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
= render "commit_box"
= render "projects/diffs/diffs", diffs: @diffs, project: @project
-= render "projects/notes/notes_with_form"
+= render "projects/notes/notes_with_form", view: params[:view]
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/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/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index c577dfa8d55..8d66bae8cdf 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -2,24 +2,20 @@
.pull-right
- if @available_keys.include?(deploy_key)
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- %i.fa.fa-plus
+ = icon('plus')
Enable
- else
- if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned?
= link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right"
- else
= link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- %i.fa.fa-power-off
+ = icon('power-off')
Disable
- - if project = project_for_deploy_key(deploy_key)
- = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do
- %i.fa.fa-key
- %strong= deploy_key.title
- - else
- %i.fa.fa-key
- %strong= deploy_key.title
-
+ = icon('key')
+ %strong= deploy_key.title
+ %br
+ %code.key-fingerprint= deploy_key.fingerprint
%p.light.prepend-top-10
- if deploy_key.public?
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
deleted file mode 100644
index 7d44652af72..00000000000
--- a/app/views/projects/deploy_keys/show.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- page_title @key.title, "Deploy Keys"
-%h3.page-title
- Deploy key:
- = @key.title
- %small
- created on
- = @key.created_at.stamp("Aug 21, 2011")
-.back-link
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do
- &larr; To keys list
-%hr
-%pre= @key.key
-.pull-right
- = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index d4b019780f5..99ee23a1ddc 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -10,8 +10,9 @@
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
- elsif diff_file.diff.submodule?
- - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
- = submodule_link(submodule_item, @commit.id, project.repository)
+ %span
+ - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
+ = submodule_link(submodule_item, @commit.id, project.repository)
- else
%span
- if diff_file.renamed_file
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 75f3a80f0d7..37fd1b1ec8a 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -18,6 +18,8 @@
- 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, :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
- if type_right == 'new'
@@ -29,12 +31,14 @@
%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, :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
- if @reply_allowed
- comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right)
- if comments_left.present? || comments_right.present?
- = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments_left, notes2: comments_right
+ = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
- if diff_file.diff.diff.blank? && diff_file.mode_changed?
.file-mode-changed
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 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/edit.html.haml b/app/views/projects/edit.html.haml
index c09d794ef7f..3fecd25c324 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -6,7 +6,7 @@
Project settings
%hr
.panel-body
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
%fieldset
.form-group.project_name_holder
@@ -41,39 +41,46 @@
%legend
Features:
.form-group
- = f.label :issues_enabled, "Issues", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :issues_enabled
- %span.descr Lightweight issue tracking system for this project
+ = f.label :issues_enabled do
+ = f.check_box :issues_enabled
+ %strong Issues
+ %br
+ %span.descr Lightweight issue tracking system for this project
.form-group
- = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :merge_requests_enabled
- %span.descr Submit changes to be merged upstream.
+ = f.label :merge_requests_enabled do
+ = f.check_box :merge_requests_enabled
+ %strong Merge Requests
+ %br
+ %span.descr Submit changes to be merged upstream.
.form-group
- = f.label :wiki_enabled, "Wiki", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :wiki_enabled
- %span.descr Pages for project documentation
+ = f.label :wiki_enabled do
+ = f.check_box :wiki_enabled
+ %strong Wiki
+ %br
+ %span.descr Pages for project documentation
.form-group
- = f.label :snippets_enabled, "Snippets", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :snippets_enabled
- %span.descr Share code pastes with others out of git repository
+ = f.label :snippets_enabled do
+ = f.check_box :snippets_enabled
+ %strong Snippets
+ %br
+ %span.descr Share code pastes with others out of git repository
%fieldset.features
%legend
Project avatar:
.form-group
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
- if @project.avatar?
= project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
%p.light
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 2016f5c709c..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,12 +23,11 @@
= 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
%label Labels
.issue-show-labels
- @issue.labels.each do |label|
- = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
- = render_colored_label(label)
+ = link_to_label(label)
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 7d7217eb2a8..f39bb7d2574 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -3,12 +3,10 @@
%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){
$('#issue_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index ef36d1f9547..1b45bb1af0c 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,45 +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.str-truncated
- = link_to_gfm issue.title, issue_path(issue), class: "row_title"
- .issue-labels
- - issue.labels.each do |label|
- = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do
- = render_colored_label(label)
- .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 709ea1f7897..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
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
+ - 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'
+ = 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 261d52dedc1..534c545329b 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,7 +1,7 @@
-= 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-10.col-sm-offset-2
+ .col-sm-offset-2.col-sm-10
.alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 82829452862..7fa1ee53f76 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,8 +1,8 @@
%li{id: dom_id(label)}
- = render_colored_label(label)
+ = link_to_label(label)
.pull-right
%strong.append-right-20
- = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
+ = link_to_label(label) do
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 7d19415a7f4..d44fe486212 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -13,4 +13,7 @@
= paginate @labels, theme: 'gitlab'
- else
.light-well
- .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
+ - if can? current_user, :admin_label, @project
+ .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
+ - else
+ .nothing-here-block No labels created
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 9a2aa9c3de0..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,12 +20,11 @@
= 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
%label Labels
.merge-request-show-labels
- @merge_request.labels.each do |label|
- = link_to namespace_project_merge_requests_path(@project.namespace, @project, label_name: label.name) do
- = render_colored_label(label)
+ = link_to_label(label)
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 1c7160bce5f..9cf389dbe38 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,12 +1,9 @@
-= 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();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 5d5a23b5409..0bcd543fee7 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,11 +1,10 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
- %span.str-truncated
+ %span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
.merge-request-labels
- merge_request.labels.each do |label|
- = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do
- = render_colored_label(label)
+ = link_to_label(label, project: merge_request.project)
.pull-right.light
- if merge_request.merged?
%span
@@ -13,7 +12,7 @@
MERGED
- elsif merge_request.closed?
%span
- %i.fa.fa-close
+ %i.fa.fa-ban
CLOSED
- else
%span.hidden-xs.hidden-sm
@@ -36,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?
@@ -49,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 24a9563dd4d..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
@@ -19,30 +19,31 @@
.mr-compare.merge-request
%ul.nav.nav-tabs.merge-request-tabs
- %li.commits-tab{data: {action: 'commits', toggle: 'tab'}}
- = link_to url_for(params) do
- %i.fa.fa-history
+ %li.commits-tab
+ = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+ = icon('history')
Commits
%span.badge= @commits.size
- %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}}
- = link_to url_for(params) do
- %i.fa.fa-list-alt
+ %li.diffs-tab
+ = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+ = icon('list-alt')
Changes
%span.badge= @diffs.size
- .commits.tab-content
- = render "projects/commits/commits", project: @project
- .diffs.tab-content
- - if @diffs.present?
- = render "projects/diffs/diffs", diffs: @diffs, project: @project
- - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .alert.alert-danger
- %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
- %p To preserve performance the line changes are not shown.
- - else
- .alert.alert-danger
- %h4 This comparison includes a huge diff.
- %p To preserve performance the line changes are not shown.
+ .tab-content
+ #commits.commits.tab-pane
+ = render "projects/commits/commits", project: @project
+ #diffs.diffs.tab-pane
+ - if @diffs.present?
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .alert.alert-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
+ - else
+ .alert.alert-danger
+ %h4 This comparison includes a huge diff.
+ %p To preserve performance the line changes are not shown.
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -50,11 +51,11 @@
e.preventDefault();
});
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
-
:javascript
var merge_request
merge_request = new MergeRequest({
- action: 'commits'
+ action: 'new',
+ diffs_loaded: true,
+ commits_loaded: true
});
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index c2f5cdacae7..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"
@@ -22,57 +22,52 @@
%span into
%strong.label-branch #{@merge_request.target_branch}
- if @merge_request.open?
- %span.pull-right
- .btn-group
- %a.btn.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.fa.fa-download
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
- %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ .btn-group.btn-group-sm.pull-right
+ %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
+ = icon('download')
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
= render "projects/merge_requests/show/how_to_merge"
- = render "projects/merge_requests/show/state_widget"
+ = render "projects/merge_requests/widget/show.html.haml"
- if @commits.present?
%ul.nav.nav-tabs.merge-request-tabs
- %li.notes-tab{data: {action: 'notes', toggle: 'tab'}}
- = link_to merge_request_path(@merge_request) do
- %i.fa.fa-comments
+ %li.notes-tab
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
+ = icon('comments')
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
- %li.commits-tab{data: {action: 'commits', toggle: 'tab'}}
- = link_to merge_request_path(@merge_request), title: 'Commits' do
- %i.fa.fa-history
+ %li.commits-tab
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
+ = icon('history')
Commits
%span.badge= @commits.size
- %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}}
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do
- %i.fa.fa-list-alt
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
+ = icon('list-alt')
Changes
%span.badge= @merge_request.diffs.size
- .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
- = render "projects/merge_requests/discussion"
- .commits.tab-content
- = render "projects/merge_requests/show/commits"
- .diffs.tab-content
- - if current_page?(action: 'diffs')
- = render "projects/merge_requests/show/diffs"
+ .tab-content
+ #notes.notes.tab-pane.voting_notes
+ = render "projects/merge_requests/discussion"
+ #commits.commits.tab-pane
+ - 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"
.mr-loading-status
= spinner
-
:javascript
var merge_request;
merge_request = new MergeRequest({
- url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
- check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
- ci_enable: #{@project.ci_service ? "true" : "false"},
- current_status: "#{@merge_request.automerge_status}",
action: "#{controller.action_name}"
});
diff --git a/app/views/projects/merge_requests/automerge.js.haml b/app/views/projects/merge_requests/automerge.js.haml
index a53cbb150a4..33321651e32 100644
--- a/app/views/projects/merge_requests/automerge.js.haml
+++ b/app/views/projects/merge_requests/automerge.js.haml
@@ -1,6 +1,6 @@
--if @status
+- if @status
:plain
- merge_request.mergeInProgress();
--else
+ merge_request_widget.mergeInProgress();
+- else
:plain
- merge_request.alreadyOrCannotBeMerged()
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ab845a7e719..e0bc1df97ee 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,12 +1,13 @@
- page_title "Merge Requests"
.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
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
- = render 'shared/issuable_filter'
+ - 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
.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/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 6474d32ac08..22f601ac99e 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -1,4 +1,4 @@
-%div#modal_merge_info.modal.hide
+%div#modal_merge_info.modal
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
deleted file mode 100644
index 882b219f6e2..00000000000
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ /dev/null
@@ -1,88 +0,0 @@
-- unless @allowed_to_merge
- - if @project.archived?
- %p
- %strong Archived projects cannot be committed to!
- - else
- .automerge_widget.cannot_be_merged.hide
- %strong This request can't be merged automatically. Even if it could be merged, you don't have permission to do so.
- .automerge_widget.work_in_progress.hide
- %strong This request can't be accepted because it is marked a Work In Progress. Even if it could be accepted, you don't have permission to do so.
- .automerge_widget.can_be_merged.hide
- %strong This request can be merged automatically, but you don't have permission to do so.
-
-
-- if @show_merge_controls
- .automerge_widget.can_be_merged.hide
- .clearfix
- = form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post do |f|
- .accept-merge-holder.clearfix.js-toggle-container
- .accept-action
- = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .accept-control.checkbox
- = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source-branch
- .accept-control
- = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
- %i.fa.fa-edit
- Modify commit message
- .js-toggle-content.hide.prepend-top-20
- = render 'shared/commit_message_container', params: params,
- text: @merge_request.merge_commit_message,
- rows: 14, hint: true
-
- %br
- .light
- If you still want to merge this request manually - use
- %strong
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
-
-
- .automerge_widget.no_satellite.hide
- %p
- %span
- %strong This repository does not have satellite. Ask an administrator to fix this issue
-
- .automerge_widget.cannot_be_merged.hide
- %h4
- This request can't be merged with GitLab.
- You should do it manually with
- %strong
- = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do
- command line
-
- %p
- %button.btn.disabled{:type => 'button'}
- %i.fa.fa-warning
- Accept Merge Request
- &nbsp;
- This usually happens when Git can not resolve conflicts between branches automatically.
-
- .automerge_widget.work_in_progress.hide
- %h4
- This request can't be accepted because it is marked a <strong>Work In Progress</strong>.
-
- %p
- %button.btn.disabled{:type => 'button'}
- %i.fa.fa-warning
- Accept Merge Request
- &nbsp;
-
- When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted.
-
- .automerge_widget.unchecked
- %p
- %strong
- %i.fa.fa-spinner.fa-spin
- Checking for ability to automatically merge…
-
- .automerge_widget.already_cannot_be_merged.hide
- %p
- %strong This merge request can not be merged. Try to reload the page.
-
- .merge-in-progress.hide
- %p
- %i.fa.fa-spinner.fa-spin
- &nbsp;
- Merge is in progress. Please wait. Page will be automatically reloaded. &nbsp;
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_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
deleted file mode 100644
index 3b1cd53df37..00000000000
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-- if @commits.any?
- .ci_widget.ci-success{style: "display:none"}
- = icon("check")
- %span CI build passed
- for #{@merge_request.last_commit_short_sha}.
- = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
-
-
- .ci_widget.ci-failed{style: "display:none"}
- = icon("times")
- %span CI build failed
- for #{@merge_request.last_commit_short_sha}.
- = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
-
- - [:running, :pending].each do |status|
- .ci_widget{class: "ci-#{status}", style: "display:none"}
- = icon("clock-o")
- %span CI build #{status}
- for #{@merge_request.last_commit_short_sha}.
- = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
-
- .ci_widget
- = icon("spinner spin")
- Checking for CI status for #{@merge_request.last_commit_short_sha}
-
- .ci_widget.ci-canceled{style: "display:none"}
- = icon("times")
- %span CI build canceled
- for #{@merge_request.last_commit_short_sha}.
- = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
-
- .ci_widget.ci-error{style: "display:none"}
- = icon("times")
- %span Cannot connect to the CI server. Please check your settings and try again.
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 46e92a9c558..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?
- Merged
- - elsif @merge_request.closed?
- Closed
- - 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/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
deleted file mode 100644
index 59cb85edfce..00000000000
--- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- if @source_branch.blank?
- Source branch has been removed
-
-- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged?
- .remove_source_branch_widget
- %p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
- = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
- %i.fa.fa-times
- Remove Source Branch
-
- .remove_source_branch_widget.failed.hide
- Failed to remove source branch '#{@merge_request.source_branch}'
-
- .remove_source_branch_in_progress.hide
- %i.fa.fa-spinner.fa-spin
- &nbsp;
- Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded. &nbsp;
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
deleted file mode 100644
index 44bd9347f51..00000000000
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-.mr-state-widget
- - if @merge_request.source_project.ci_service && @commits.any?
- .mr-widget-heading
- = render "projects/merge_requests/show/mr_ci"
- .mr-widget-body
- - if @merge_request.open?
- - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists?
- = render "projects/merge_requests/show/mr_accept"
- - else
- = render "projects/merge_requests/show/no_accept"
-
- - if @merge_request.closed?
- %h4
- Closed
- - if @merge_request.closed_event
- by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
- %p Changes were not merged into target branch
-
- - if @merge_request.merged?
- %h4
- Merged
- - if @merge_request.merge_event
- by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
- = render "projects/merge_requests/show/remove_source_branch"
-
- - if @merge_request.locked?
- %h4
- Merge in progress...
- %p
- Merging is in progress. While merging this request is locked and cannot be closed.
-
- - unless @commits.any?
- %h4 Nothing to merge
- %p
- Nothing to merge from
- %span.label-branch #{@merge_request.source_branch}
- to
- %span.label-branch #{@merge_request.target_branch}
- %br
- Try to use different branches or push new code.
-
- - if @closes_issues.present? && @merge_request.open?
- .mr-widget-footer
- %span
- %i.fa.fa-check
- Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
- = succeed '.' do
- != gfm(issues_sentence(@closes_issues))
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 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
new file mode 100644
index 00000000000..b5704c502c8
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_closed.html.haml
@@ -0,0 +1,9 @@
+.mr-state-widget
+ = render 'projects/merge_requests/widget/heading'
+ .mr-widget-body
+ %h4
+ 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)}
+ %p Changes were not merged into target branch
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
new file mode 100644
index 00000000000..4cc9c652b61
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -0,0 +1,44 @@
+- if @merge_request.has_ci?
+ .mr-widget-heading
+ .ci_widget.ci-success{style: "display:none"}
+ = icon("check")
+ %span CI build passed
+ for #{@merge_request.last_commit_short_sha}.
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+
+ .ci_widget.ci-failed{style: "display:none"}
+ = icon("times")
+ %span CI build failed
+ for #{@merge_request.last_commit_short_sha}.
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+
+ - [:running, :pending].each do |status|
+ .ci_widget{class: "ci-#{status}", style: "display:none"}
+ = icon("clock-o")
+ %span CI build #{status}
+ for #{@merge_request.last_commit_short_sha}.
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+
+ .ci_widget
+ = 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
+ for #{@merge_request.last_commit_short_sha}.
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+
+ .ci_widget.ci-error{style: "display:none"}
+ = icon("times")
+ %span Cannot connect to the CI server. Please check your settings and try again.
+
+ :coffeescript
+ $ ->
+ merge_request_widget.getCiStatus()
diff --git a/app/views/projects/merge_requests/widget/_locked.html.haml b/app/views/projects/merge_requests/widget/_locked.html.haml
new file mode 100644
index 00000000000..13ec278847b
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_locked.html.haml
@@ -0,0 +1,8 @@
+.mr-state-widget
+ = render 'projects/merge_requests/widget/heading'
+ .mr-widget-body
+ %h4
+ Merge in progress...
+ %p
+ Merging is in progress. While merging this request is locked and cannot be closed.
+
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
new file mode 100644
index 00000000000..a3b13140810
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -0,0 +1,41 @@
+.mr-state-widget
+ = render 'projects/merge_requests/widget/heading'
+ .mr-widget-body
+ %h4
+ 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)}
+ %div
+ - if @source_branch.blank?
+ Source branch has been removed
+
+ - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged?
+ .remove_source_branch_widget
+ %p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
+ = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
+ %i.fa.fa-times
+ Remove Source Branch
+
+ .remove_source_branch_widget.failed.hide
+ Failed to remove source branch '#{@merge_request.source_branch}'
+
+ .remove_source_branch_in_progress.hide
+ %i.fa.fa-spinner.fa-spin
+ &nbsp;
+ Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded. &nbsp;
+
+ :coffeescript
+ $('.remove_source_branch').on 'click', ->
+ $('.remove_source_branch_widget').hide()
+ $('.remove_source_branch_in_progress').show()
+
+ $(".remove_source_branch").on "ajax:success", (e, data, status, xhr) ->
+ location.reload()
+
+ $(".remove_source_branch").on "ajax:error", (e, data, status, xhr) ->
+ $('.remove_source_branch_widget').hide()
+ $('.remove_source_branch_in_progress').hide()
+ $('.remove_source_branch_widget.failed').show()
+
+
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
new file mode 100644
index 00000000000..bb794912f8f
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -0,0 +1,29 @@
+.mr-state-widget
+ = render 'projects/merge_requests/widget/heading'
+ .mr-widget-body
+ - if @project.archived?
+ = render 'projects/merge_requests/widget/open/archived'
+ - elsif !@project.satellite.exists?
+ = render 'projects/merge_requests/widget/open/no_satellite'
+ - elsif @merge_request.commits.blank?
+ = render 'projects/merge_requests/widget/open/nothing'
+ - elsif @merge_request.branch_missing?
+ = render 'projects/merge_requests/widget/open/missing_branch'
+ - elsif @merge_request.unchecked?
+ = render 'projects/merge_requests/widget/open/check'
+ - elsif @merge_request.cannot_be_merged?
+ = render 'projects/merge_requests/widget/open/conflicts'
+ - elsif @merge_request.work_in_progress?
+ = render 'projects/merge_requests/widget/open/wip'
+ - elsif !@merge_request.can_be_merged_by?(current_user)
+ = render 'projects/merge_requests/widget/open/not_allowed'
+ - elsif @merge_request.can_be_merged?
+ = render 'projects/merge_requests/widget/open/accept'
+
+ - if @closes_issues.present?
+ .mr-widget-footer
+ %span
+ %i.fa.fa-check
+ Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
+ = succeed '.' do
+ != gfm(issues_sentence(@closes_issues))
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
new file mode 100644
index 00000000000..263cab7a9e8
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -0,0 +1,20 @@
+- if @merge_request.open?
+ = render 'projects/merge_requests/widget/open'
+- elsif @merge_request.merged?
+ = render 'projects/merge_requests/widget/merged'
+- elsif @merge_request.closed?
+ = render 'projects/merge_requests/widget/closed'
+- elsif @merge_request.locked?
+ = render 'projects/merge_requests/widget/locked'
+
+:javascript
+ var merge_request_widget;
+
+ merge_request_widget = new MergeRequestWidget({
+ url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ check_enable: #{@merge_request.unchecked? ? "true" : "false"},
+ url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
+ ci_enable: #{@project.ci_service ? "true" : "false"},
+ current_status: "#{@merge_request.automerge_status}",
+ });
+
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
new file mode 100644
index 00000000000..f5bacaf280a
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -0,0 +1,32 @@
+= 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
+ = f.button class: "btn btn-create accept_merge_request" do
+ Accept Merge Request
+ - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
+ .accept-control.checkbox
+ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source-branch
+ .accept-control
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
+ %i.fa.fa-edit
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-20
+ = render 'shared/commit_message_container', params: params,
+ text: @merge_request.merge_commit_message,
+ rows: 14, hint: true
+
+ %br
+ .light
+ If you want to merge this request manually, you can use the
+ %strong
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+
+ :coffeescript
+ $('.accept-mr-form').on 'ajax:before', ->
+ btn = $('.accept_merge_request')
+ btn.disable()
+ btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress")
+
diff --git a/app/views/projects/merge_requests/widget/open/_archived.html.haml b/app/views/projects/merge_requests/widget/open/_archived.html.haml
new file mode 100644
index 00000000000..eaf113ee568
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_archived.html.haml
@@ -0,0 +1,2 @@
+%p
+ %strong Archived projects do not provide commit access.
diff --git a/app/views/projects/merge_requests/widget/open/_check.html.haml b/app/views/projects/merge_requests/widget/open/_check.html.haml
new file mode 100644
index 00000000000..e775447cb75
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_check.html.haml
@@ -0,0 +1,7 @@
+%strong
+ %i.fa.fa-spinner.fa-spin
+ Checking automatic merge…
+
+:coffeescript
+ $ ->
+ merge_request_widget.getMergeStatus()
diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
new file mode 100644
index 00000000000..d1db5fec43a
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -0,0 +1,9 @@
+- if @merge_request.can_be_merged_by?(current_user)
+ %h4
+ This merge request contains merge conflicts that must be resolved.
+ You can try it manually on the
+ %strong
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+- else
+ %strong This merge request contains merge conflicts that must be resolved.
+ Only those with write access to this repository can merge merge requests.
diff --git a/app/views/projects/merge_requests/show/_no_accept.html.haml b/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml
index 423fcd48e25..423fcd48e25 100644
--- a/app/views/projects/merge_requests/show/_no_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_missing_branch.html.haml
diff --git a/app/views/projects/merge_requests/widget/open/_no_satellite.html.haml b/app/views/projects/merge_requests/widget/open/_no_satellite.html.haml
new file mode 100644
index 00000000000..3718cfd8333
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_no_satellite.html.haml
@@ -0,0 +1,3 @@
+%p
+ %span
+ %strong This repository does not have a satellite. Please ask an administrator to fix this issue!
diff --git a/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml b/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml
new file mode 100644
index 00000000000..82f6ffd8fcb
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_not_allowed.html.haml
@@ -0,0 +1,2 @@
+%strong This request can be merged automatically.
+Only those with write access to this repository can merge merge requests.
diff --git a/app/views/projects/merge_requests/widget/open/_nothing.html.haml b/app/views/projects/merge_requests/widget/open/_nothing.html.haml
new file mode 100644
index 00000000000..4d526576bc2
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_nothing.html.haml
@@ -0,0 +1,8 @@
+%h4 Nothing to merge
+%p
+ Nothing to merge from
+ %span.label-branch #{@merge_request.source_branch}
+ to
+ %span.label-branch #{@merge_request.target_branch}
+ %br
+ Try to use different branches or push new code.
diff --git a/app/views/projects/merge_requests/widget/open/_reload.html.haml b/app/views/projects/merge_requests/widget/open/_reload.html.haml
new file mode 100644
index 00000000000..5787f6efea4
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_reload.html.haml
@@ -0,0 +1 @@
+This merge request cannot be merged. Try to reload the page.
diff --git a/app/views/projects/merge_requests/widget/open/_wip.html.haml b/app/views/projects/merge_requests/widget/open/_wip.html.haml
new file mode 100644
index 00000000000..4ce3ab31278
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_wip.html.haml
@@ -0,0 +1,13 @@
+- if @merge_request.can_be_merged_by?(current_user)
+ %h4
+ This merge request cannot be accepted because it is marked as Work In Progress.
+
+ %p
+ %button.btn.disabled{:type => 'button'}
+ %i.fa.fa-warning
+ Accept Merge Request
+ &nbsp;
+ When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted.
+- else
+ %strong This merge request is marked as Work In Progress.
+ Only those with write access to this repository can merge merge requests.
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 95b7070ce5c..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,10 +45,7 @@
:javascript
- disableButtonIfEmptyField("#milestone_title", ".btn-save");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 62360158ff9..14a0580f966 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -13,10 +13,10 @@
= milestone.expires_at
.row
.col-sm-6
- = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
+ = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.issues.count, 'Issue'
&nbsp;
- = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
+ = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.merge_requests.count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 22172a31289..5947498e379 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -61,12 +61,13 @@
Participants
%span.badge= @users.count
- - if can?(current_user, :write_issue, @project)
- .pull-right
+ .pull-right
+ - 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
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
+ - if can?(current_user, :read_issue, @project)
+ = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
@@ -85,7 +86,7 @@
.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: 'Declined (closed)', merge_requests: @merge_requests.declined, id: 'closed')
+ = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
.col-md-3
.panel.panel-primary
.panel-heading Merged
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..5114e63c05f 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
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 789f3e19fd2..c6726cbafa3 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -1,34 +1,34 @@
-- note1 = notes1.present? ? notes1.first : nil
-- note2 = notes2.present? ? notes2.first : nil
+- note1 = notes_left.present? ? notes_left.first : nil
+- note2 = notes_right.present? ? notes_right.first : nil
%tr.notes_holder
- if note1
- %td.notes_line
+ %td.notes_line.old
%span.btn.disabled
%i.fa.fa-comment
- = notes1.count
- %td.notes_content.parallel
+ = notes_left.count
+ %td.notes_content.parallel.old
%ul.notes{ rel: note1.discussion_id }
- = render notes1
+ = render notes_left
.discussion-reply-holder
- = link_to_reply_diff(note1)
+ = link_to_reply_diff(note1, 'old')
- else
- %td= ""
- %td= ""
+ %td.notes_line.old= ""
+ %td.notes_content.parallel.old= ""
- if note2
- %td.notes_line
+ %td.notes_line.new
%span.btn.disabled
%i.fa.fa-comment
- = notes2.count
- %td.notes_content.parallel
+ = notes_right.count
+ %td.notes_content.parallel.new
%ul.notes{ rel: note2.discussion_id }
- = render notes2
+ = render notes_right
.discussion-reply-holder
- = link_to_reply_diff(note2)
+ = link_to_reply_diff(note2, 'new')
- else
- %td= ""
- %td= ""
+ %td.notes_line.new= ""
+ %td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 2ada6cb6700..3fb044d736e 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,11 +1,13 @@
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
+ = hidden_field_tag :view, params[:view]
+ = hidden_field_tag :line_type
= note_target_fields(@note)
= f.hidden_field :commit_id
= f.hidden_field :line_code
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note,
classes: 'note_text js-note-text'
@@ -15,10 +17,7 @@
.error-alert
.note-form-actions
- .buttons
+ .buttons.clearfix
= f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 0a77f200f56..5478a887f91 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'] 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 813e37276bd..04222b8f7c4 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -3,8 +3,8 @@
.js-notes-busy
.js-main-target-form
-- if can? current_user, :write_note, @project
- = render "projects/notes/form"
+- if can? current_user, :create_note, @project
+ = render "projects/notes/form", view: params[:view]
:javascript
- new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
+ new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}")
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/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 635e4d70941..860a997cff8 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -38,8 +38,9 @@
&nbsp;
- if current_user == user
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
- %i.fa.fa-minus.fa-inverse
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
+ = icon("sign-out")
+ Leave
- else
= link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 6edb92acd4d..162583e4b1d 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -11,7 +11,7 @@
.clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @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/update.js.haml b/app/views/projects/update.js.haml
index 4f3f4cab8d5..7d9bd08385a 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.project-edit-content .btn-save').enableButton();
+ $('.project-edit-content .btn-save').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9fbfa0b1aeb..904600499ae 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -12,8 +12,7 @@
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
.row
- .col-sm-2
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
%p.cgray
To link to a (new) page you can just type
%code [Link Title](page-slug)
@@ -41,6 +40,3 @@
- else
= f.submit 'Create page', class: "btn-create btn"
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 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/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 6834969de8b..dace172438c 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -1,4 +1,4 @@
-%div#modal-new-wiki.modal.hide
+%div#modal-new-wiki.modal
.modal-dialog
.modal-content
.modal-header
@@ -8,6 +8,8 @@
= label_tag :new_wiki_path do
%span Page slug
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project)
+ %p.hidden.text-danger{data: { error: "slug" }}
+ The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and /
%p.hint
Please don't use spaces.
.modal-footer
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 47016daf1f0..5ee70be1ad6 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -5,7 +5,7 @@
= hidden_field_tag :scope, params[:scope]
.search-holder.clearfix
.form-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input input-mn-300", id: "dashboard_search", autofocus: true
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
= button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true'
.pull-right
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 3f489a04e71..6de2aed29ed 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -3,6 +3,7 @@
.input-group-addon.git-protocols
.input-group-btn
%button{ |
+ type: 'button', |
class: "btn btn-sm #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
:"data-clone" => project.ssh_url_to_repo, |
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH",
@@ -11,6 +12,7 @@
SSH
.input-group-btn
%button{ |
+ type: 'button', |
class: "btn btn-sm #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
:"data-clone" => project.http_url_to_repo, |
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}",
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 30ba361c860..5f51b0d450f 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -1,4 +1,4 @@
-#modal-confirm-danger.modal.hide{tabindex: -1}
+#modal-confirm-danger.modal{tabindex: -1}
.modal-dialog
.modal-content
.modal-header
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index fba69dd0f3f..d6a2e177da1 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,10 +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
- = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ -# 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/_project.html.haml b/app/views/shared/_project.html.haml
index 722a7f7ce0f..6bd61455d21 100644
--- a/app/views/shared/_project.html.haml
+++ b/app/views/shared/_project.html.haml
@@ -3,8 +3,6 @@
- if avatar
.dash-project-avatar
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
- .dash-project-access-icon
- = visibility_level_icon(project.visibility_level)
%span.str-truncated
%span.namespace-name
- if project.namespace
@@ -16,6 +14,3 @@
%span.pull-right.light
%i.fa.fa-star
= project.star_count
- - else
- %span.arrow
- %i.fa.fa-angle-right
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
new file mode 100644
index 00000000000..46990895d33
--- /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_issue, @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_issue, @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 4ab9421f013..a829782fc4f 100644
--- a/app/views/shared/_issuable_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -3,24 +3,36 @@
%ul.nav.nav-tabs
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened') do
- %i.fa.fa-exclamation-circle
+ = icon('exclamation-circle')
#{state_filters_text_for(:opened, @project)}
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed') do
- %i.fa.fa-check-circle
- #{state_filters_text_for(:closed, @project)}
+
+ - if defined?(type) && type == :merge_requests
+ %li{class: ("active" if params[:state] == 'merged')}
+ = link_to page_filter_path(state: 'merged') do
+ = icon('check-circle')
+ #{state_filters_text_for(:merged, @project)}
+
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
+ = icon('ban')
+ #{state_filters_text_for(:closed, @project)}
+ - else
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
+ = icon('check-circle')
+ #{state_filters_text_for(:closed, @project)}
+
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all') do
- %i.fa.fa-compass
+ = icon('compass')
#{state_filters_text_for(:all, @project)}
.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],
@@ -51,6 +63,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/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
new file mode 100644
index 00000000000..e434e1b6b98
--- /dev/null
+++ b/app/views/shared/issuable/_form.html.haml
@@ -0,0 +1,116 @@
+- if issuable.errors.any?
+ .row
+ .col-sm-offset-2.col-sm-10
+ .alert.alert-danger
+ - issuable.errors.full_messages.each do |msg|
+ %span= msg
+ %br
+.form-group
+ = f.label :title, class: 'control-label' do
+ %strong= 'Title *'
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, autofocus: true,
+ class: 'form-control pad js-gfm-input', required: true
+
+ - 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
+ <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
+ <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'
+ .col-sm-10
+
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do
+ = render 'projects/zen', f: f, attr: :description,
+ classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left
+ Parsed with
+ #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach files by dragging &amp; dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
+
+ .clearfix
+ .error-alert
+ %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 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 issuable.is_a?(MergeRequest)
+ %hr
+ - 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 })
+ .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', 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?
+ %p
+ Please review the
+ %strong #{link_to 'guidelines for contribution', guide_url}
+ to this repository.
+ - if issuable.new_record?
+ = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
+ - else
+ = f.submit 'Save changes', class: 'btn btn-save'
+ - if issuable.new_record?
+ - cancel_project = issuable.source_project
+ - else
+ - cancel_project = issuable.project
+ = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 639d203dcd6..58c3de64b77 100644
--- a/app/views/shared/_issuable_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,6 +1,6 @@
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 9610f9ce414..913b6744844 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -12,13 +12,13 @@
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
- .form-group
- .file-editor
+ .file-editor
+ .form-group
= f.label :file_name, "File", class: 'control-label'
.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'
@@ -29,7 +29,7 @@
- else
= f.submit 'Save', class: "btn-save btn"
- - if @snippet.respond_to?(:project)
+ - if @snippet.project_id
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/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 6ed45fedfa2..15d53499e03 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,11 +9,12 @@
.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), target: '_blank' do
+ = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user
- .pull-right
+ .pull-right.hidden-xs
= link_to profile_path, class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
Edit Profile settings
diff --git a/bin/guard b/bin/guard
deleted file mode 100755
index 0c1a532bd01..00000000000
--- a/bin/guard
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env ruby
-#
-# This file was generated by Bundler.
-#
-# The application 'guard' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-require 'pathname'
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
- Pathname.new(__FILE__).realpath)
-
-require 'rubygems'
-require 'bundler/setup'
-
-load Gem.bin_path('guard', 'guard')
diff --git a/bin/rake b/bin/rake
index 8017a0271d2..0fb4e07e13a 100755
--- a/bin/rake
+++ b/bin/rake
@@ -3,6 +3,5 @@ begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
end
-require_relative '../config/boot'
-require 'rake'
-Rake.application.run
+require 'bundler/setup'
+load Gem.bin_path('rake', 'rake')
diff --git a/bin/spring b/bin/spring
index 253ec37c345..7b45d374fcd 100755
--- a/bin/spring
+++ b/bin/spring
@@ -1,17 +1,14 @@
#!/usr/bin/env ruby
-# This file loads spring without using Bundler, in order to be fast
-# It gets overwritten when you run the `spring binstub` command
+# This file loads spring without using Bundler, in order to be fast.
+# It gets overwritten when you run the `spring binstub` command.
unless defined?(Spring)
require "rubygems"
require "bundler"
- if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
- ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
- ENV["GEM_HOME"] = ""
- Gem.paths = ENV
-
+ if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)
+ Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq }
gem "spring", match[1]
require "spring/binstub"
end
diff --git a/config.ru b/config.ru
index e90863a5c21..a2525c81361 100644
--- a/config.ru
+++ b/config.ru
@@ -2,11 +2,14 @@
if defined?(Unicorn)
require 'unicorn'
- # Unicorn self-process killer
- require 'unicorn/worker_killer'
- # Max memory size (RSS) per worker
- use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20))
+ if ENV['RAILS_ENV'] == 'production' || ENV['RAILS_ENV'] == 'staging'
+ # Unicorn self-process killer
+ require 'unicorn/worker_killer'
+
+ # Max memory size (RSS) per worker
+ use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20))
+ end
end
require ::File.expand_path('../config/environment', __FILE__)
diff --git a/config/aws.yml.example b/config/aws.yml.example
index 29d029b078d..bb10c3cec7b 100644
--- a/config/aws.yml.example
+++ b/config/aws.yml.example
@@ -1,5 +1,8 @@
# See https://github.com/jnicklas/carrierwave#using-amazon-s3
# for more options
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
production:
access_key_id: AKIA1111111111111UA
secret_access_key: secret
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index fbc7f515f34..c32ac2042d0 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -4,9 +4,13 @@
#
########################### NOTE #####################################
# This file should not receive new settings. All configuration options #
-# are being moved to ApplicationSetting model! #
+# that do not require application restart are being moved to #
+# ApplicationSetting model! #
+# If you change this file in a Merge Request, please also create #
+# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests #
########################################################################
#
+#
# How to use:
# 1. Copy file as gitlab.yml
# 2. Update gitlab -> host with your fully qualified domain name
@@ -58,12 +62,13 @@ production: &base
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
- ## Default theme
- ## BASIC = 1
- ## MARS = 2
- ## MODERN = 3
- ## GRAY = 4
- ## COLOR = 5
+ ## Default theme ID
+ ## 1 - Graphite
+ ## 2 - Charcoal
+ ## 3 - Green
+ ## 4 - Gray
+ ## 5 - Violet
+ ## 6 - Blue
# default_theme: 2 # default: 2
## Automatic issue closing
@@ -149,7 +154,7 @@ production: &base
allow_username_or_email_login: false
# To maintain tight control over the number of active users on your GitLab installation,
- # enable this setting to keep new users blocked until they have been cleared by the admin
+ # enable this setting to keep new users blocked until they have been cleared by the admin
# (default: false).
block_auto_created_users: false
@@ -182,12 +187,19 @@ production: &base
# Allow login via Twitter, Google, etc. using OmniAuth providers
enabled: false
+ # Uncomment this to automatically sign in with a specific omniauth provider's without
+ # showing GitLab's sign-in page (default: show the GitLab sign-in page)
+ # auto_sign_in_with_provider: saml
+
# CAUTION!
# This allows users to login without having a user account first (default: false).
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
+ # Look up new users in LDAP servers. If a match is found (same uid), automatically
+ # link the omniauth identity with the LDAP account. (default: false)
+ auto_link_ldap_user: false
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
@@ -210,6 +222,15 @@ production: &base
# args: { scope: 'api' } }
# - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET'}
+ # - { name: 'saml',
+ # args: {
+ # assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ # idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ # idp_sso_target_url: 'https://login.example.com/idp',
+ # issuer: 'https://gitlab.example.com',
+ # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ # } }
+
@@ -236,6 +257,9 @@ production: &base
# aws_secret_access_key: 'secret123'
# # The remote 'directory' to store your backups. For S3, this would be the bucket name.
# remote_directory: 'my.s3.bucket'
+ # # Use multipart uploads when file size reaches 100MB, see
+ # # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
+ # multipart_chunk_size: 104857600
## GitLab Shell settings
gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 2351ef7b0ce..7b5d488f59e 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -87,6 +87,11 @@ end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
+Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
+Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
+Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil?
+Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
+
Settings.omniauth['providers'] ||= []
Settings['issues_tracker'] ||= {}
@@ -98,7 +103,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
-Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
+Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
@@ -126,6 +131,7 @@ Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
+Settings.gitlab['session_expire_delay'] ||= 10080
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
@@ -169,6 +175,7 @@ Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'co
if Settings.backup['upload']['connection']
Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
end
+Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
#
# Git
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index 38a5fa98dc2..1d958904e8f 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -3,7 +3,8 @@ if Rails.env.development?
# initialization is skipped so trigger it
Rack::MiniProfilerRails.initialize!(Rails.application)
+
Rack::MiniProfiler.config.position = 'right'
- Rack::MiniProfiler.config.start_hidden = true
- Rack::MiniProfiler.config.skip_paths << '/specs'
+ Rack::MiniProfiler.config.start_hidden = false
+ Rack::MiniProfiler.config.skip_paths << '/teaspoon'
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index 103aa06ca32..df73ec1304a 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -12,6 +12,16 @@ if Gitlab::LDAP::Config.enabled?
end
OmniAuth.config.allowed_request_methods = [:post]
+#In case of auto sign-in, the GET method is used (users don't get to click on a button)
+OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env|
OmniAuth::RequestForgeryProtection.new(env).call
end
+
+if Gitlab.config.omniauth.enabled
+ Gitlab.config.omniauth.providers.each do |provider|
+ if provider['name'] == 'kerberos'
+ require 'omniauth-kerberos'
+ end
+ end
+end
diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example
index 332865d2881..b1bbcca1d61 100644
--- a/config/initializers/rack_attack.rb.example
+++ b/config/initializers/rack_attack.rb.example
@@ -1,6 +1,7 @@
# 1. Rename this file to rack_attack.rb
# 2. Review the paths_to_be_protected and add any other path you need protecting
#
+# If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
paths_to_be_protected = [
"#{Rails.application.config.relative_url_root}/users/password",
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index b2d59f1c4b7..6d274cd95a1 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -1,11 +1,15 @@
# Be sure to restart your server when you modify this file.
+require 'gitlab/current_settings'
+include Gitlab::CurrentSettings
+Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay
+
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
- expire_after: 1.week,
+ expire_after: Settings.gitlab['session_expire_delay'] * 60,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
)
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index f0fe2fdfa43..25ec247a095 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -5,6 +5,7 @@
#
# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html
#
+# If you change this file in a Merge Request, please also create a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp
diff --git a/config/resque.yml.example b/config/resque.yml.example
index 347f3599b20..d98f43f71b2 100644
--- a/config/resque.yml.example
+++ b/config/resque.yml.example
@@ -1,3 +1,6 @@
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
development: redis://localhost:6379
test: redis://localhost:6379
production: unix:/var/run/redis/redis.sock
diff --git a/config/routes.rb b/config/routes.rb
index bf2cb6421c5..33f55dde476 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,7 +2,6 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
- mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
use_doorkeeper do
controllers applications: 'oauth/applications',
authorized_applications: 'oauth/authorized_applications',
@@ -150,7 +149,12 @@ 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
@@ -166,7 +170,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :deploy_keys, only: [:index, :show, :new, :create, :destroy]
+ resources :deploy_keys, only: [:index, :new, :create, :destroy]
resources :hooks, only: [:index, :create, :destroy] do
get :test
@@ -204,7 +208,6 @@ Gitlab::Application.routes.draw do
resource :profile, only: [:show, :update] do
member do
get :history
- get :design
get :applications
put :reset_private_token
@@ -223,6 +226,7 @@ Gitlab::Application.routes.draw do
put :reset
end
end
+ resource :preferences, only: [:show, :update]
resources :keys
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
@@ -294,7 +298,7 @@ Gitlab::Application.routes.draw do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end
- root to: "dashboard#show"
+ root to: "root#show"
#
# Project Area
@@ -422,7 +426,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do
+ resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do
member do
put :enable
put :disable
@@ -450,6 +454,7 @@ Gitlab::Application.routes.draw do
resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
member do
get :diffs
+ get :commits
post :automerge
get :automerge_check
get :ci_status
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 86a5512e761..b937b092789 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -8,6 +8,9 @@
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
+# Note: If you change this file in a Merge Request, please also create a
+# Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
# WARNING: See config/application.rb under "Relative url support" for the list of
# other files that need to be changed for relative url support
#
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index bba2fc4b186..b25d0dfc701 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do
s.email = 'admin@example.com'
s.notification_email = 'admin@example.com'
s.username = 'root'
- s.password = '5iveL!fe'
+ s.password = 'password'
s.admin = true
s.projects_limit = 100
s.confirmed_at = DateTime.now
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index ae4c0550a4f..87839770924 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -23,7 +23,7 @@ Sidekiq::Testing.inline! do
name: group_path.titleize,
path: group_path
)
- group.description = Faker::Lorem.sentence
+ group.description = FFaker::Lorem.sentence
group.save
group.add_owner(User.first)
@@ -35,7 +35,7 @@ Sidekiq::Testing.inline! do
import_url: url,
namespace_id: group.id,
name: project_path.titleize,
- description: Faker::Lorem.sentence,
+ description: FFaker::Lorem.sentence,
visibility_level: Gitlab::VisibilityLevel.values.sample
}
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index 24952a1f661..378354efd5a 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -2,9 +2,9 @@ Gitlab::Seeder.quiet do
(2..20).each do |i|
begin
User.create!(
- username: Faker::Internet.user_name,
- name: Faker::Name.name,
- email: Faker::Internet.email,
+ username: FFaker::Internet.user_name,
+ name: FFaker::Name.name,
+ email: FFaker::Internet.email,
confirmed_at: DateTime.now,
password: '12345678'
)
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index 2296821e528..a43116829d9 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -3,7 +3,7 @@ Gitlab::Seeder.quiet do
(1..5).each do |i|
milestone_params = {
title: "v#{i}.0",
- description: Faker::Lorem.sentence,
+ description: FFaker::Lorem.sentence,
state: ['opened', 'closed'].sample,
}
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index e8b01b46d22..c636e96381c 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -2,8 +2,8 @@ Gitlab::Seeder.quiet do
Project.all.each do |project|
(1..10).each do |i|
issue_params = {
- title: Faker::Lorem.sentence(6),
- description: Faker::Lorem.sentence,
+ title: FFaker::Lorem.sentence(6),
+ description: FFaker::Lorem.sentence,
state: ['opened', 'closed'].sample,
milestone: project.milestones.sample,
assignee: project.team.users.sample
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index f9b2fd8b05f..0825776ffaa 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -10,8 +10,8 @@ Gitlab::Seeder.quiet do
params = {
source_branch: source_branch,
target_branch: target_branch,
- title: Faker::Lorem.sentence(6),
- description: Faker::Lorem.sentences(3).join(" "),
+ title: FFaker::Lorem.sentence(6),
+ description: FFaker::Lorem.sentences(3).join(" "),
milestone: project.milestones.sample,
assignee: project.team.users.sample
}
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index b3a6f39c7d5..3bd4b442ade 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -28,8 +28,8 @@ eos
PersonalSnippet.seed(:id, [{
id: i,
author_id: user.id,
- title: Faker::Lorem.sentence(3),
- file_name: Faker::Internet.domain_word + '.rb',
+ title: FFaker::Lorem.sentence(3),
+ file_name: FFaker::Internet.domain_word + '.rb',
visibility_level: Gitlab::VisibilityLevel.values.sample,
content: content,
}])
diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb
index d37be53c7b9..566c0705638 100644
--- a/db/fixtures/development/13_comments.rb
+++ b/db/fixtures/development/13_comments.rb
@@ -6,7 +6,7 @@ Gitlab::Seeder.quiet do
note_params = {
noteable_type: 'Issue',
noteable_id: issue.id,
- note: Faker::Lorem.sentence,
+ note: FFaker::Lorem.sentence,
}
Notes::CreateService.new(project, user, note_params).execute
@@ -21,7 +21,7 @@ Gitlab::Seeder.quiet do
note_params = {
noteable_type: 'MergeRequest',
noteable_id: mr.id,
- note: Faker::Lorem.sentence,
+ note: FFaker::Lorem.sentence,
}
Notes::CreateService.new(project, user, note_params).execute
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 8b560ee09e0..1af8dfc0ef0 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,5 +1,5 @@
if ENV['GITLAB_ROOT_PASSWORD'].blank?
- password = '5iveL!fe'
+ password = 'password'
expire_time = Time.now
else
password = ENV['GITLAB_ROOT_PASSWORD']
@@ -12,7 +12,7 @@ admin = User.create(
username: 'root',
password: password,
password_expires_at: expire_time,
- theme_id: Gitlab::Theme::MARS
+ theme_id: Gitlab::Themes::APPLICATION_DEFAULT
)
diff --git a/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
new file mode 100644
index 00000000000..6a78294f0b2
--- /dev/null
+++ b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddUserOauthApplicationsToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :user_oauth_applications, :bool, default: true
+ end
+end
diff --git a/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
new file mode 100644
index 00000000000..83e08101407
--- /dev/null
+++ b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
@@ -0,0 +1,5 @@
+class AddAfterSignOutPathForApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :after_sign_out_path, :string
+ end
+end \ No newline at end of file
diff --git a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
new file mode 100644
index 00000000000..ffa22e6d5ef
--- /dev/null
+++ b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
@@ -0,0 +1,5 @@
+class AddSessionExpireDelayForApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :session_expire_delay, :integer, default: 10080, null: false
+ end
+end \ No newline at end of file
diff --git a/db/migrate/20150610065936_add_dashboard_to_users.rb b/db/migrate/20150610065936_add_dashboard_to_users.rb
new file mode 100644
index 00000000000..2628e450722
--- /dev/null
+++ b/db/migrate/20150610065936_add_dashboard_to_users.rb
@@ -0,0 +1,9 @@
+class AddDashboardToUsers < ActiveRecord::Migration
+ def up
+ add_column :users, :dashboard, :integer, default: 0
+ end
+
+ def down
+ remove_column :users, :dashboard
+ 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/schema.rb b/db/schema.rb
index 1ab91256406..3a5af6a76d4 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: 20150516060434) do
+ActiveRecord::Schema.define(version: 20150620233230) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -29,10 +29,13 @@ ActiveRecord::Schema.define(version: 20150516060434) do
t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
- t.integer "max_attachment_size", default: 10, null: false
+ t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
+ t.boolean "user_oauth_applications", default: true
+ t.string "after_sign_out_path"
+ t.integer "session_expire_delay", default: 10080, null: false
end
create_table "broadcast_messages", force: true do |t|
@@ -493,12 +496,13 @@ ActiveRecord::Schema.define(version: 20150516060434) do
t.string "bitbucket_access_token"
t.string "bitbucket_access_token_secret"
t.string "location"
- t.string "public_email", default: "", null: false
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
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 4e00dceac2b..424b9e9a47e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,29 +3,32 @@
## User documentation
- [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.
+- [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
-- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
## Administrator documentation
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Log system](logs/logs.md) Log system.
+- [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
-- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
-- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
-- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
-- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
-- [Operations](operations/README.md) Keeping GitLab up and running
-- [Log system](logs/logs.md) Log system
## Contributor documentation
diff --git a/doc/api/README.md b/doc/api/README.md
index f6757b0a6aa..ca58c184543 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -19,6 +19,7 @@
- [Deploy Keys](deploy_keys.md)
- [System Hooks](system_hooks.md)
- [Groups](groups.md)
+- [Namespaces](namespaces.md)
## Clients
diff --git a/doc/api/groups.md b/doc/api/groups.md
index c903a850fdd..0b9f6406d8d 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -1,178 +1,192 @@
-# Groups
-
-## List project groups
-
-Get a list of groups. (As user: my groups, as admin: all groups)
-
-```
-GET /groups
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-You can search for groups by name or path, see below.
-
-## Details of a group
-
-Get all details of a group.
-
-```
-GET /groups/:id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-
-## New group
-
-Creates a new project group. Available only for users who can create groups.
-
-```
-POST /groups
-```
-
-Parameters:
-
-- `name` (required) - The name of the group
-- `path` (required) - The path of the group
-- `description` (optional) - The group's description
-
-## Transfer project to group
-
-Transfer a project to the Group namespace. Available only for admin
-
-```
-POST /groups/:id/projects/:project_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `project_id` (required) - The ID of a project
-
-## Remove group
-
-Removes group with all projects inside.
-
-```
-DELETE /groups/:id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-
-## Search for group
-
-Get all groups that match your string in their name or path.
-
-```
-GET /groups?search=foobar
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-## Group members
-
-**Group access levels**
-
-The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
-
-```
-GUEST = 10
-REPORTER = 20
-DEVELOPER = 30
-MASTER = 40
-OWNER = 50
-```
-
-### List group members
-
-Get a list of group members viewable by the authenticated user.
-
-```
-GET /groups/:id/members
-```
-
-```json
-[
- {
- "id": 1,
- "username": "raymond_smith",
- "email": "ray@smith.org",
- "name": "Raymond Smith",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- },
- {
- "id": 2,
- "username": "john_doe",
- "email": "joh@doe.org",
- "name": "John Doe",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- }
-]
-```
-
-### Add group member
-
-Adds a user to the list of group members.
-
-```
-POST /groups/:id/members
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `user_id` (required) - The ID of a user to add
-- `access_level` (required) - Project access level
-
-### Edit group team member
-
-Updates a group team member to a specified access level.
-
-```
-PUT /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID of a group
-- `user_id` (required) - The ID of a group member
-- `access_level` (required) - Project access level
-
-### Remove user team member
-
-Removes user from user team.
-
-```
-DELETE /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-- `user_id` (required) - The ID of a group member
+# Groups
+
+## List project groups
+
+Get a list of groups. (As user: my groups, as admin: all groups)
+
+```
+GET /groups
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
+
+You can search for groups by name or path, see below.
+
+## Details of a group
+
+Get all details of a group.
+
+```
+GET /groups/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a group
+
+## New group
+
+Creates a new project group. Available only for users who can create groups.
+
+```
+POST /groups
+```
+
+Parameters:
+
+- `name` (required) - The name of the group
+- `path` (required) - The path of the group
+- `description` (optional) - The group's description
+
+## Transfer project to group
+
+Transfer a project to the Group namespace. Available only for admin
+
+```
+POST /groups/:id/projects/:project_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a group
+- `project_id` (required) - The ID of a project
+
+## Remove group
+
+Removes group with all projects inside.
+
+```
+DELETE /groups/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a user group
+
+## Search for group
+
+Get all groups that match your string in their name or path.
+
+```
+GET /groups?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
+
+## Group members
+
+**Group access levels**
+
+The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
+
+```
+GUEST = 10
+REPORTER = 20
+DEVELOPER = 30
+MASTER = 40
+OWNER = 50
+```
+
+### List group members
+
+Get a list of group members viewable by the authenticated user.
+
+```
+GET /groups/:id/members
+```
+
+```json
+[
+ {
+ "id": 1,
+ "username": "raymond_smith",
+ "email": "ray@smith.org",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+ },
+ {
+ "id": 2,
+ "username": "john_doe",
+ "email": "joh@doe.org",
+ "name": "John Doe",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+ }
+]
+```
+
+### Add group member
+
+Adds a user to the list of group members.
+
+```
+POST /groups/:id/members
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a group
+- `user_id` (required) - The ID of a user to add
+- `access_level` (required) - Project access level
+
+### Edit group team member
+
+Updates a group team member to a specified access level.
+
+```
+PUT /groups/:id/members/:user_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a group
+- `user_id` (required) - The ID of a group member
+- `access_level` (required) - Project access level
+
+### Remove user team member
+
+Removes user from user team.
+
+```
+DELETE /groups/:id/members/:user_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a user group
+- `user_id` (required) - The ID of a group member
+
+## Namespaces in groups
+
+By default, groups only get 20 namespaces at a time because the API results are paginated.
+
+To get more (up to 100), pass the following as an argument to the API call:
+```
+/groups?per_page=100
+```
+
+And to switch pages add:
+```
+/groups?per_page=100&page=2
+``` \ No newline at end of file
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index c1d82ad9576..7b0873a9111 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -221,7 +221,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Update MR
-Updates an existing merge request. You can change branches, title, or even close the MR.
+Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
PUT /projects/:id/merge_request/:merge_request_id
@@ -231,7 +231,6 @@ Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - ID of MR
-- `source_branch` - The source branch
- `target_branch` - The target branch
- `assignee_id` - Assignee user ID
- `title` - Title of MR
@@ -242,7 +241,6 @@ Parameters:
{
"id": 1,
"target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
"description": "description1",
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
new file mode 100644
index 00000000000..7b3238441f6
--- /dev/null
+++ b/doc/api/namespaces.md
@@ -0,0 +1,44 @@
+# Namespaces
+
+## List namespaces
+
+Get a list of namespaces. (As user: my namespaces, as admin: all namespaces)
+
+```
+GET /namespaces
+```
+
+```json
+[
+ {
+ "id": 1,
+ "path": "user1",
+ "kind": "user"
+ },
+ {
+ "id": 2,
+ "path": "group1",
+ "kind": "group"
+ }
+]
+```
+
+You can search for namespaces by name or path, see below.
+
+## Search for namespace
+
+Get all namespaces that match your string in their name or path.
+
+```
+GET /namespaces?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "path": "user1",
+ "kind": "user"
+ }
+]
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index cd141daadc8..8b04282f160 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
}
]
```
diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md
index 4ad3bd534e0..21f1b3edecd 100644
--- a/doc/development/db_dump.md
+++ b/doc/development/db_dump.md
@@ -4,6 +4,9 @@ Sometimes it is useful to import the database from a production environment
into a staging environment for testing. The procedure below assumes you have
SSH+sudo access to both the production environment and the staging VM.
+**Destroy your staging VM** when you are done with it. It is important to avoid
+data leaks.
+
On the staging VM, add the following line to `/etc/gitlab/gitlab.rb` to speed up
large database imports.
@@ -12,6 +15,8 @@ large database imports.
echo "postgresql['checkpoint_segments'] = 64" | sudo tee -a /etc/gitlab/gitlab.rb
sudo touch /etc/gitlab/skip-auto-migrations
sudo gitlab-ctl reconfigure
+sudo gitlab-ctl stop unicorn
+sudo gitlab-ctl stop sidekiq
```
Next, we let the production environment stream a compressed SQL dump to our
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 821027f43fa..2d1d0fb4154 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -177,3 +177,33 @@ File.open(full_path) do # Etc.
```
A check like this could have avoided CVE-2013-4583.
+
+## Properly anchor regular expressions to the start and end of strings
+
+When using regular expressions to validate user input that is passed as an argument to a shell command, make sure to use the `\A` and `\z` anchors that designate the start and end of the string, rather than `^` and `$`, or no anchors at all.
+
+If you don't, an attacker could use this to execute commands with potentially harmful effect.
+
+For example, when a project's `import_url` is validated like below, the user could trick GitLab into cloning from a Git repository on the local filesystem.
+
+```ruby
+validates :import_url, format: { with: URI.regexp(%w(ssh git http https)) }
+# URI.regexp(%w(ssh git http https)) roughly evaluates to /(ssh|git|http|https):(something_that_looks_like_a_url)/
+```
+
+Suppose the user submits the following as their import URL:
+
+```
+file://git:/tmp/lol
+```
+
+Since there are no anchors in the used regular expression, the `git:/tmp/lol` in the value would match, and the validation would pass.
+
+When importing, GitLab would execute the following command, passing the `import_url` as an argument:
+
+
+```sh
+git clone file://git:/tmp/lol
+```
+
+Git would simply ignore the `git:` part, interpret the path as `file:///tmp/lol` and import the repository into the new project, in turn potentially giving the attacker access to any repository in the system, whether private or not.
diff --git a/doc/gitlab_basics/README.md b/doc/gitlab_basics/README.md
new file mode 100644
index 00000000000..de56c3ab4fc
--- /dev/null
+++ b/doc/gitlab_basics/README.md
@@ -0,0 +1,7 @@
+# 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)
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/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_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/create_your_ssh_keys.md b/doc/gitlab_basics/create_your_ssh_keys.md
new file mode 100644
index 00000000000..1e7f7c28513
--- /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 commandline 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 commandline
+
+* Go to your [commandline](start_using_git.md) and follow the [instructions](https://gitlab.com/help/ssh/README) to generate it
+
+* Copy the SSH Key that your commandline 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..f01a2f77eec
--- /dev/null
+++ b/doc/gitlab_basics/start_using_git.md
@@ -0,0 +1,67 @@
+# Start using Git on the commandline
+
+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 d167d2889bb..cf58abea4eb 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -88,8 +88,8 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
- cd git-2.1.2/
+ curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.4.3.tar.gz | tar xz
+ cd git-2.4.3/
./configure
make prefix=/usr/local all
@@ -195,9 +195,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-11-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-12-stable gitlab
-**Note:** You can change `7-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -241,10 +241,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Copy the example Rack attack config
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
- # Configure Git global settings for git user, useful when editing via web
- # Edit user.email according to what is set in gitlab.yml
- sudo -u git -H git config --global user.name "GitLab"
- sudo -u git -H git config --global user.email "example@example.com"
+ # Configure Git global settings for git user, used when editing via web editor
sudo -u git -H git config --global core.autocrlf input
# Configure Redis connection settings
@@ -302,6 +299,8 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
+**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
+
### Initialize Database and Activate Advanced Features
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
@@ -369,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.
@@ -402,7 +404,7 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
root
- 5iveL!fe
+ password
**Important Note:** On login you'll be prompted to change the password.
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/bitbucket.md b/doc/integration/bitbucket.md
index d82e1f8b41b..6a0fa4ce015 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -68,6 +68,8 @@ Bitbucket will generate an application ID and secret key for you to use.
1. Save the configuration file.
+1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```).
+
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a Bitbucket icon below the regular sign in form.
@@ -80,43 +82,59 @@ To allow projects to be imported directly into GitLab, Bitbucket requires two ex
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
-### Step 1: Known hosts
+### Step 1: Public key
-To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
+To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
-1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+
+1. Create a new SSH key:
```sh
- ssh git@bitbucket.org
+ sudo -u git -H ssh-keygen
```
-1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+ When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
+ Make sure to use an **empty passphrase**.
+
+1. Configure SSH client to use your new key:
+
+ Open the SSH configuration file of the git user.
```sh
- The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
- RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
- Are you sure you want to continue connecting (yes/no)?
+ sudo editor /home/git/.ssh/config
```
-1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+ Add a host configuration for `bitbucket.org`.
+
+ ```sh
+ Host bitbucket.org
+ IdentityFile ~/.ssh/bitbucket_rsa
+ User git
+ ```
-1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2:
+### Step 2: Known hosts
-### Step 2: Public key
+To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
-To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
+1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
-If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+ ```sh
+ sudo -u git -H ssh bitbucket.org
+ ```
-1. Create a new SSH key:
+1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
```sh
- sudo -u git -H ssh-keygen
+ The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
+ RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ Are you sure you want to continue connecting (yes/no)?
```
- When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
- Make sure to use an **empty passphrase**.
+1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+
+1. Your GitLab server is now able to connect to Bitbucket over SSH.
-2. Restart GitLab to allow it to find the new public key.
+1. Restart GitLab to allow it to find the new public key.
You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index b67f793c591..904d5d7fee2 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,13 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Security
+
+GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email' or 'userPrincipalName' attribute.
+An LDAP user who is allowed to change their email on the LDAP server can [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users) on your GitLab server.
+
+We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
+
## Configuring GitLab for LDAP integration
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 24f7b4bb4b4..8e2a602ec35 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -75,6 +75,7 @@ Now we can choose one or more of the Supported Providers below to continue confi
- [Google](google.md)
- [Shibboleth](shibboleth.md)
- [Twitter](twitter.md)
+- [SAML](saml.md)
## Enable OmniAuth for an Existing User
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
new file mode 100644
index 00000000000..a8cc5c8f74a
--- /dev/null
+++ b/doc/integration/saml.md
@@ -0,0 +1,77 @@
+# SAML OmniAuth Provider
+
+GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as Microsoft ADFS to authenticate users.
+
+First configure SAML 2.0 support in GitLab, then register the GitLab application in your SAML IdP:
+
+1. Make sure GitLab is configured with HTTPS. See [Using HTTPS](../install/installation.md#using-https) for instructions.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "saml",
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ - { name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ } }
+ ```
+
+1. Change the value for 'assertion_consumer_service_url' to match the HTTPS endpoint of GitLab (append 'users/auth/saml/callback' to the HTTPS URL of your GitLab installation to generate the correct value).
+
+1. Change the values of 'idp_cert_fingerprint', 'idp_sso_target_url', 'name_identifier_format' to match your IdP. Check [the omniauth-saml documentation](https://github.com/PracticallyGreen/omniauth-saml) for details on these options.
+
+1. Change the value of 'issuer' to a unique name, which will identify the application to the IdP.
+
+1. Restart GitLab for the changes to take effect.
+
+1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified in 'issuer'.
+
+To ease configuration, most IdP accept a metadata URL for the application to provide configuration information to the IdP. To build the metadata URL for GitLab, append 'users/auth/saml/metadata' to the HTTPS URL of your GitLab installation, for instance:
+ ```
+ https://gitlab.example.com/users/auth/saml/metadata
+ ```
+
+At a minimum the IdP *must* provide 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. GitLab will also use claims with name 'name', 'first_name', 'last_name' (see [the omniauth-saml gem](https://github.com/PracticallyGreen/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb) for supported claims).
+
+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.
+
diff --git a/doc/operations/README.md b/doc/operations/README.md
index f1456c6c8e2..6a35dab7b6c 100644
--- a/doc/operations/README.md
+++ b/doc/operations/README.md
@@ -2,3 +2,4 @@
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
+- [Understanding Unicorn and unicorn-worker-killer](unicorn.md)
diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md
new file mode 100644
index 00000000000..31b432cd411
--- /dev/null
+++ b/doc/operations/unicorn.md
@@ -0,0 +1,86 @@
+# Understanding Unicorn and unicorn-worker-killer
+
+## Unicorn
+
+GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web
+server, to handle web requests (web browsers and Git HTTP clients). Unicorn is
+a daemon written in Ruby and C that can load and run a Ruby on Rails
+application; in our case the Rails application is GitLab Community Edition or
+GitLab Enterprise Edition.
+
+Unicorn has a multi-process architecture to make better use of available CPU
+cores (processes can run on different cores) and to have stronger fault
+tolerance (most failures stay isolated in only one process and cannot take down
+GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby
+environment with the GitLab application code, and then spawns 'workers' which
+inherit this clean initial environment. The 'master' never handles any
+requests, that is left to the workers. The operating system network stack
+queues incoming requests and distributes them among the workers.
+
+In a perfect world, the master would spawn its pool of workers once, and then
+the workers handle incoming web requests one after another until the end of
+time. In reality, worker processes can crash or time out: if the master notices
+that a worker takes too long to handle a request it will terminate the worker
+process with SIGKILL ('kill -9'). No matter how the worker process ended, the
+master process will replace it with a new 'clean' process again. Unicorn is
+designed to be able to replace 'crashed' workers without dropping user
+requests.
+
+This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The
+master process has PID 56227 below.
+
+```
+[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing
+[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10
+[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538
+[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready
+```
+
+### Tunables
+
+The main tunables for Unicorn are the number of worker processes and the
+request timeout after which the Unicorn master terminates a worker process.
+See the [omnibus-gitlab Unicorn settings
+documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md)
+if you want to adjust these settings.
+
+## unicorn-worker-killer
+
+GitLab has memory leaks. These memory leaks manifest themselves in long-running
+processes, such as Unicorn workers. (The Unicorn master process is not known to
+leak memory, probably because it does not handle user requests.)
+
+To make these memory leaks manageable, GitLab comes with the
+[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This
+gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn
+workers to do a memory self-check after every 16 requests. If the memory of the
+Unicorn worker exceeds a pre-set limit then the worker process exits. The
+Unicorn master then automatically replaces the worker process.
+
+This is a robust way to handle memory leaks: Unicorn is designed to handle
+workers that 'crash' so no user requests will be dropped. The
+unicorn-worker-killer gem is designed to only terminate a worker process _in
+between requests_, so no user requests are affected.
+
+This is what a Unicorn worker memory restart looks like in unicorn_stderr.log.
+You see that worker 4 (PID 125918) is inspecting itself and decides to exit.
+The threshold memory value was 254802235 bytes, about 250MB. With GitLab this
+threshold is a random value between 200 and 250 MB. The master process (PID
+117565) then reaps the worker process and spawns a new 'worker 4' with PID
+127549.
+
+```
+[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes)
+[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1)
+[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4
+[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549
+[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready
+```
+
+One other thing that stands out in the log snippet above, taken from
+Gitlab.com, is that 'worker 4' was serving requests for only 23 seconds. This
+is a normal value for our current GitLab.com setup and traffic.
+
+The high frequency of Unicorn memory restarts on some GitLab sites can be a
+source of confusion for administrators. Usually they are a [red
+herring](http://en.wikipedia.org/wiki/Red_herring).
diff --git a/doc/profile/2fa.png b/doc/profile/2fa.png
new file mode 100644
index 00000000000..bbf415210d5
--- /dev/null
+++ b/doc/profile/2fa.png
Binary files differ
diff --git a/doc/profile/2fa_auth.png b/doc/profile/2fa_auth.png
new file mode 100644
index 00000000000..4a4fbe68984
--- /dev/null
+++ b/doc/profile/2fa_auth.png
Binary files differ
diff --git a/doc/profile/README.md b/doc/profile/README.md
new file mode 100644
index 00000000000..6f8359d87fa
--- /dev/null
+++ b/doc/profile/README.md
@@ -0,0 +1,4 @@
+# Profile Settings
+
+- [Preferences](preferences.md)
+- [Two-factor Authentication (2FA)](two_factor_authentication.md)
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
new file mode 100644
index 00000000000..ce5f1936782
--- /dev/null
+++ b/doc/profile/preferences.md
@@ -0,0 +1,32 @@
+# Profile Preferences
+
+Settings in the **Profile > Preferences** page allow the user to customize
+various aspects of the site to their liking.
+
+## Application theme
+
+Changing this setting allows the user to customize the color scheme used for the
+navigation bar on the left side of the screen.
+
+The default is **Charcoal**.
+
+## Syntax highlighting theme
+
+Changing this setting allows the user to customize the theme used when viewing
+syntax highlighted code on the site.
+
+The default is **White**.
+
+## Behavior
+
+### Default Dashboard
+
+For users who have access to a large number of projects but only keep up with a
+select few, the amount of activity on the default Dashboard page can be
+overwhelming.
+
+Changing this setting allows the user to redefine what their default dashboard
+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**.
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
new file mode 100644
index 00000000000..fb215c8b269
--- /dev/null
+++ b/doc/profile/two_factor_authentication.md
@@ -0,0 +1,67 @@
+# Two-factor Authentication (2FA)
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+GitLab account. Once enabled, in addition to supplying your username and
+password to login, you'll be prompted for a code generated by an application on
+your phone.
+
+By enabling 2FA, the only way someone other than you can log into your account
+is to know your username and password *and* have access to your phone.
+
+## Enabling 2FA
+
+**In GitLab:**
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Enable Two-factor Authentication**.
+
+![Two-factor setup](2fa.png)
+
+**On your phone:**
+
+1. Install a compatible application. We recommend [Google Authenticator]
+\(proprietary\) or [FreeOTP] \(open source\).
+1. In the application, add a new entry in one of two ways:
+ * Scan the code with your phone's camera to add the entry automatically.
+ * Enter the details provided to add the entry manually.
+
+**In GitLab:**
+
+1. Enter the six-digit pin number from the entry on your phone into the **Pin
+ code** field.
+1. Click **Submit**.
+
+If the pin you entered was correct, you'll see a message indicating that
+Two-factor Authentication has been enabled, and you'll be presented with a list
+of recovery codes.
+
+## Recovery Codes
+
+Should you ever lose access to your phone, you can use one of the ten provided
+backup codes to login to your account. We suggest copying or printing them for
+storage in a safe place. **Each code can be used only once** to log in to your
+account.
+
+If you lose the recovery codes or just want to generate new ones, you can do so
+from the **Profile Settings** > **Account** page where you first enabled 2FA.
+
+## Logging in with 2FA Enabled
+
+Logging in with 2FA enabled is only slightly different than a normal login.
+Enter your username and password credentials as you normally would, and you'll
+be presented with a second prompt for an authentication code. Enter the pin from
+your phone's application or a recovery code to log in.
+
+![Two-factor authentication on sign in](2fa_auth.png)
+
+## Disabling 2FA
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Disable Two-factor Authentication**.
+
+[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
+[FreeOTP]: https://fedorahosted.org/freeotp/
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
index 780a45bca20..9875bebf66b 100644
--- a/doc/project_services/irker.md
+++ b/doc/project_services/irker.md
@@ -4,7 +4,7 @@ GitLab provides a way to push update messages to an Irker server. When
configured, pushes to a project will trigger the service to send data directly
to the Irker server.
-See the project homepage for further info: http://www.catb.org/esr/irker/
+See the project homepage for further info: https://gitlab.com/esr/irker
## Needed setup
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index bca4fcfb404..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.
```
@@ -299,3 +299,26 @@ 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.
+
+## 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 41a994f3f68..69171cd1765 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -47,7 +47,6 @@ Git: /usr/bin/git
Runs the following rake tasks:
-- `gitlab:env:check`
- `gitlab:gitlab_shell:check`
- `gitlab:sidekiq:check`
- `gitlab:app:check`
@@ -147,7 +146,7 @@ Do you want to continue (yes/no)? yes
## Clear redis cache
-If for some reason the dashboard shows wrong information you might want to
+If for some reason the dashboard shows wrong information you might want to
clear Redis' cache.
For Omnibus-packages:
@@ -166,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 eb97f3cd7f6..7fb22938690 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -30,17 +30,15 @@ All steps from issue template are explained below
```
Xth: (7 working days before the 22nd)
-- [ ] Update the CE changelog (#LINK)
-- [ ] Update the EE changelog (#LINK)
-- [ ] Update the CI changelog (#LINK)
- [ ] Triage the omnibus-gitlab milestone
Xth: (6 working days before the 22nd)
- [ ] Merge CE master in to EE master via merge request (#LINK)
- [ ] Determine QA person and notify this person
-- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary
+- [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary
- [ ] Create CE, EE, CI RC1 versions (#LINK)
+- [ ] Build RC1 packages (EE first) (#LINK)
Xth: (5 working days before the 22nd)
@@ -53,7 +51,9 @@ Xth: (4 working days before the 22nd)
- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
- [ ] Create regression issues (CE, CI) (#LINK)
-- [ ] Tweet about rc1 (#LINK)
+- [ ] Tweet about rc1 (#LINK), proposed text:
+
+> GitLab x.x.0.rc1 is available https://packages.gitlab.com/gitlab/unstable Use at your own risk. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
Xth: (3 working days before the 22nd)
@@ -61,7 +61,7 @@ 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)
@@ -71,9 +71,14 @@ Xth: (1 working day before the 22nd)
- [ ] Update GitLab.com with the stable version (#LINK)
- [ ] Update ci.gitLab.com with the stable version (#LINK)
-22nd:
+22nd before 12AM CET:
+
+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)
+- [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST
```
@@ -137,7 +142,8 @@ Tweet about the RC release:
## Prepare the blog post
-1. Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
+1. The blog post template for this release should already exist and might have comments that were added during the month.
+1. Fill out as much of the blog post template as you can.
1. Make sure the blog post contains information about the GitLab CI release.
1. Check the changelog of CE and EE for important changes.
1. Also check the CI changelog
@@ -150,6 +156,7 @@ Tweet about the RC release:
1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
1. Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor)
1. Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)'
+1. Create a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) for the release after this.
## Create CE, EE, CI stable versions
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.11.md b/doc/update/6.x-or-7.x-to-7.12.md
index b1daa648f1f..5705fb360db 100644
--- a/doc/update/6.x-or-7.x-to-7.11.md
+++ b/doc/update/6.x-or-7.x-to-7.12.md
@@ -1,7 +1,7 @@
-# From 6.x or 7.x to 7.11
-*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.11.md) for the most up to date instructions.*
+# 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.*
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.11.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.12.
## 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-11-stable
+sudo -u git -H git checkout 7-12-stable
```
OR
@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-11-stable-ee
+sudo -u git -H git checkout 7-12-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-11-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-12-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-11-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-11-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-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-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,10 +182,15 @@ 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-11-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-11-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-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.
* 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).
+
## 9. Start application
sudo service gitlab start
diff --git a/doc/update/7.11-to-7.12.md b/doc/update/7.11-to-7.12.md
new file mode 100644
index 00000000000..cc14a135926
--- /dev/null
+++ b/doc/update/7.11-to-7.12.md
@@ -0,0 +1,129 @@
+# From 7.11 to 7.12
+
+### 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-12-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-12-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-11-stable:config/gitlab.yml.example origin/7-12-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.11)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.10 to 7.11](7.10-to-7.11.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/workflow/README.md b/doc/workflow/README.md
index b90a6a50af9..f1959d30139 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,16 +1,15 @@
# Workflow
-- [Feature branch workflow](workflow.md)
-- [Project forking workflow](forking_workflow.md)
-- [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md)
+- [Change your time zone](timezone.md)
+- [Feature branch workflow](workflow.md)
+- [GitLab Flow](gitlab_flow.md)
- [Groups](groups.md)
+- [Keyboard shortcuts](shortcuts.md)
- [Labels](labels.md)
-- [GitLab Flow](gitlab_flow.md)
- [Notifications](notifications.md)
-- [Migrating from SVN to GitLab](migrating_from_svn.md)
-- [Project importing from GitHub to GitLab](import_projects_from_github.md)
-- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md)
+- [Project Features](project_features.md)
+- [Project forking workflow](forking_workflow.md)
- [Protected branches](protected_branches.md)
-- [Change your time zone](timezone.md)
-- [Web Editor](web_editor.md) \ No newline at end of file
+- [Web Editor](web_editor.md)
+- ["Work In Progress" Merge Requests](wip_merge_requests.md)
diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md
deleted file mode 100644
index 8644b4ffc73..00000000000
--- a/doc/workflow/import_projects_from_github.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Project importing from GitHub to GitLab
-
-You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if
-GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
-To get to the importer page you need to go to "New project" page.
-
-![New project page](github_importer/new_project_page.png)
-
-Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
-
-![Importer page](github_importer/importer.png)
-
-To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data. \ No newline at end of file
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
new file mode 100644
index 00000000000..19395657719
--- /dev/null
+++ b/doc/workflow/importing/README.md
@@ -0,0 +1,9 @@
+# Migrating projects to a GitLab instance
+
+1. [Bitbucket](import_projects_from_bitbucket.md)
+2. [GitHub](import_projects_from_github.md)
+3. [GitLab.com](import_projects_from_gitlab_com.md)
+4. [SVN](migrating_from_svn.md)
+
+### Note
+* If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported. \ No newline at end of file
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png
new file mode 100644
index 00000000000..df55a081803
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png
new file mode 100644
index 00000000000..5253889d251
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png
new file mode 100644
index 00000000000..ffa87ce5b2e
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png
new file mode 100644
index 00000000000..0e08703f421
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png
Binary files differ
diff --git a/doc/workflow/github_importer/importer.png b/doc/workflow/importing/github_importer/importer.png
index 57636717571..57636717571 100644
--- a/doc/workflow/github_importer/importer.png
+++ b/doc/workflow/importing/github_importer/importer.png
Binary files differ
diff --git a/doc/workflow/github_importer/new_project_page.png b/doc/workflow/importing/github_importer/new_project_page.png
index 002f22d81d7..002f22d81d7 100644
--- a/doc/workflow/github_importer/new_project_page.png
+++ b/doc/workflow/importing/github_importer/new_project_page.png
Binary files differ
diff --git a/doc/workflow/gitlab_importer/importer.png b/doc/workflow/importing/gitlab_importer/importer.png
index d2a286d8cac..d2a286d8cac 100644
--- a/doc/workflow/gitlab_importer/importer.png
+++ b/doc/workflow/importing/gitlab_importer/importer.png
Binary files differ
diff --git a/doc/workflow/gitlab_importer/new_project_page.png b/doc/workflow/importing/gitlab_importer/new_project_page.png
index 5e239208e1e..5e239208e1e 100644
--- a/doc/workflow/gitlab_importer/new_project_page.png
+++ b/doc/workflow/importing/gitlab_importer/new_project_page.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md
new file mode 100644
index 00000000000..1e9825e2e10
--- /dev/null
+++ b/doc/workflow/importing/import_projects_from_bitbucket.md
@@ -0,0 +1,26 @@
+# Import your project from Bitbucket to GitLab
+
+It takes just a few steps to import your existing Bitbucket projects to GitLab. But keep in mind that it is possible only if Bitbucket support is enabled on your GitLab instance. You can read more about Bitbucket support [here](doc/integration/bitbucket.md).
+
+* Sign in to GitLab.com and go to your dashboard
+
+* Click on "New project"
+
+![New project in GitLab](bitbucket_importer/bitbucket_import_new_project.png)
+
+* Click on the "Bitbucket" button
+
+![Bitbucket](bitbucket_importer/bitbucket_import_select_bitbucket.png)
+
+* Grant GitLab access to your Bitbucket account
+
+![Grant access](bitbucket_importer/bitbucket_import_grant_access.png)
+
+* Click on the projects that you'd like to import or "Import all projects"
+
+![Import projects](bitbucket_importer/bitbucket_import_select_project.png)
+
+A new GitLab project will be created with your imported data.
+
+### Note
+Milestones and wiki pages are not imported from Bitbucket.
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
new file mode 100644
index 00000000000..3efa92cb868
--- /dev/null
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -0,0 +1,20 @@
+# Import your project from GitHub to GitLab
+
+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.
+
+![New project page](github_importer/new_project_page.png)
+
+* Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+![Importer page](github_importer/importer.png)
+
+* To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
+
+### 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.
diff --git a/doc/workflow/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index f4c4e955d46..f4c4e955d46 100644
--- a/doc/workflow/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 485db4834e9..485db4834e9 100644
--- a/doc/workflow/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
new file mode 100644
index 00000000000..ffcb832cdd7
--- /dev/null
+++ b/doc/workflow/shortcuts.md
@@ -0,0 +1,5 @@
+# GitLab keyboard shortcuts
+
+You can see GitLab's keyboard shortcuts by using 'shift + ?'
+
+![Shortcuts](shortcuts.png) \ No newline at end of file
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
new file mode 100644
index 00000000000..68756ed1f98
--- /dev/null
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index b513088dbc5..7e08c0e51ac 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -1,10 +1,22 @@
# Changing your time zone
-GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in config/application.rb.
+The global time zone configuration parameter can be changed in `config/gitlab.yml`:
+```
+ # time_zone: 'UTC'
+```
+
+Uncomment and customize if you want to change the default time zone of GitLab application.
+
+To see all available time zones, run `bundle exec rake time:zones:all`.
+
+
+## Changing time zone in omnibus installations
+
+GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in `/etc/gitlab/gitlab.rb`.
To update, add the time zone that best applies to your location. Here are two examples:
```
-gitlab_rails['time_zone'] = 'America/New_York'
+gitlab_rails['time_zone'] = 'America/New_York'
```
or
```
@@ -15,4 +27,4 @@ After you added this field, reconfigure and restart:
```
gitlab-ctl reconfigure
gitlab-ctl restart
-``` \ No newline at end of file
+```
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
new file mode 100644
index 00000000000..46035a5e6b6
--- /dev/null
+++ b/doc/workflow/wip_merge_requests.md
@@ -0,0 +1,13 @@
+# "Work In Progress" Merge Requests
+
+To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**.
+
+![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png)
+
+To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`.
+
+![Mark as WIP](wip_merge_requests/mark_as_wip.png)
+
+To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix.
+
+![Unark as WIP](wip_merge_requests/unmark_as_wip.png)
diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png
new file mode 100644
index 00000000000..4791e5de972
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png
new file mode 100644
index 00000000000..8fa83a201ac
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/mark_as_wip.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png
new file mode 100644
index 00000000000..d45e68f31c5
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png
Binary files differ
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/README.md b/docker/README.md
index 2e533ae9dd5..9507aa6a63c 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -13,7 +13,7 @@ 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 login to the web interface with username `root` and password `5iveL!fe`.
+You can login to the web interface with username `root` and password `password`.
Next time, you can just use docker start and stop to run the container.
@@ -80,7 +80,7 @@ sudo docker pull sytse/gitlab-app:7.10.1
```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 run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1
```
After this you can login to the web interface as explained above in 'After starting a container'.
@@ -94,7 +94,12 @@ sudo docker build --tag gitlab-data docker/data/
sudo docker build --tag gitlab-app:7.10.1 docker/app/
```
-After this run the images as described in the previous section.
+After this run the images:
+
+```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
+```
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.
@@ -107,7 +112,7 @@ The directories on data container are:
### Configure GitLab
-These container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+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:
@@ -122,7 +127,7 @@ You can find all available options in [Omnibus GitLab documentation](https://git
### Upgrade GitLab with app and data images
-To updgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
@@ -130,7 +135,7 @@ It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated
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 --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
```
On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup the app image:
@@ -141,15 +146,17 @@ sudo docker rmi gitlab-app:7.8.1
### Publish images to Dockerhub
-Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name):
+- 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:7.10.1 sytse/gitlab-app:7.10.1
+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 commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-data sytse/gitlab-data
+sudo docker push sytse/gitlab-data
```
## 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.
diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature
index 9df47eb51fd..33439cd1e85 100644
--- a/features/admin/deploy_keys.feature
+++ b/features/admin/deploy_keys.feature
@@ -8,11 +8,6 @@ Feature: Admin Deploy Keys
When I visit admin deploy keys page
Then I should see all public deploy keys
- Scenario: Deploy Keys show
- When I visit admin deploy keys page
- And I click on first deploy key
- Then I should see deploy key details
-
Scenario: Deploy Keys new
When I visit admin deploy keys page
And I click 'New Deploy Key'
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/dashboard/group.feature b/features/dashboard/group.feature
index cf4b8d7283b..e3c01db2ebb 100644
--- a/features/dashboard/group.feature
+++ b/features/dashboard/group.feature
@@ -24,7 +24,8 @@ Feature: Dashboard Group
When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
- Then I should not see the "Leave" button for group "Owned"
+ When I click on the "Leave" button for group "Owned"
+ Then I should see the "Can not leave message"
@javascript
Scenario: Guest should be able to leave from group
diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature
index 7801ae5b8ca..1fa4ac88ddc 100644
--- a/features/profile/active_tab.feature
+++ b/features/profile/active_tab.feature
@@ -18,9 +18,9 @@ Feature: Profile Active Tab
Then the active main tab should be SSH Keys
And no other main tabs should be active
- Scenario: On Profile Design
- Given I visit profile design page
- Then the active main tab should be Design
+ Scenario: On Profile Preferences
+ Given I visit profile preferences page
+ Then the active main tab should be Preferences
And no other main tabs should be active
Scenario: On Profile History
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d586167cdf5..0dd0afde8b1 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -84,16 +84,3 @@ Feature: Profile
Then I visit profile applications page
And I click to remove application
Then I see that application is removed
-
- @javascript
- Scenario: I change my application theme
- Given I visit profile design page
- When I change my application theme
- Then I should see the theme change immediately
- And I should receive feedback that the changes were saved
-
- @javascript
- Scenario: I change my code preview theme
- Given I visit profile design page
- When I change my code preview theme
- Then I should receive feedback that the changes were saved
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 05faad4e645..8661ea98c20 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -35,6 +35,11 @@ Feature: Project Active Tab
Then the active main tab should be Merge Requests
And no other main tabs should be active
+ Scenario: On Project Members
+ Given I visit my project's members page
+ Then the active main tab should be Members
+ And no other main tabs should be active
+
Scenario: On Project Wiki
Given I visit my project's wiki page
Then the active main tab should be Wiki
@@ -49,13 +54,6 @@ Feature: Project Active Tab
# Sub Tabs: Settings
- Scenario: On Project Settings/Team
- Given I visit my project's settings page
- And I click the "Team" tab
- Then the active sub nav should be Team
- And no other sub navs should be active
- And the active main tab should be Settings
-
Scenario: On Project Settings/Edit
Given I visit my project's settings page
And I click the "Edit" tab
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/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index 56b9a13678d..4a2b870e082 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -77,3 +77,17 @@ Feature: Project Commits Diff Comments
And I submit the diff comment
Then I should not see the diff comment form
And I should see a discussion reply button
+
+ @javascript
+ Scenario: I can add a comment on a side-by-side commit diff (left side)
+ Given I open a diff comment form
+ And I click side-by-side diff button
+ When I leave a diff comment in a parallel view on the left side like "Old comment"
+ Then I should see a diff comment on the left side saying "Old comment"
+
+ @javascript
+ Scenario: I can add a comment on a side-by-side commit diff (right side)
+ Given I open a diff comment form
+ And I click side-by-side diff button
+ When I leave a diff comment in a parallel view on the right side like "New comment"
+ Then I should see a diff comment on the right side saying "New comment"
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..a15298fc452 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -184,3 +184,15 @@ 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 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/merge_requests.feature b/features/project/merge_requests.feature
index 60caf783fe4..947f668e432 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -10,7 +10,7 @@ Feature: Project Merge Requests
Then I should see "Bug NS-04" in merge requests
And I should not see "Feature NS-03" in merge requests
- Scenario: I should see closed merge requests
+ Scenario: I should see rejected merge requests
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
@@ -207,3 +219,11 @@ Feature: Project Merge Requests
Then I should see that I am subscribed
When I click button "Unsubscribe"
Then I should see that I am unsubscribed
+
+ @javascript
+ Scenario: I can change the target branch
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ When I click the "Target branch" dropdown
+ And I select a new target branch
+ Then I should see new target branch changes
diff --git a/features/project/project.feature b/features/project/project.feature
index ef11bceed11..56ae5c78d01 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -68,3 +68,8 @@ Feature: Project
When I visit project "Shop" page
Then I should not see "New Issue" button
And I should not see "New Merge Request" button
+
+ Scenario: I should not see Project snippets
+ Given I disable snippets in project
+ When I visit project "Shop" page
+ Then I should not see "Snippets" button
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 977cd609a11..2ebfa3c1660 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -70,6 +70,11 @@ Feature: Project Wiki
Then I should see non-escaped link in the pages list
@javascript
+ Scenario: Creating an invalid new page
+ Given I create a New page with an invalid name
+ Then I should see an error message
+
+ @javascript
Scenario: Edit Wiki page that has a path
Given I create a New page with paths
And I click on the "Pages" button
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/applications.rb b/features/steps/admin/applications.rb
index d59088fa3c3..7c12cb96921 100644
--- a/features/steps/admin/applications.rb
+++ b/features/steps/admin/applications.rb
@@ -8,7 +8,7 @@ class Spinach::Features::AdminApplications < Spinach::FeatureSteps
end
step 'I should see application form' do
- page.should have_content "New application"
+ expect(page).to have_content "New application"
end
step 'I fill application form out and submit' do
@@ -18,9 +18,9 @@ class Spinach::Features::AdminApplications < Spinach::FeatureSteps
end
step 'I see application' do
- page.should have_content "Application: test"
- page.should have_content "Application Id"
- page.should have_content "Secret"
+ expect(page).to have_content "Application: test"
+ expect(page).to have_content "Application Id"
+ expect(page).to have_content "Secret"
end
step 'I click edit' do
@@ -28,28 +28,28 @@ class Spinach::Features::AdminApplications < Spinach::FeatureSteps
end
step 'I see edit application form' do
- page.should have_content "Edit application"
+ expect(page).to have_content "Edit application"
end
step 'I change name of application and submit' do
- page.should have_content "Edit application"
+ expect(page).to have_content "Edit application"
fill_in :doorkeeper_application_name, with: 'test_changed'
click_on "Submit"
end
step 'I see that application was changed' do
- page.should have_content "test_changed"
- page.should have_content "Application Id"
- page.should have_content "Secret"
+ expect(page).to have_content "test_changed"
+ expect(page).to have_content "Application Id"
+ expect(page).to have_content "Secret"
end
step 'I click to remove application' do
- within '.oauth-applications' do
+ page.within '.oauth-applications' do
click_on "Destroy"
end
end
step "I see that application is removed" do
- page.find(".oauth-applications").should_not have_content "test_changed"
+ expect(page.find(".oauth-applications")).not_to have_content "test_changed"
end
end
diff --git a/features/steps/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb
index a35fa34a3a2..f6daf852977 100644
--- a/features/steps/admin/broadcast_messages.rb
+++ b/features/steps/admin/broadcast_messages.rb
@@ -8,7 +8,7 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
end
step 'I should be all broadcast messages' do
- page.should have_content "Migration to new server"
+ expect(page).to have_content "Migration to new server"
end
step 'submit form with new broadcast message' do
@@ -18,11 +18,11 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
end
step 'I should be redirected to admin messages page' do
- current_path.should == admin_broadcast_messages_path
+ expect(current_path).to eq admin_broadcast_messages_path
end
step 'I should see newly created broadcast message' do
- page.should have_content 'Application update from 4:00 CST to 5:00 CST'
+ expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST'
end
step 'submit form with new customized broadcast message' do
@@ -35,7 +35,7 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
end
step 'I should see a customized broadcast message' do
- page.should have_content 'Application update from 4:00 CST to 5:00 CST'
- page.should have_selector %(div[style="background-color:#f2dede;color:#b94a48"])
+ 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"])
end
end
diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
index fb0b611762e..56787eeb6b3 100644
--- a/features/steps/admin/deploy_keys.rb
+++ b/features/steps/admin/deploy_keys.rb
@@ -10,21 +10,10 @@ class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
step 'I should see all public deploy keys' do
DeployKey.are_public.each do |p|
- page.should have_content p.title
+ expect(page).to have_content p.title
end
end
- step 'I click on first deploy key' do
- click_link DeployKey.are_public.first.title
- end
-
- step 'I should see deploy key details' do
- deploy_key = DeployKey.are_public.first
- current_path.should == admin_deploy_key_path(deploy_key)
- page.should have_content(deploy_key.title)
- page.should have_content(deploy_key.key)
- end
-
step 'I visit admin deploy key page' do
visit admin_deploy_key_path(deploy_key)
end
@@ -44,11 +33,11 @@ class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
end
step 'I should be on admin deploy keys page' do
- current_path.should == admin_deploy_keys_path
+ expect(current_path).to eq admin_deploy_keys_path
end
step 'I should see newly created deploy key' do
- page.should have_content(deploy_key.title)
+ expect(page).to have_content(deploy_key.title)
end
def deploy_key
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 721460b9371..9cc74a97c3a 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -28,32 +28,32 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
step 'I should see newly created group' do
- page.should have_content "Group: gitlab"
- page.should have_content "Group description"
+ expect(page).to have_content "Group: gitlab"
+ expect(page).to have_content "Group description"
end
step 'I should be redirected to group page' do
- current_path.should == admin_group_path(Group.find_by(path: 'gitlab'))
+ expect(current_path).to eq admin_group_path(Group.find_by(path: 'gitlab'))
end
When 'I select user "John Doe" from user list as "Reporter"' do
select2(user_john.id, from: "#user_ids", multiple: true)
- within "#new_project_member" do
+ page.within "#new_project_member" do
select "Reporter", from: "access_level"
end
click_button "Add users to group"
end
step 'I should see "John Doe" in team list in every project as "Reporter"' do
- within ".group-users-list" do
- page.should have_content "John Doe"
- page.should have_content "Reporter"
+ page.within ".group-users-list" do
+ expect(page).to have_content "John Doe"
+ expect(page).to have_content "Reporter"
end
end
step 'I should be all groups' do
Group.all.each do |group|
- page.should have_content group.name
+ expect(page).to have_content group.name
end
end
@@ -62,14 +62,14 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
step 'I remove user "John Doe" from group' do
- within "#user_#{user_john.id}" do
+ page.within "#user_#{user_john.id}" do
click_link 'Remove user from group'
end
end
step 'I should not see "John Doe" in team list' do
- within ".group-users-list" do
- page.should_not have_content "John Doe"
+ page.within ".group-users-list" do
+ expect(page).not_to have_content "John Doe"
end
end
diff --git a/features/steps/admin/logs.rb b/features/steps/admin/logs.rb
index 904e5468655..f9e49588c75 100644
--- a/features/steps/admin/logs.rb
+++ b/features/steps/admin/logs.rb
@@ -4,8 +4,8 @@ class Spinach::Features::AdminLogs < Spinach::FeatureSteps
include SharedAdmin
step 'I should see tabs with available logs' do
- page.should have_content 'production.log'
- page.should have_content 'githost.log'
- page.should have_content 'application.log'
+ expect(page).to have_content 'production.log'
+ expect(page).to have_content 'githost.log'
+ expect(page).to have_content 'application.log'
end
end
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 9be4d39d2d5..655f1895279 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -5,7 +5,7 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
step 'I should see all projects' do
Project.all.each do |p|
- page.should have_content p.name_with_namespace
+ expect(page).to have_content p.name_with_namespace
end
end
@@ -15,9 +15,9 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
step 'I should see project details' do
project = Project.first
- current_path.should == admin_namespace_project_path(project.namespace, project)
- page.should have_content(project.name_with_namespace)
- page.should have_content(project.creator.name)
+ expect(current_path).to eq admin_namespace_project_path(project.namespace, project)
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).to have_content(project.creator.name)
end
step 'I visit admin project page' do
@@ -34,8 +34,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
end
step 'I should see project transfered' do
- page.should have_content 'Web / ' + project.name
- page.should have_content 'Namespace: Web'
+ expect(page).to have_content 'Web / ' + project.name
+ expect(page).to have_content 'Namespace: Web'
end
def project
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 15ca0d80f1a..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
- current_application_settings.gravatar_enabled.should be_false
- current_application_settings.home_page_url.should == 'https://about.gitlab.com/'
- page.should have_content 'Application settings saved successfully'
+ 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
@@ -41,18 +41,18 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
end
step 'I should see service template settings saved' do
- page.should have_content 'Application settings saved successfully'
+ expect(page).to have_content 'Application settings saved successfully'
end
step 'I should see all checkboxes checked' do
- all('input[type=checkbox]').each do |checkbox|
- checkbox.should be_checked
+ page.all('input[type=checkbox]').each do |checkbox|
+ expect(checkbox).to be_checked
end
end
step 'I should see Slack settings saved' do
- find_field('Webhook').value.should eq 'http://localhost'
- find_field('Username').value.should eq 'test_user'
- find_field('Channel').value.should eq '#test_channel'
+ expect(find_field('Webhook').value).to eq 'http://localhost'
+ expect(find_field('Username').value).to eq 'test_user'
+ expect(find_field('Channel').value).to eq '#test_channel'
end
end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index e1383097248..6c4b91586d6 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -5,7 +5,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
step 'I should see all users' do
User.all.each do |user|
- page.should have_content user.name
+ expect(page).to have_content user.name
end
end
@@ -23,13 +23,13 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'See username error message' do
- within "#error_explanation" do
- page.should have_content "Username"
+ page.within "#error_explanation" do
+ expect(page).to have_content "Username"
end
end
step 'Not changed form action url' do
- page.should have_selector %(form[action="/admin/users/#{@user.username}"])
+ expect(page).to have_selector %(form[action="/admin/users/#{@user.username}"])
end
step 'I submit modified user' do
@@ -38,7 +38,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I see user attributes changed' do
- page.should have_content 'Can create groups: Yes'
+ expect(page).to have_content 'Can create groups: Yes'
end
step 'click edit on my user' do
@@ -53,7 +53,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I see the secondary email' do
- page.should have_content "Secondary email: #{@user_with_secondary_email.emails.last.email}"
+ expect(page).to have_content "Secondary email: #{@user_with_secondary_email.emails.last.email}"
end
step 'I click remove secondary email' do
@@ -61,7 +61,7 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I should not see secondary email anymore' do
- page.should_not have_content "Secondary email:"
+ expect(page).not_to have_content "Secondary email:"
end
step 'user "Mike" with groups and projects' do
@@ -79,8 +79,8 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I should see user "Mike" details' do
- page.should have_content 'Account'
- page.should have_content 'Personal projects limit'
+ expect(page).to have_content 'Account'
+ expect(page).to have_content 'Personal projects limit'
end
step 'user "Pete" with ssh keys' do
@@ -94,8 +94,8 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I should see key list' do
- page.should have_content 'ssh-rsa Key2'
- page.should have_content 'ssh-rsa Key1'
+ expect(page).to have_content 'ssh-rsa Key2'
+ expect(page).to have_content 'ssh-rsa Key1'
end
step 'I click on the key title' do
@@ -103,8 +103,8 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I should see key details' do
- page.should have_content 'ssh-rsa Key2'
- page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
+ expect(page).to have_content 'ssh-rsa Key2'
+ expect(page).to have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
end
step 'I click on remove key' do
@@ -112,6 +112,47 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
end
step 'I should see the key removed' do
- page.should_not have_content 'ssh-rsa Key2'
+ 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/archived_projects.rb b/features/steps/dashboard/archived_projects.rb
index 969baf92287..36e092f50c6 100644
--- a/features/steps/dashboard/archived_projects.rb
+++ b/features/steps/dashboard/archived_projects.rb
@@ -9,14 +9,14 @@ class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps
end
step 'I should see "Shop" project link' do
- page.should have_link "Shop"
+ expect(page).to have_link "Shop"
end
step 'I should not see "Forum" project link' do
- page.should_not have_link "Forum"
+ expect(page).not_to have_link "Forum"
end
step 'I should see "Forum" project link' do
- page.should have_link "Forum"
+ expect(page).to have_link "Forum"
end
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 8508b2a8096..cb3a80cac29 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -4,16 +4,16 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
include SharedProject
step 'I should see "New Project" link' do
- page.should have_link "New project"
+ expect(page).to have_link "New project"
end
step 'I should see "Shop" project link' do
- page.should have_link "Shop"
+ expect(page).to have_link "Shop"
end
step 'I should see last push widget' do
- page.should have_content "You pushed to fix"
- page.should have_link "Create Merge Request"
+ expect(page).to have_content "You pushed to fix"
+ expect(page).to have_link "Create Merge Request"
end
step 'I click "Create Merge Request" link' do
@@ -21,14 +21,14 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page' do
- current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project)
- find("#merge_request_target_project_id").value.should == @project.id.to_s
- find("#merge_request_source_branch").value.should == "fix"
- find("#merge_request_target_branch").value.should == "master"
+ expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
+ expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
+ expect(find("input#merge_request_source_branch").value).to eq "fix"
+ expect(find("input#merge_request_target_branch").value).to eq "master"
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,
@@ -38,7 +38,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I should see "John Doe joined project Shop" event' do
- page.should have_content "John Doe joined project #{project.name_with_namespace}"
+ expect(page).to have_content "John Doe joined project #{project.name_with_namespace}"
end
step 'user with name "John Doe" left project "Shop"' do
@@ -51,7 +51,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I should see "John Doe left project Shop" event' do
- page.should have_content "John Doe left project #{project.name_with_namespace}"
+ expect(page).to have_content "John Doe left project #{project.name_with_namespace}"
end
step 'I have group with projects' do
@@ -64,13 +64,13 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I should see projects list' do
@user.authorized_projects.all.each do |project|
- page.should have_link project.name_with_namespace
+ expect(page).to have_link project.name_with_namespace
end
end
step 'I should see groups list' do
Group.all.each do |group|
- page.should have_link group.name
+ expect(page).to have_link group.name
end
end
@@ -80,6 +80,6 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I should see 1 project at group list' do
- find('span.last_activity/span').should have_content('1')
+ expect(find('span.last_activity/span')).to have_content('1')
end
end
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 3da3d62d0c0..726b37cfde5 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -4,27 +4,27 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
include SharedProject
step 'I should see push event' do
- page.should have_selector('span.pushed')
+ expect(page).to have_selector('span.pushed')
end
step 'I should not see push event' do
- page.should_not have_selector('span.pushed')
+ expect(page).not_to have_selector('span.pushed')
end
step 'I should see new member event' do
- page.should have_selector('span.joined')
+ expect(page).to have_selector('span.joined')
end
step 'I should not see new member event' do
- page.should_not have_selector('span.joined')
+ expect(page).not_to have_selector('span.joined')
end
step 'I should see merge request event' do
- page.should have_selector('span.accepted')
+ expect(page).to have_selector('span.accepted')
end
step 'I should not see merge request event' do
- page.should_not have_selector('span.accepted')
+ expect(page).not_to have_selector('span.accepted')
end
step 'this project has push event' do
@@ -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/group.rb b/features/steps/dashboard/group.rb
index 8384df2fb59..0c6a0ae3725 100644
--- a/features/steps/dashboard/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -17,29 +17,29 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
end
step 'I should not see the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.fa.fa-sign-out')
+ expect(find(:css, 'li', text: "Owner")).not_to have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for groupr "Guest"' do
- find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.fa.fa-sign-out')
+ expect(find(:css, 'li', text: "Guest")).not_to have_selector(:css, 'i.fa.fa-sign-out')
# poltergeist always confirms popups.
end
step 'I should see group "Owned" in group list' do
- page.should have_content("Owned")
+ expect(page).to have_content("Owned")
end
step 'I should not see group "Owned" in group list' do
- page.should_not have_content("Owned")
+ expect(page).not_to have_content("Owned")
end
step 'I should see group "Guest" in group list' do
- page.should have_content("Guest")
+ expect(page).to have_content("Guest")
end
step 'I should not see group "Guest" in group list' do
- page.should_not have_content("Guest")
+ expect(page).not_to have_content("Guest")
end
step 'I click new group link' do
@@ -53,11 +53,15 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
end
step 'I should be redirected to group "Samurai" page' do
- current_path.should == group_path(Group.find_by(name: 'Samurai'))
+ expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
end
step 'I should see newly created group "Samurai"' do
- page.should have_content "Samurai"
- page.should have_content "Tokugawa Shogunate"
+ expect(page).to have_content "Samurai"
+ expect(page).to have_content "Tokugawa Shogunate"
+ end
+
+ step 'I should see the "Can not leave message"' do
+ expect(page).to have_content "You can not leave Owned group because you're the last owner"
end
end
diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb
index ef433c57c6e..86ab31a58ab 100644
--- a/features/steps/dashboard/help.rb
+++ b/features/steps/dashboard/help.rb
@@ -12,7 +12,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end
step 'I should see "Rake Tasks" page markdown rendered' do
- page.should have_content "Gather information about GitLab and the system it runs on"
+ expect(page).to have_content "Gather information about GitLab and the system it runs on"
end
step 'Header "Rebuild project satellites" should have correct ids and links' do
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 60da36e86de..cbe54e2dc79 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -46,11 +46,11 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
def should_see(issue)
- page.should have_content(issue.title[0..10])
+ expect(page).to have_content(issue.title[0..10])
end
def should_not_see(issue)
- page.should_not have_content(issue.title[0..10])
+ expect(page).not_to have_content(issue.title[0..10])
end
def assigned_issue
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 9d92082bb83..cec8d06adee 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -50,11 +50,11 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
def should_see(merge_request)
- page.should have_content(merge_request.title[0..10])
+ expect(page).to have_content(merge_request.title[0..10])
end
def should_not_see(merge_request)
- page.should_not have_content(merge_request.title[0..10])
+ expect(page).not_to have_content(merge_request.title[0..10])
end
def assigned_merge_request
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 93456a81ecf..d4440c1fb4d 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -4,13 +4,13 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
include SharedProject
step 'I click "New project" link' do
- within('.content') do
+ page.within('.content') do
click_link "New project"
end
end
step 'I see "New project" page' do
- page.should have_content("Project path")
+ expect(page).to have_content('Project path')
end
step 'I click on "Import project from GitHub"' do
@@ -19,11 +19,11 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
step 'I see instructions on how to import from GitHub' do
github_modal = first('.modal-body')
- github_modal.should be_visible
- github_modal.should have_content "To enable importing projects from GitHub"
+ expect(github_modal).to be_visible
+ expect(github_modal).to have_content "To enable importing projects from GitHub"
- all('.modal-body').each do |element|
- element.should_not be_visible unless element == github_modal
+ page.all('.modal-body').each do |element|
+ expect(element).not_to be_visible unless element == github_modal
end
end
end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
index b9ad2f13e29..59c73fe63f2 100644
--- a/features/steps/dashboard/starred_projects.rb
+++ b/features/steps/dashboard/starred_projects.rb
@@ -8,8 +8,8 @@ class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
end
step 'I should not see project "Shop"' do
- within 'aside' do
- page.should_not have_content('Shop')
+ page.within 'aside' do
+ expect(page).not_to have_content('Shop')
end
end
end
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
index 0c2127d4c4b..89b82293ef2 100644
--- a/features/steps/explore/groups.rb
+++ b/features/steps/explore/groups.rb
@@ -39,19 +39,19 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
end
step 'I should not see project "Enterprise" items' do
- page.should_not have_content "Enterprise"
+ expect(page).not_to have_content "Enterprise"
end
step 'I should see project "Internal" items' do
- page.should have_content "Internal"
+ expect(page).to have_content "Internal"
end
step 'I should not see project "Internal" items' do
- page.should_not have_content "Internal"
+ expect(page).not_to have_content "Internal"
end
step 'I should see project "Community" items' do
- page.should have_content "Community"
+ expect(page).to have_content "Community"
end
step 'I change filter to Everyone\'s' do
@@ -59,11 +59,11 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
end
step 'I should see group member "John Doe"' do
- page.should have_content "John Doe"
+ expect(page).to have_content "John Doe"
end
step 'I should not see member roles' do
- body.should_not match(%r{owner|developer|reporter|guest}i)
+ expect(body).not_to match(%r{owner|developer|reporter|guest}i)
end
protected
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 26b71406bd8..8b498e7b4a6 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -4,56 +4,56 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
include SharedProject
step 'I should see project "Empty Public Project"' do
- page.should have_content "Empty Public Project"
+ expect(page).to have_content "Empty Public Project"
end
step 'I should see public project details' do
- page.should have_content '32 branches'
- page.should have_content '16 tags'
+ expect(page).to have_content '32 branches'
+ expect(page).to have_content '16 tags'
end
step 'I should see project readme' do
- page.should have_content 'README.md'
+ expect(page).to have_content 'README.md'
end
step 'I should see empty public project details' do
- page.should have_content 'Git global setup'
+ expect(page).to have_content 'Git global setup'
end
step 'I should see empty public project details with http clone info' do
project = Project.find_by(name: 'Empty Public Project')
- all(:css, '.git-empty .clone').each do |element|
- element.text.should include(project.http_url_to_repo)
+ page.all(:css, '.git-empty .clone').each do |element|
+ expect(element.text).to include(project.http_url_to_repo)
end
end
step 'I should see empty public project details with ssh clone info' do
project = Project.find_by(name: 'Empty Public Project')
- all(:css, '.git-empty .clone').each do |element|
- element.text.should include(project.url_to_repo)
+ page.all(:css, '.git-empty .clone').each do |element|
+ expect(element.text).to include(project.url_to_repo)
end
end
step 'I should see project "Community" home page' do
- within '.navbar-gitlab .title' do
- page.should have_content 'Community'
+ page.within '.navbar-gitlab .title' do
+ expect(page).to have_content 'Community'
end
end
step 'I should see project "Internal" home page' do
- within '.navbar-gitlab .title' do
- page.should have_content 'Internal'
+ page.within '.navbar-gitlab .title' do
+ expect(page).to have_content 'Internal'
end
end
step 'I should see an http link to the repository' do
project = Project.find_by(name: 'Community')
- page.should have_field('project_clone', with: project.http_url_to_repo)
+ expect(page).to have_field('project_clone', with: project.http_url_to_repo)
end
step 'I should see an ssh link to the repository' do
project = Project.find_by(name: 'Community')
- page.should have_field('project_clone', with: project.url_to_repo)
+ expect(page).to have_field('project_clone', with: project.url_to_repo)
end
step 'I visit "Community" issues page' do
@@ -70,9 +70,9 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
step 'I should see list of issues for "Community" project' do
- page.should have_content "Bug"
- page.should have_content public_project.name
- page.should have_content "New feature"
+ expect(page).to have_content "Bug"
+ expect(page).to have_content public_project.name
+ expect(page).to have_content "New feature"
end
step 'I visit "Internal" issues page' do
@@ -89,9 +89,9 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
step 'I should see list of issues for "Internal" project' do
- page.should have_content "Internal Bug"
- page.should have_content internal_project.name
- page.should have_content "New internal feature"
+ expect(page).to have_content "Internal Bug"
+ expect(page).to have_content internal_project.name
+ expect(page).to have_content "New internal feature"
end
step 'I visit "Community" merge requests page' do
@@ -107,8 +107,8 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I should see list of merge requests for "Community" project' do
- page.should have_content public_project.name
- page.should have_content public_merge_request.source_project.name
+ expect(page).to have_content public_project.name
+ expect(page).to have_content public_merge_request.source_project.name
end
step 'I visit "Internal" merge requests page' do
@@ -124,8 +124,8 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I should see list of merge requests for "Internal" project' do
- page.should have_content internal_project.name
- page.should have_content internal_merge_request.source_project.name
+ expect(page).to have_content internal_project.name
+ expect(page).to have_content internal_merge_request.source_project.name
end
def internal_project
@@ -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 228b83e5fd0..2812c5473e9 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -16,7 +16,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
- within ".users-group-form" do
+ page.within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
@@ -25,14 +25,14 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see "Mike" in team list as "Reporter"' do
- within '.well-list' do
- page.should have_content('Mike')
- page.should have_content('Reporter')
+ page.within '.well-list' do
+ expect(page).to have_content('Mike')
+ expect(page).to have_content('Reporter')
end
end
step 'I select "sjobs@apple.com" as "Reporter"' do
- within ".users-group-form" do
+ page.within ".users-group-form" do
select2("sjobs@apple.com", from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
@@ -41,39 +41,39 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
- within '.well-list' do
- page.should have_content('sjobs@apple.com')
- page.should have_content('invited')
- page.should have_content('Reporter')
+ page.within '.well-list' do
+ expect(page).to have_content('sjobs@apple.com')
+ expect(page).to have_content('invited')
+ expect(page).to have_content('Reporter')
end
end
step 'I should see group "Owned" projects list' do
Group.find_by(name: "Owned").projects.each do |project|
- page.should have_link project.name
+ expect(page).to have_link project.name
end
end
step 'I should see projects activity feed' do
- page.should have_content 'closed issue'
+ expect(page).to have_content 'closed issue'
end
step 'I should see issues from group "Owned" assigned to me' do
assigned_to_me(:issues).each do |issue|
- page.should have_content issue.title
+ expect(page).to have_content issue.title
end
end
step 'I should see merge requests from group "Owned" assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
- page.should have_content issue.title[0..80]
+ expect(page).to have_content issue.title[0..80]
end
end
step 'I select user "Mary Jane" from list with role "Reporter"' do
user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
click_button 'Add members'
- within ".users-group-form" do
+ page.within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
@@ -82,22 +82,22 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see user "John Doe" in team list' do
projects_with_access = find(".panel .well-list")
- projects_with_access.should have_content("John Doe")
+ expect(projects_with_access).to have_content("John Doe")
end
step 'I should not see user "John Doe" in team list' do
projects_with_access = find(".panel .well-list")
- projects_with_access.should_not have_content("John Doe")
+ expect(projects_with_access).not_to have_content("John Doe")
end
step 'I should see user "Mary Jane" in team list' do
projects_with_access = find(".panel .well-list")
- projects_with_access.should have_content("Mary Jane")
+ expect(projects_with_access).to have_content("Mary Jane")
end
step 'I should not see user "Mary Jane" in team list' do
projects_with_access = find(".panel .well-list")
- projects_with_access.should_not have_content("Mary Jane")
+ expect(projects_with_access).not_to have_content("Mary Jane")
end
step 'project from group "Owned" has issues assigned to me' do
@@ -122,28 +122,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see new group "Owned" name' do
- within ".navbar-gitlab" do
- page.should have_content "new-name"
+ page.within ".navbar-gitlab" do
+ expect(page).to have_content "new-name"
end
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
- Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader
- Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
+ 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 }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
- page.should have_link("Remove avatar")
+ expect(page).to have_link("Remove avatar")
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,11 +154,11 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should not see group "Owned" avatar' do
- Group.find_by(name: "Owned").avatar?.should be_false
+ expect(Group.find_by(name: "Owned").avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
- page.should_not have_link("Remove avatar")
+ expect(page).not_to have_link("Remove avatar")
end
step 'I click on the "Remove User From Group" button for "John Doe"' do
@@ -172,17 +172,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should not see the "Remove User From Group" button for "John Doe"' do
- find(:css, 'li', text: "John Doe").should_not have_selector(:css, 'a.btn-remove')
+ expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove')
# poltergeist always confirms popups.
end
step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
- find(:css, 'li', text: "Mary Jane").should_not have_selector(:css, 'a.btn-remove')
+ expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove')
# poltergeist always confirms popups.
end
step 'I search for \'Mary\' member' do
- within '.member-search-form' do
+ page.within '.member-search-form' do
fill_in 'search', with: 'Mary'
click_button 'Search'
end
@@ -193,7 +193,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see group milestones index page has no milestones' do
- page.should have_content('No milestones to show')
+ expect(page).to have_content('No milestones to show')
end
step 'Group has projects with milestones' do
@@ -201,10 +201,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see group milestones index page with milestones' do
- page.should have_content('Version 7.2')
- page.should have_content('GL-113')
- page.should have_link('2 Issues', href: group_milestone_path("owned", "version-7-2", title: "Version 7.2"))
- page.should have_link('3 Merge Requests', href: group_milestone_path("owned", "gl-113", title: "GL-113"))
+ expect(page).to have_content('Version 7.2')
+ expect(page).to have_content('GL-113')
+ expect(page).to have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
+ expect(page).to have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
end
step 'I click on one group milestone' do
@@ -212,14 +212,14 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see group milestone with descriptions and expiry date' do
- page.should have_content('expires at Aug 20, 2114')
+ expect(page).to have_content('expires at Aug 20, 2114')
end
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
- page.should have_content('Milestone GL-113')
- page.should have_content('Progress: 0 closed – 4 open')
- page.should have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
- page.should have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
+ expect(page).to have_content('Milestone GL-113')
+ expect(page).to have_content('Progress: 0 closed – 4 open')
+ expect(page).to have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
+ expect(page).to have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
end
protected
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 8595ee876a4..79e3b55f6e1 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -15,8 +15,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('SSH Keys')
end
- step 'the active main tab should be Design' do
- ensure_active_main_tab('Design')
+ step 'the active main tab should be Preferences' do
+ ensure_active_main_tab('Preferences')
end
step 'the active main tab should be History' do
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
index 2b6ac37d866..10ebe705365 100644
--- a/features/steps/profile/emails.rb
+++ b/features/steps/profile/emails.rb
@@ -6,9 +6,9 @@ class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
end
step 'I should see my emails' do
- page.should have_content(@user.email)
+ expect(page).to have_content(@user.email)
@user.emails.each do |email|
- page.should have_content(email.email)
+ expect(page).to have_content(email.email)
end
end
@@ -19,14 +19,14 @@ class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
step 'I should see new email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
- email.should_not be_nil
- page.should have_content("my@email.com")
+ expect(email).not_to be_nil
+ expect(page).to have_content("my@email.com")
end
step 'I should not see email "my@email.com"' do
email = @user.emails.find_by(email: "my@email.com")
- email.should be_nil
- page.should_not have_content("my@email.com")
+ expect(email).to be_nil
+ expect(page).not_to have_content("my@email.com")
end
step 'I click link "Remove" for "my@email.com"' do
@@ -43,6 +43,6 @@ class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
step 'I should not have @user.email added' do
email = @user.emails.find_by(email: @user.email)
- email.should be_nil
+ expect(email).to be_nil
end
end
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 13e93618eb7..447ea6d9d10 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -7,6 +7,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end
step 'I should see global notifications settings' do
- page.should have_content "Notifications Settings"
+ expect(page).to have_content "Notifications"
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 791982d16c3..11e1163c352 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -3,44 +3,46 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
include SharedPaths
step 'I should see my profile info' do
- page.should have_content "Profile Settings"
+ expect(page).to have_content "This information will appear on your profile"
end
step 'I change my profile info' do
- fill_in "user_skype", with: "testskype"
- fill_in "user_linkedin", with: "testlinkedin"
- fill_in "user_twitter", with: "testtwitter"
- fill_in "user_website_url", with: "testurl"
- fill_in "user_location", with: "Ukraine"
- click_button "Save changes"
+ fill_in 'user_skype', with: 'testskype'
+ fill_in 'user_linkedin', with: 'testlinkedin'
+ fill_in 'user_twitter', with: 'testtwitter'
+ fill_in 'user_website_url', with: 'testurl'
+ fill_in 'user_location', with: 'Ukraine'
+ fill_in 'user_bio', with: 'I <3 GitLab'
+ click_button 'Save changes'
@user.reload
end
step 'I should see new profile info' do
- @user.skype.should == 'testskype'
- @user.linkedin.should == 'testlinkedin'
- @user.twitter.should == 'testtwitter'
- @user.website_url.should == 'testurl'
- find("#user_location").value.should == "Ukraine"
+ expect(@user.skype).to eq 'testskype'
+ expect(@user.linkedin).to eq 'testlinkedin'
+ expect(@user.twitter).to eq 'testtwitter'
+ expect(@user.website_url).to eq 'testurl'
+ expect(@user.bio).to eq 'I <3 GitLab'
+ expect(find('#user_location').value).to eq 'Ukraine'
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
- @user.avatar.should be_instance_of AvatarUploader
- @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
+ expect(@user.avatar).to be_instance_of AvatarUploader
+ expect(@user.avatar.url).to eq "/uploads/user/avatar/#{ @user.id }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
- page.should have_link("Remove avatar")
+ expect(page).to have_link("Remove avatar")
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
@@ -51,15 +53,15 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see my gravatar' do
- @user.avatar?.should be_false
+ expect(@user.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
- page.should_not have_link("Remove avatar")
+ expect(page).not_to have_link("Remove avatar")
end
step 'I try change my password w/o old one' do
- within '.update-password' do
+ page.within '.update-password' do
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
@@ -67,7 +69,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I change my password' do
- within '.update-password' do
+ page.within '.update-password' do
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
@@ -76,7 +78,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I unsuccessfully change my password' do
- within '.update-password' do
+ page.within '.update-password' do
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
@@ -85,23 +87,27 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I should see a missing password error message" do
- page.should 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
- page.should 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
- within '.update-token' do
+ page.within '.update-token' do
@old_token = @user.private_token
click_button "Reset"
end
end
step 'I should see new token' do
- find("#token").value.should_not == @old_token
- find("#token").value.should == @user.reload.private_token
+ expect(find("#token").value).not_to eq @old_token
+ expect(find("#token").value).to eq @user.reload.private_token
end
step 'I have activity' do
@@ -109,28 +115,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see my activity' do
- page.should have_content "#{current_user.name} closed issue"
- end
-
- step "I change my application theme" do
- within '.application-theme' do
- choose "Violet"
- end
- end
-
- step "I change my code preview theme" do
- within '.code-preview-theme' do
- choose "Solarized dark"
- end
- end
-
- step "I should see the theme change immediately" do
- page.should have_selector('body.ui_color')
- page.should_not have_selector('body.ui_basic')
- end
-
- step "I should receive feedback that the changes were saved" do
- page.should have_content("saved")
+ expect(page).to have_content "#{current_user.name} closed issue"
end
step 'my password is expired' do
@@ -139,11 +124,11 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step "I am not an ldap user" do
current_user.identities.delete
- current_user.ldap_user?.should be_false
+ expect(current_user.ldap_user?).to eq false
end
step 'I redirected to expired password page' do
- current_path.should == new_profile_password_path
+ expect(current_path).to eq new_profile_password_path
end
step 'I submit new password' do
@@ -154,26 +139,26 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I redirected to sign in page' do
- current_path.should == new_user_session_path
+ expect(current_path).to eq new_user_session_path
end
step 'I should be redirected to password page' do
- current_path.should == edit_profile_password_path
+ expect(current_path).to eq edit_profile_password_path
end
step 'I should be redirected to account page' do
- current_path.should == profile_account_path
+ expect(current_path).to eq profile_account_path
end
step 'I click on my profile picture' do
- click_link 'profile-pic'
+ find(:css, '.sidebar-user').click
end
step 'I should see my user page' do
- page.should have_content "User Activity"
+ expect(page).to have_content "User Activity"
- within '.navbar-gitlab' do
- page.should have_content current_user.name
+ page.within '.navbar-gitlab' do
+ expect(page).to have_content current_user.name
end
end
@@ -187,7 +172,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see groups I belong to' do
- page.should have_css('.profile-groups-avatars', visible: true)
+ expect(page).to have_css('.profile-groups-avatars', visible: true)
end
step 'I click on new application button' do
@@ -195,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see application form' do
- page.should have_content "New application"
+ expect(page).to have_content "New Application"
end
step 'I fill application form out and submit' do
@@ -205,9 +190,9 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I see application' do
- page.should have_content "Application: test"
- page.should have_content "Application Id"
- page.should have_content "Secret"
+ expect(page).to have_content "Application: test"
+ expect(page).to have_content "Application Id"
+ expect(page).to have_content "Secret"
end
step 'I click edit' do
@@ -215,28 +200,28 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I see edit application form' do
- page.should have_content "Edit application"
+ expect(page).to have_content "Edit application"
end
step 'I change name of application and submit' do
- page.should have_content "Edit application"
+ expect(page).to have_content "Edit application"
fill_in :doorkeeper_application_name, with: 'test_changed'
click_on "Submit"
end
step 'I see that application was changed' do
- page.should have_content "test_changed"
- page.should have_content "Application Id"
- page.should have_content "Secret"
+ expect(page).to have_content "test_changed"
+ expect(page).to have_content "Application Id"
+ expect(page).to have_content "Secret"
end
step 'I click to remove application' do
- within '.oauth-applications' do
+ page.within '.oauth-applications' do
click_on "Destroy"
end
end
step "I see that application is removed" do
- page.find(".oauth-applications").should_not have_content "test_changed"
+ expect(page.find(".oauth-applications")).not_to have_content "test_changed"
end
end
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index ea912e5b4da..c7f879d247d 100644
--- a/features/steps/profile/ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -3,7 +3,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
step 'I should see my ssh keys' do
@user.keys.each do |key|
- page.should have_content(key.title)
+ expect(page).to have_content(key.title)
end
end
@@ -19,9 +19,9 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
step 'I should see new ssh key "Laptop"' do
key = Key.find_by(title: "Laptop")
- page.should have_content(key.title)
- page.should have_content(key.key)
- current_path.should == profile_key_path(key)
+ expect(page).to have_content(key.title)
+ expect(page).to have_content(key.key)
+ expect(current_path).to eq profile_key_path(key)
end
step 'I click link "Work"' do
@@ -37,7 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end
step 'I should not see "Work" ssh key' do
- page.should_not have_content "Work"
+ expect(page).not_to have_content "Work"
end
step 'I have ssh key "ssh-rsa Work"' do
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index dd3215adb1a..fabbc1d3d81 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -20,7 +20,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
end
step 'I click the "Edit" tab' do
- within '.project-settings-nav' do
+ page.within '.project-settings-nav' do
click_link('Project')
end
end
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index 37ad0c77655..db1387763d5 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -19,11 +19,11 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
end
step 'I should not see "Archived"' do
- page.should_not have_content "Archived"
+ expect(page).not_to have_content "Archived"
end
step 'I should see "Archived"' do
- page.should have_content "Archived"
+ expect(page).to have_content "Archived"
end
When 'I set project archived' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 07f7e5796a3..338f5e8d3ee 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -8,8 +8,8 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
end
step 'I should see "Shop" all branches list' do
- page.should have_content "Branches"
- page.should have_content "master"
+ expect(page).to have_content "Branches"
+ expect(page).to have_content "master"
end
step 'I click link "Protected"' do
@@ -17,9 +17,9 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
end
step 'I should see "Shop" protected branches list' do
- within ".protected-branches-list" do
- page.should have_content "stable"
- page.should_not have_content "master"
+ page.within ".protected-branches-list" do
+ expect(page).to have_content "stable"
+ expect(page).not_to have_content "master"
end
end
@@ -57,29 +57,29 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
end
step 'I should see new branch created' do
- page.should have_content 'deploy_keys'
+ expect(page).to have_content 'deploy_keys'
end
step 'I should see new an error that branch is invalid' do
- page.should have_content 'Branch name invalid'
+ expect(page).to have_content 'Branch name invalid'
end
step 'I should see new an error that ref is invalid' do
- page.should have_content 'Invalid reference name'
+ expect(page).to have_content 'Invalid reference name'
end
step 'I should see new an error that branch already exists' do
- page.should have_content 'Branch already exists'
+ expect(page).to have_content 'Branch already exists'
end
step "I click branch 'improve/awesome' delete link" do
- within '.js-branch-improve\/awesome' do
+ page.within '.js-branch-improve\/awesome' do
find('.btn-remove').click
sleep 0.05
end
end
step "I should not see branch 'improve/awesome'" do
- all(visible: true).should_not have_content 'improve/awesome'
+ expect(page.all(visible: true)).not_to have_content 'improve/awesome'
end
end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index c888e82e207..e6330ec457e 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -2,13 +2,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
+ include SharedDiffNote
include RepoHelpers
step 'I see project commits' do
commit = @project.repository.commit
- page.should have_content(@project.name)
- page.should have_content(commit.message[0..20])
- page.should have_content(commit.short_id)
+ expect(page).to have_content(@project.name)
+ expect(page).to have_content(commit.message[0..20])
+ expect(page).to have_content(commit.short_id)
end
step 'I click atom feed link' do
@@ -17,10 +18,10 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I see commits atom feed' do
commit = @project.repository.commit
- response_headers['Content-Type'].should have_content("application/atom+xml")
- body.should have_selector("title", text: "#{@project.name}:master commits")
- body.should have_selector("author email", text: commit.author_email)
- body.should have_selector("entry summary", text: commit.description[0..10])
+ expect(response_headers['Content-Type']).to have_content("application/atom+xml")
+ expect(body).to have_selector("title", text: "#{@project.name}:master commits")
+ expect(body).to have_selector("author email", text: commit.author_email)
+ expect(body).to have_selector("entry summary", text: commit.description[0..10])
end
step 'I click on commit link' do
@@ -28,8 +29,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I see commit info' do
- page.should have_content sample_commit.message
- page.should have_content "Showing #{sample_commit.files_changed_count} changed files"
+ expect(page).to have_content sample_commit.message
+ expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files"
end
step 'I fill compare fields with refs' do
@@ -45,38 +46,37 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I should see additional file lines' do
- within @diff.parent do
- first('.new_line').text.should_not have_content "..."
+ page.within @diff.parent do
+ expect(first('.new_line').text).not_to have_content "..."
end
end
step 'I see compared refs' do
- page.should have_content "Compare View"
- page.should have_content "Commits (1)"
- page.should have_content "Showing 2 changed files"
+ expect(page).to have_content "Compare View"
+ expect(page).to have_content "Commits (1)"
+ expect(page).to have_content "Showing 2 changed files"
end
step 'I see breadcrumb links' do
- page.should have_selector('ul.breadcrumb')
- page.should have_selector('ul.breadcrumb a', count: 4)
+ expect(page).to have_selector('ul.breadcrumb')
+ expect(page).to have_selector('ul.breadcrumb a', count: 4)
end
step 'I see commits stats' do
- page.should have_content 'Top 50 Committers'
- page.should have_content 'Committers'
- page.should have_content 'Total commits'
- page.should have_content 'Authors'
+ expect(page).to have_content 'Top 50 Committers'
+ expect(page).to have_content 'Committers'
+ expect(page).to have_content 'Total commits'
+ expect(page).to have_content 'Authors'
end
step 'I visit big commit page' do
- Commit::DIFF_SAFE_FILES = 20
+ stub_const('Commit::DIFF_SAFE_FILES', 20)
visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
end
step 'I see big commit warning' do
- page.should have_content sample_big_commit.message
- page.should have_content "Too many changes"
- Commit::DIFF_SAFE_FILES = 100
+ expect(page).to have_content sample_big_commit.message
+ expect(page).to have_content "Too many changes"
end
step 'I visit a commit with an image that changed' do
@@ -84,20 +84,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'The diff links to both the previous and current image' do
- links = all('.two-up span div a')
- links[0]['href'].should =~ %r{blob/#{sample_image_commit.old_blob_id}}
- links[1]['href'].should =~ %r{blob/#{sample_image_commit.new_blob_id}}
- end
-
- step 'I click side-by-side diff button' do
- click_link "Side-by-side"
- end
-
- step 'I see side-by-side diff button' do
- page.should have_content "Side-by-side"
+ links = page.all('.two-up span div a')
+ expect(links[0]['href']).to match %r{blob/#{sample_image_commit.old_blob_id}}
+ expect(links[1]['href']).to match %r{blob/#{sample_image_commit.new_blob_id}}
end
step 'I see inline diff button' do
- page.should have_content "Inline"
+ expect(page).to have_content "Inline"
end
end
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
index 3465fcbfd07..e6f8faf50fd 100644
--- a/features/steps/project/commits/tags.rb
+++ b/features/steps/project/commits/tags.rb
@@ -4,8 +4,8 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
include SharedPaths
step 'I should see "Shop" all tags list' do
- page.should have_content "Tags"
- page.should have_content "v1.0.0"
+ expect(page).to have_content "Tags"
+ expect(page).to have_content "v1.0.0"
end
step 'I click new tag link' do
@@ -37,37 +37,37 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
end
step 'I should see new tag created' do
- page.should have_content 'v7.0'
+ expect(page).to have_content 'v7.0'
end
step 'I should see new an error that tag is invalid' do
- page.should have_content 'Tag name invalid'
+ expect(page).to have_content 'Tag name invalid'
end
step 'I should see new an error that tag ref is invalid' do
- page.should have_content 'Invalid reference name'
+ expect(page).to have_content 'Invalid reference name'
end
step 'I should see new an error that tag already exists' do
- page.should have_content 'Tag already exists'
+ expect(page).to have_content 'Tag already exists'
end
step "I delete tag 'v1.1.0'" do
- within '.tags' do
+ page.within '.tags' do
first('.btn-remove').click
sleep 0.05
end
end
step "I should not see tag 'v1.1.0'" do
- within '.tags' do
- all(visible: true).should_not have_content 'v1.1.0'
+ page.within '.tags' do
+ expect(page.all(visible: true)).not_to have_content 'v1.1.0'
end
end
step 'I delete all tags' do
- within '.tags' do
- all('.btn-remove').each do |remove|
+ page.within '.tags' do
+ page.all('.btn-remove').each do |remove|
remove.click
sleep 0.05
end
@@ -75,8 +75,8 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
end
step 'I should see tags info message' do
- within '.tags' do
- page.should have_content 'Repository has no tags yet.'
+ page.within '.tags' do
+ expect(page).to have_content 'Repository has no tags yet.'
end
end
end
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 63ff84c82ef..40cada6da45 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -29,9 +29,9 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
def check_author_link(email, user)
author_link = find('.commit-author-link')
- author_link['href'].should == user_path(user)
- author_link['data-original-title'].should == email
- find('.commit-author-name').text.should == user.name
+ expect(author_link['href']).to eq user_path(user)
+ expect(author_link['data-original-title']).to eq email
+ expect(find('.commit-author-name').text).to eq user.name
end
def user_primary
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 6b85cf74f5f..0d39e1997b5 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -8,20 +8,20 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
end
step 'I should see project page' do
- page.should have_content "Empty"
- current_path.should == namespace_project_path(Project.last.namespace, Project.last)
+ expect(page).to have_content "Empty"
+ expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
end
step 'I should see empty project instuctions' do
- page.should have_content "git init"
- page.should have_content "git remote"
- page.should have_content Project.last.url_to_repo
+ expect(page).to have_content "git init"
+ expect(page).to have_content "git remote"
+ expect(page).to have_content Project.last.url_to_repo
end
step 'I see empty project instuctions' do
- page.should have_content "git init"
- page.should have_content "git remote"
- page.should have_content Project.last.url_to_repo
+ expect(page).to have_content "git init"
+ expect(page).to have_content "git remote"
+ expect(page).to have_content Project.last.url_to_repo
end
step 'I click on HTTP' do
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
end
step 'Remote url should update to http link' do
- page.should have_content "git remote add origin #{Project.last.http_url_to_repo}"
+ expect(page).to have_content "git remote add origin #{Project.last.http_url_to_repo}"
end
step 'If I click on SSH' do
@@ -37,6 +37,6 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
end
step 'Remote url should update to ssh link' do
- page.should have_content "git remote add origin #{Project.last.url_to_repo}"
+ expect(page).to have_content "git remote add origin #{Project.last.url_to_repo}"
end
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 81d1182cd1b..a4d6c9a1b8e 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -8,20 +8,20 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should see project deploy key' do
- within '.enabled-keys' do
- page.should have_content deploy_key.title
+ page.within '.enabled-keys' do
+ expect(page).to have_content deploy_key.title
end
end
step 'I should see other project deploy key' do
- within '.available-keys' do
- page.should have_content other_deploy_key.title
+ page.within '.available-keys' do
+ expect(page).to have_content other_deploy_key.title
end
end
step 'I should see public deploy key' do
- within '.available-keys' do
- page.should have_content public_deploy_key.title
+ page.within '.available-keys' do
+ expect(page).to have_content public_deploy_key.title
end
end
@@ -36,12 +36,12 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- current_path.should == namespace_project_deploy_keys_path(@project.namespace, @project)
+ expect(current_path).to eq namespace_project_deploy_keys_path(@project.namespace, @project)
end
step 'I should see newly created deploy key' do
- within '.enabled-keys' do
- page.should have_content(deploy_key.title)
+ page.within '.enabled-keys' do
+ expect(page).to have_content(deploy_key.title)
end
end
@@ -56,8 +56,8 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should only see the same deploy key once' do
- within '.available-keys' do
- page.should have_selector('ul li', count: 1)
+ page.within '.available-keys' do
+ expect(page).to have_selector('ul li', count: 1)
end
end
@@ -66,7 +66,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I click attach deploy key' do
- within '.available-keys' do
+ page.within '.available-keys' do
click_link 'Enable'
end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 8e58597db20..0e433781d7a 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -4,8 +4,8 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
include SharedProject
step 'I click link "Fork"' do
- page.should have_content "Shop"
- page.should have_content "Fork"
+ expect(page).to have_content "Shop"
+ expect(page).to have_content "Fork"
click_link "Fork"
end
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I should see the forked project page' do
- page.should have_content "Project was successfully forked."
+ expect(page).to have_content "Project was successfully forked."
end
step 'I already have a project named "Shop" in my namespace' do
@@ -23,11 +23,11 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I should see a "Name has already been taken" warning' do
- page.should have_content "Name has already been taken"
+ expect(page).to have_content "Name has already been taken"
end
step 'I fork to my namespace' do
- within '.fork-namespaces' do
+ page.within '.fork-namespaces' do
click_link current_user.name
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index ebfa102cee5..78812c52026 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -21,17 +21,17 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I should see merge request "Merge Request On Forked Project"' do
- @project.merge_requests.size.should >= 1
+ expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- @merge_request.title.should == "Merge Request On Forked Project"
- @merge_request.source_project.should == @forked_project
- @merge_request.source_branch.should == "fix"
- @merge_request.target_branch.should == "master"
- page.should have_content @forked_project.path_with_namespace
- page.should have_content @project.path_with_namespace
- page.should have_content @merge_request.source_branch
- page.should have_content @merge_request.target_branch
+ expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(@merge_request.title).to eq "Merge Request On Forked Project"
+ expect(@merge_request.source_project).to eq @forked_project
+ expect(@merge_request.source_branch).to eq "fix"
+ expect(@merge_request.target_branch).to eq "master"
+ expect(page).to have_content @forked_project.path_with_namespace
+ expect(page).to have_content @project.path_with_namespace
+ expect(page).to have_content @merge_request.source_branch
+ expect(page).to have_content @merge_request.target_branch
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
@@ -56,7 +56,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see the commit under the forked from project' do
commit = @project.repository.commit
- page.should have_content(commit.message)
+ expect(page).to have_content(commit.message)
end
step 'I click "Create Merge Request on fork" link' do
@@ -64,12 +64,12 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page for the forked project' do
- current_path.should == new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project)
- find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
- find("#merge_request_target_project_id").value.should == @project.id.to_s
- find("#merge_request_source_branch").value.should have_content "new_design"
- find("#merge_request_target_branch").value.should have_content "master"
- find("#merge_request_title").value.should == "New Design"
+ expect(current_path).to eq new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project)
+ expect(find("#merge_request_source_project_id").value).to eq @forked_project.id.to_s
+ expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
+ expect(find("#merge_request_source_branch").value).to have_content "new_design"
+ expect(find("#merge_request_target_branch").value).to have_content "master"
+ expect(find("#merge_request_title").value).to eq "New Design"
verify_commit_link(".mr_target_commit", @project)
verify_commit_link(".mr_source_commit", @forked_project)
end
@@ -83,22 +83,22 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I should see the edited merge request' do
- page.should have_content "An Edited Forked Merge Request"
- @project.merge_requests.size.should >= 1
+ expect(page).to have_content "An Edited Forked Merge Request"
+ expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- @merge_request.source_project.should == @forked_project
- @merge_request.source_branch.should == "fix"
- @merge_request.target_branch.should == "master"
- page.should have_content @forked_project.path_with_namespace
- page.should have_content @project.path_with_namespace
- page.should have_content @merge_request.source_branch
- page.should have_content @merge_request.target_branch
+ expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(@merge_request.source_project).to eq @forked_project
+ expect(@merge_request.source_branch).to eq "fix"
+ expect(@merge_request.target_branch).to eq "master"
+ expect(page).to have_content @forked_project.path_with_namespace
+ expect(page).to have_content @project.path_with_namespace
+ expect(page).to have_content @merge_request.source_branch
+ expect(page).to have_content @merge_request.target_branch
end
step 'I should see last push widget' do
- page.should have_content "You pushed to new_design"
- page.should have_link "Create Merge Request"
+ expect(page).to have_content "You pushed to new_design"
+ expect(page).to have_link "Create Merge Request"
end
step 'I click link edit "Merge Request On Forked Project"' do
@@ -106,26 +106,26 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
- current_path.should == edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- page.should have_content "Edit merge request ##{@merge_request.id}"
- find("#merge_request_title").value.should == "Merge Request On Forked Project"
+ expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(page).to have_content "Edit merge request ##{@merge_request.id}"
+ expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project"
end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
select "Select branch", from: "merge_request_target_branch"
- find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
- find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
- find(:select, "merge_request_source_branch", {}).value.should == ""
- find(:select, "merge_request_target_branch", {}).value.should == ""
+ expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s
+ expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s
+ expect(find(:select, "merge_request_source_branch", {}).value).to eq ""
+ expect(find(:select, "merge_request_target_branch", {}).value).to eq ""
click_button "Compare branches"
end
step 'I should see validation errors' do
- page.should have_content "You must select source and target branch"
+ expect(page).to have_content "You must select source and target branch"
end
step 'the target repository should be the original repository' do
- page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
+ expect(page).to have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
end
step 'I click "Assign to" dropdown"' do
@@ -139,13 +139,13 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see the users from the target project ID' do
expect(page).to have_selector('.user-result', visible: true, count: 2)
users = page.all('.user-name')
- users[0].text.should == 'Unassigned'
- users[1].text.should == @project.users.first.name
+ expect(users[0].text).to eq 'Unassigned'
+ expect(users[1].text).to eq @project.users.first.name
end
# Verify a link is generated against the correct project
def verify_commit_link(container_div, container_project)
# This should force a wait for the javascript to execute
- find(:div,container_div).find(".commit_short_id")['href'].should have_content "#{container_project.path_with_namespace}/commit"
+ expect(find(:div,container_div).find(".commit_short_id")['href']).to have_content "#{container_project.path_with_namespace}/commit"
end
end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index a2807c340f6..5e7e573a6ab 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -3,7 +3,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
include SharedProject
step 'page should have graphs' do
- page.should have_selector ".stat-graph"
+ expect(page).to have_selector ".stat-graph"
end
When 'I visit project "Shop" graph page' do
@@ -17,7 +17,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
step 'page should have commits graphs' do
- page.should have_content "Commit statistics for master"
- page.should have_content "Commits per day of month"
+ expect(page).to have_content "Commit statistics for master"
+ expect(page).to have_content "Commits per day of month"
end
end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 4b135202593..04e3bf78ede 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -19,18 +19,18 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
end
step 'I should see project hook' do
- page.should have_content @hook.url
+ expect(page).to have_content @hook.url
end
step 'I submit new hook' do
- @url = Faker::Internet.uri("http")
+ @url = FFaker::Internet.uri("http")
fill_in "hook_url", with: @url
expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1)
end
step 'I should see newly created hook' do
- current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
- page.should have_content(@url)
+ expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
+ expect(page).to have_content(@url)
end
step 'I click test hook button' do
@@ -44,19 +44,19 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
end
step 'hook should be triggered' do
- current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
- page.should have_selector '.flash-notice',
+ expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project)
+ expect(page).to have_selector '.flash-notice',
text: 'Hook successfully executed.'
end
step 'I should see hook error message' do
- page.should have_selector '.flash-alert',
+ expect(page).to have_selector '.flash-alert',
text: 'Hook execution failed. '\
'Ensure the project has commits.'
end
step 'I should see hook service down error message' do
- page.should have_selector '.flash-alert',
+ expect(page).to have_selector '.flash-alert',
text: 'Hook execution failed. '\
'Ensure hook URL is correct and '\
'service is up.'
diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index 5740bd12837..50bb32429b9 100644
--- a/features/steps/project/issues/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -5,26 +5,26 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
include Select2Helper
step 'I should see "Bugfix1" in issues list' do
- within ".issues-list" do
- page.should have_content "Bugfix1"
+ page.within ".issues-list" do
+ expect(page).to have_content "Bugfix1"
end
end
step 'I should see "Bugfix2" in issues list' do
- within ".issues-list" do
- page.should have_content "Bugfix2"
+ page.within ".issues-list" do
+ expect(page).to have_content "Bugfix2"
end
end
step 'I should not see "Bugfix2" in issues list' do
- within ".issues-list" do
- page.should_not have_content "Bugfix2"
+ page.within ".issues-list" do
+ expect(page).not_to have_content "Bugfix2"
end
end
step 'I should not see "Feature1" in issues list' do
- within ".issues-list" do
- page.should_not have_content "Feature1"
+ page.within ".issues-list" do
+ expect(page).not_to have_content "Feature1"
end
end
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
end
step 'I click link "feature"' do
- within ".labels-filter" do
+ page.within ".labels-filter" do
click_link "feature"
end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 504f0cff724..9ace6436b15 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -7,24 +7,23 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedMarkdown
step 'I should see "Release 0.4" in issues' do
- page.should have_content "Release 0.4"
+ expect(page).to have_content "Release 0.4"
end
step 'I should not see "Release 0.3" in issues' do
- page.should_not have_content "Release 0.3"
+ expect(page).not_to have_content "Release 0.3"
end
step 'I should not see "Tweet control" in issues' do
- page.should_not have_content "Tweet control"
+ expect(page).not_to have_content "Tweet control"
end
step 'I should see that I am subscribed' do
- find(".subscribe-button span").text.should == "Unsubscribe"
+ expect(find('.subscribe-button span')).to have_content 'Unsubscribe'
end
step 'I should see that I am unsubscribed' do
- sleep 0.2
- find(".subscribe-button span").text.should == "Subscribe"
+ expect(find('.subscribe-button span')).to have_content 'Subscribe'
end
step 'I click link "Closed"' do
@@ -36,11 +35,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see "Release 0.3" in issues' do
- page.should have_content "Release 0.3"
+ expect(page).to have_content "Release 0.3"
end
step 'I should not see "Release 0.4" in issues' do
- page.should_not have_content "Release 0.4"
+ expect(page).not_to have_content "Release 0.4"
end
step 'I click link "All"' do
@@ -52,7 +51,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see issue "Release 0.4"' do
- page.should have_content "Release 0.4"
+ expect(page).to have_content "Release 0.4"
end
step 'I click link "New Issue"' do
@@ -66,9 +65,9 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I see current user as the first user' do
expect(page).to have_selector('.user-result', visible: true, count: 4)
users = page.all('.user-name')
- users[0].text.should == 'Any'
- users[1].text.should == 'Unassigned'
- users[2].text.should == current_user.name
+ expect(users[0].text).to eq 'Any'
+ expect(users[1].text).to eq 'Unassigned'
+ expect(users[2].text).to eq current_user.name
end
step 'I submit new issue "500 error on profile"' do
@@ -87,16 +86,16 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see label \'bug\' with issue' do
- within '.issue-show-labels' do
- page.should have_content 'bug'
+ page.within '.issue-show-labels' do
+ expect(page).to have_content 'bug'
end
end
step 'I should see issue "500 error on profile"' do
issue = Issue.find_by(title: "500 error on profile")
- page.should have_content issue.title
- page.should have_content issue.author_name
- page.should have_content issue.project.name
+ expect(page).to have_content issue.title
+ expect(page).to have_content issue.author_name
+ expect(page).to have_content issue.project.name
end
step 'I fill in issue search with "Re"' do
@@ -139,7 +138,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I should see selected milestone with title "v3.0"' do
issues_milestone_selector = "#issue_milestone_id_chzn > a"
- find(issues_milestone_selector).should have_content("v3.0")
+ expect(find(issues_milestone_selector)).to have_content("v3.0")
end
When 'I select first assignee from "Shop" project' do
@@ -152,7 +151,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
issues_assignee_selector = "#issue_assignee_id_chzn > a"
assignee_name = project.users.first.name
- find(issues_assignee_selector).should have_content(assignee_name)
+ expect(find(issues_assignee_selector)).to have_content(assignee_name)
end
step 'project "Shop" have "Release 0.4" open issue' do
@@ -190,8 +189,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I see empty project details with ssh clone info' do
project = Project.find_by(name: 'Empty Project')
- all(:css, '.git-empty .clone').each do |element|
- element.text.should include(project.url_to_repo)
+ page.all(:css, '.git-empty .clone').each do |element|
+ expect(element.text).to include(project.url_to_repo)
end
end
@@ -201,7 +200,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I leave a comment with code block' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```"
click_button "Add Comment"
sleep 0.05
@@ -209,13 +208,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see an error alert section within the comment form' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
find(".error-alert")
end
end
step 'The code block should be unchanged' do
- page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
+ expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```")
end
step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do
@@ -239,15 +238,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see \'Bugfix1\' in issues' do
- page.should have_content 'Bugfix1'
+ expect(page).to have_content 'Bugfix1'
end
step 'I should see \'Feature1\' in issues' do
- page.should have_content 'Feature1'
+ expect(page).to have_content 'Feature1'
end
step 'I should not see \'Bugfix1\' in issues' do
- page.should_not have_content 'Bugfix1'
+ expect(page).not_to have_content 'Bugfix1'
end
step 'issue \'Release 0.4\' has label \'bug\'' do
@@ -257,11 +256,29 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I click label \'bug\'' do
- within ".issues-list" do
+ page.within ".issues-list" do
click_link 'bug'
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/labels.rb b/features/steps/project/issues/labels.rb
index 6ce34c500c6..d656acf4220 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -8,14 +8,14 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I remove label \'bug\'' do
- within "#label_#{bug_label.id}" do
+ page.within "#label_#{bug_label.id}" do
click_link 'Remove'
end
end
step 'I delete all labels' do
- within '.labels' do
- all('.btn-remove').each do |remove|
+ page.within '.labels' do
+ page.all('.btn-remove').each do |remove|
remove.click
sleep 0.05
end
@@ -23,8 +23,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I should see labels help message' do
- within '.labels' do
- page.should have_content 'Create first label or generate default set of '\
+ page.within '.labels' do
+ expect(page).to have_content 'Create first label or generate default set of '\
'labels'
end
end
@@ -48,38 +48,38 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I should see label label exist error message' do
- within '.label-form' do
- page.should have_content 'Title has already been taken'
+ page.within '.label-form' do
+ expect(page).to have_content 'Title has already been taken'
end
end
step 'I should see label color error message' do
- within '.label-form' do
- page.should have_content 'Color is invalid'
+ page.within '.label-form' do
+ expect(page).to have_content 'Color is invalid'
end
end
step 'I should see label \'feature\'' do
- within '.manage-labels-list' do
- page.should have_content 'feature'
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'feature'
end
end
step 'I should see label \'bug\'' do
- within '.manage-labels-list' do
- page.should have_content 'bug'
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'bug'
end
end
step 'I should not see label \'bug\'' do
- within '.manage-labels-list' do
- page.should_not have_content 'bug'
+ page.within '.manage-labels-list' do
+ expect(page).not_to have_content 'bug'
end
end
step 'I should see label \'support\'' do
- within '.manage-labels-list' do
- page.should have_content 'support'
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'support'
end
end
@@ -90,8 +90,8 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I should see label \'fix\'' do
- within '.manage-labels-list' do
- page.should have_content 'fix'
+ page.within '.manage-labels-list' do
+ expect(page).to have_content 'fix'
end
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index cce87a6d981..708c5243947 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -6,9 +6,9 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see milestone "v2.2"' do
milestone = @project.milestones.find_by(title: "v2.2")
- page.should have_content(milestone.title[0..10])
- page.should have_content(milestone.expires_at)
- page.should have_content("Issues")
+ expect(page).to have_content(milestone.title[0..10])
+ expect(page).to have_content(milestone.expires_at)
+ expect(page).to have_content("Issues")
end
step 'I click link "v2.2"' do
@@ -26,9 +26,9 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see milestone "v2.3"' do
milestone = @project.milestones.find_by(title: "v2.3")
- page.should have_content(milestone.title[0..10])
- page.should have_content(milestone.expires_at)
- page.should have_content("Issues")
+ expect(page).to have_content(milestone.title[0..10])
+ expect(page).to have_content(milestone.expires_at)
+ expect(page).to have_content("Issues")
end
step 'project "Shop" has milestone "v2.2"' do
@@ -54,6 +54,6 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end
step 'I should see 3 issues' do
- page.should have_selector('#tab-issues li.issue-row', count: 4)
+ expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index f67e6e3d8ca..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"
@@ -24,44 +25,44 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see merge request "Wiki Feature"' do
- within '.merge-request' do
- page.should have_content "Wiki Feature"
+ page.within '.merge-request' do
+ expect(page).to have_content "Wiki Feature"
end
end
step 'I should see closed merge request "Bug NS-04"' do
merge_request = MergeRequest.find_by!(title: "Bug NS-04")
- merge_request.closed?.should be_true
- page.should have_content "Closed 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
- page.should have_content "Bug NS-04"
+ expect(page).to have_content "Bug NS-04"
end
step 'I should see "Bug NS-04" in merge requests' do
- page.should have_content "Bug NS-04"
+ expect(page).to have_content "Bug NS-04"
end
step 'I should see "Feature NS-03" in merge requests' do
- page.should have_content "Feature NS-03"
+ expect(page).to have_content "Feature NS-03"
end
step 'I should not see "Feature NS-03" in merge requests' do
- page.should_not have_content "Feature NS-03"
+ expect(page).not_to have_content "Feature NS-03"
end
step 'I should not see "Bug NS-04" in merge requests' do
- page.should_not have_content "Bug NS-04"
+ expect(page).not_to have_content "Bug NS-04"
end
step 'I should see that I am subscribed' do
- find(".subscribe-button span").text.should == "Unsubscribe"
+ expect(find('.subscribe-button span')).to have_content 'Unsubscribe'
end
step 'I should see that I am unsubscribed' do
- find(".subscribe-button span").should have_content("Subscribe")
+ expect(find('.subscribe-button span')).to have_content 'Subscribe'
end
step 'I click button "Unsubscribe"' do
@@ -108,22 +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
- find('.diffs-tab').click
- sleep 2
+ step 'I click on the Changes tab' do
+ page.within '.merge-request-tabs' do
+ click_link 'Changes'
+ end
+
+ # 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 = all('#commit-diff-viewtype')
- expect(buttons.count).to eq(2)
-
- buttons.each do |b|
- expect(b['href']).should_not 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
@@ -131,11 +136,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click on the commit in the merge request' do
- within '.merge-request-tabs' do
+ page.within '.merge-request-tabs' do
click_link 'Commits'
end
- within '.commits' do
+ page.within '.commits' do
click_link Commit.truncate_sha(sample_commit.id)
end
end
@@ -161,24 +166,30 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see a discussion has started on diff' 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"
+ 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
- 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"
+ 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
- page.should have_content "#{current_user.name} started a discussion on commit"
- page.should 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
- page.should have_button 'Accept Merge Request'
+ expect(page).to have_button 'Accept Merge Request'
end
step 'I modify merge commit message' do
@@ -187,6 +198,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'merge request "Bug NS-05" is mergeable' do
+ merge_request.project.satellite.create
merge_request.mark_as_mergeable
end
@@ -195,14 +207,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
merge!: true,
)
- within '.can_be_merged' do
+ page.within '.mr-state-widget' do
click_button "Accept Merge Request"
end
end
step 'I should see merged request' do
- within '.issue-box' do
- page.should have_content "Merged"
+ page.within '.issue-box' do
+ expect(page).to have_content "Merged"
end
end
@@ -211,76 +223,78 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see reopened merge request "Bug NS-04"' do
- within '.issue-box' do
- page.should have_content "Open"
+ page.within '.issue-box' do
+ expect(page).to have_content "Open"
end
end
step 'I click link "Hide inline discussion" of the second file' do
- within '.files [id^=diff]:nth-child(2)' do
+ page.within '.files [id^=diff]:nth-child(2)' do
find('.js-toggle-diff-comments').click
end
end
step 'I click link "Show inline discussion" of the second file' do
- within '.files [id^=diff]:nth-child(2)' do
+ page.within '.files [id^=diff]:nth-child(2)' do
find('.js-toggle-diff-comments').click
end
end
step 'I should not see a comment like "Line is wrong" in the second file' do
- within '.files [id^=diff]:nth-child(2)' do
- page.should_not have_visible_content "Line is wrong"
+ page.within '.files [id^=diff]:nth-child(2)' do
+ expect(page).not_to have_visible_content "Line is wrong"
end
end
step 'I should see a comment like "Line is wrong" in the second file' do
- within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
- page.should have_visible_content "Line is wrong"
+ page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
+ expect(page).to have_visible_content "Line is wrong"
end
end
step 'I should not see a comment like "Line is wrong here" in the second file' do
- within '.files [id^=diff]:nth-child(2)' do
- page.should_not have_visible_content "Line is wrong here"
+ page.within '.files [id^=diff]:nth-child(2)' do
+ expect(page).not_to have_visible_content "Line is wrong here"
end
end
step 'I should see a comment like "Line is wrong here" in the second file' do
- within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
- page.should have_visible_content "Line is wrong here"
+ page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
+ expect(page).to have_visible_content "Line is wrong here"
end
end
step 'I leave a comment like "Line is correct" on line 12 of the first file' do
init_diff_note_first_file
- within(".js-discussion-note-form") do
+ page.within(".js-discussion-note-form") do
fill_in "note_note", with: "Line is correct"
click_button "Add Comment"
end
- within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do
- page.should have_content "Line is correct"
+ page.within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do
+ expect(page).to have_content "Line is correct"
end
end
step 'I leave a comment like "Line is wrong" on line 39 of the second file' do
init_diff_note_second_file
- within(".js-discussion-note-form") do
+ page.within(".js-discussion-note-form") do
fill_in "note_note", with: "Line is wrong on here"
click_button "Add Comment"
end
end
step 'I should still see a comment like "Line is correct" in the first file' do
- within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do
- page.should have_visible_content "Line is correct"
+ page.within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do
+ expect(page).to have_visible_content "Line is correct"
end
end
step 'I unfold diff' do
+ expect(page).to have_css('.js-unfold')
+
first('.js-unfold').click
end
@@ -293,8 +307,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see comments on the side-by-side diff page' do
- within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do
- page.should have_visible_content "Line is correct"
+ page.within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do
+ expect(page).to have_visible_content "Line is correct"
end
end
@@ -302,6 +316,32 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
fill_in 'issue_search', with: "Fe"
end
+ step 'I click the "Target branch" dropdown' do
+ first('.target_branch').click
+ end
+
+ step 'I select a new target branch' do
+ select "feature", from: "merge_request_target_branch"
+ click_button 'Save'
+ end
+
+ step 'I should see new target branch changes' do
+ expect(page).to have_content 'From fix into feature'
+ 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
@@ -311,12 +351,13 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
def leave_comment(message)
- 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
-
- page.should 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/network_graph.rb b/features/steps/project/network_graph.rb
index a15688ace6a..992cf2734fd 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
include SharedProject
step 'page should have network graph' do
- page.should have_selector ".network-graph"
+ expect(page).to have_selector ".network-graph"
end
When 'I visit project "Shop" network page' do
@@ -16,16 +16,16 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should select "master" in select box' do
- page.should have_selector '.select2-chosen', text: "master"
+ expect(page).to have_selector '.select2-chosen', text: "master"
end
step 'page should select "v1.0.0" in select box' do
- page.should have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
end
step 'page should have "master" on graph' do
- within '.network-graph' do
- page.should have_content 'master'
+ page.within '.network-graph' do
+ expect(page).to have_content 'master'
end
end
@@ -45,33 +45,33 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should have content not containing "v1.0.0"' do
- within '.network-graph' do
- page.should have_content 'Change some files'
+ page.within '.network-graph' do
+ expect(page).to have_content 'Change some files'
end
end
step 'page should not have content not containing "v1.0.0"' do
- within '.network-graph' do
- page.should_not have_content 'Change some files'
+ page.within '.network-graph' do
+ expect(page).not_to have_content 'Change some files'
end
end
step 'page should select "feature" in select box' do
- page.should have_selector '.select2-chosen', text: "feature"
+ expect(page).to have_selector '.select2-chosen', text: "feature"
end
step 'page should select "v1.0.0" in select box' do
- page.should have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
end
step 'page should have "feature" on graph' do
- within '.network-graph' do
- page.should have_content 'feature'
+ page.within '.network-graph' do
+ expect(page).to have_content 'feature'
end
end
When 'I looking for a commit by SHA of "v1.0.0"' do
- within ".network-form" do
+ page.within ".network-form" do
fill_in 'extended_sha1', with: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
find('button').click
end
@@ -79,13 +79,13 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should have "v1.0.0" on graph' do
- within '.network-graph' do
- page.should have_content 'v1.0.0'
+ page.within '.network-graph' do
+ expect(page).to have_content 'v1.0.0'
end
end
When 'I look for a commit by ";"' do
- within ".network-form" do
+ page.within ".network-form" do
fill_in 'extended_sha1', with: ';'
find('button').click
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 93fea693f89..b4a0ba1e27f 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -13,7 +13,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project with new settings' do
- find_field('project_name').value.should == 'NewName'
+ expect(find_field('project_name').value).to eq 'NewName'
end
step 'change project path settings' do
@@ -22,32 +22,32 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project with new path settings' do
- project.path.should == 'new-path'
+ expect(project.path).to eq 'new-path'
end
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
end
step 'I should see new project avatar' do
- @project.avatar.should be_instance_of AvatarUploader
+ expect(@project.avatar).to be_instance_of AvatarUploader
url = @project.avatar.url
- url.should == "/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
- page.should have_link('Remove avatar')
+ expect(page).to have_link('Remove avatar')
end
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,16 +59,16 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see the default project avatar' do
- @project.avatar?.should be_false
+ expect(@project.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
- page.should_not have_link('Remove avatar')
+ expect(page).not_to have_link('Remove avatar')
end
step 'I should see project "Shop" version' do
- within '.project-side' do
- page.should have_content '6.7.0.pre'
+ page.within '.project-side' do
+ expect(page).to have_content '6.7.0.pre'
end
end
@@ -78,7 +78,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project default branch changed' do
- find(:css, 'select#project_default_branch').value.should == 'fix'
+ expect(find(:css, 'select#project_default_branch').value).to eq 'fix'
end
step 'I select project "Forum" README tab' do
@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- page.should have_link 'README.md'
- page.should have_content 'Sample repo for testing gitlab features'
+ expect(page).to have_link 'README.md'
+ expect(page).to have_content 'Sample repo for testing gitlab features'
end
step 'I should see project "Shop" README' do
- page.should have_link 'README.md'
- page.should have_content 'testme'
+ expect(page).to have_link 'README.md'
+ expect(page).to have_content 'testme'
end
step 'I add project tags' do
@@ -104,10 +104,14 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should not see "New Issue" button' do
- page.should_not have_link 'New Issue'
+ expect(page).not_to have_link 'New Issue'
end
step 'I should not see "New Merge Request" button' do
- page.should_not have_link 'New Merge Request'
+ expect(page).not_to have_link 'New Merge Request'
+ end
+
+ step 'I should not see "Snippets" button' do
+ expect(page).not_to have_link 'Snippets'
end
end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 57c6e39c801..0e724138a8a 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -18,8 +18,8 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should see project "Community" home page' do
Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
- within '.navbar-gitlab .title' do
- page.should have_content 'Community'
+ page.within '.navbar-gitlab .title' do
+ expect(page).to have_content 'Community'
end
end
@@ -48,8 +48,8 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should be redirected to "Community" page' do
project = Project.find_by(name: 'Community')
- current_path.should == "/#{project.path_with_namespace}"
- status_code.should == 200
+ expect(current_path).to eq "/#{project.path_with_namespace}"
+ expect(status_code).to eq 200
end
step 'I get redirected to signin page where I sign in' do
@@ -63,7 +63,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I should be redirected to "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- current_path.should == "/#{project.path_with_namespace}"
- status_code.should == 200
+ expect(current_path).to eq "/#{project.path_with_namespace}"
+ expect(status_code).to eq 200
end
end
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 4b3d79324ab..0327fd61981 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -8,16 +8,16 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see list of available services' do
- page.should have_content 'Project services'
- page.should have_content 'Campfire'
- page.should have_content 'HipChat'
- page.should have_content 'GitLab CI'
- page.should have_content 'Assembla'
- page.should have_content 'Pushover'
- page.should have_content 'Atlassian Bamboo'
- page.should have_content 'JetBrains TeamCity'
- page.should have_content 'Asana'
- page.should have_content 'Irker (IRC gateway)'
+ expect(page).to have_content 'Project services'
+ expect(page).to have_content 'Campfire'
+ expect(page).to have_content 'HipChat'
+ expect(page).to have_content 'GitLab CI'
+ expect(page).to have_content 'Assembla'
+ expect(page).to have_content 'Pushover'
+ expect(page).to have_content 'Atlassian Bamboo'
+ expect(page).to have_content 'JetBrains TeamCity'
+ expect(page).to have_content 'Asana'
+ expect(page).to have_content 'Irker (IRC gateway)'
end
step 'I click gitlab-ci service link' do
@@ -32,7 +32,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see service settings saved' do
- find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
+ expect(find_field('Project url').value).to eq 'http://ci.gitlab.org/projects/3'
end
step 'I click hipchat service link' do
@@ -47,7 +47,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see hipchat service settings saved' do
- find_field('Room').value.should == 'gitlab'
+ expect(find_field('Room').value).to eq 'gitlab'
end
step 'I fill hipchat settings with custom server' do
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see hipchat service settings with custom server saved' do
- find_field('Server').value.should == 'https://chat.example.com'
+ expect(find_field('Server').value).to eq 'https://chat.example.com'
end
step 'I click pivotaltracker service link' do
@@ -73,7 +73,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see pivotaltracker service settings saved' do
- find_field('Token').value.should == 'verySecret'
+ expect(find_field('Token').value).to eq 'verySecret'
end
step 'I click Flowdock service link' do
@@ -87,7 +87,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Flowdock service settings saved' do
- find_field('Token').value.should == 'verySecret'
+ expect(find_field('Token').value).to eq 'verySecret'
end
step 'I click Assembla service link' do
@@ -101,7 +101,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Assembla service settings saved' do
- find_field('Token').value.should == 'verySecret'
+ expect(find_field('Token').value).to eq 'verySecret'
end
step 'I click Asana service link' do
@@ -116,8 +116,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Asana service settings saved' do
- find_field('Api key').value.should == 'verySecret'
- find_field('Restrict to branch').value.should == 'master'
+ expect(find_field('Api key').value).to eq 'verySecret'
+ expect(find_field('Restrict to branch').value).to eq 'master'
end
step 'I click email on push service link' do
@@ -130,7 +130,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see email on push service settings saved' do
- find_field('Recipients').value.should == 'qa@company.name'
+ expect(find_field('Recipients').value).to eq 'qa@company.name'
end
step 'I click Irker service link' do
@@ -145,8 +145,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Irker service settings saved' do
- find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits'
- find_field('Colorize messages').value.should == '1'
+ expect(find_field('Recipients').value).to eq 'irc://chat.freenode.net/#commits'
+ expect(find_field('Colorize messages').value).to eq '1'
end
step 'I click Slack service link' do
@@ -160,7 +160,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Slack service settings saved' do
- find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
+ expect(find_field('Webhook').value).to eq 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
end
step 'I click Pushover service link' do
@@ -178,11 +178,11 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Pushover service settings saved' do
- find_field('Api key').value.should == 'verySecret'
- find_field('User key').value.should == 'verySecret'
- find_field('Device').value.should == 'myDevice'
- find_field('Priority').find('option[selected]').value.should == '1'
- find_field('Sound').find('option[selected]').value.should == 'bike'
+ expect(find_field('Api key').value).to eq 'verySecret'
+ expect(find_field('User key').value).to eq 'verySecret'
+ expect(find_field('Device').value).to eq 'myDevice'
+ expect(find_field('Priority').find('option[selected]').value).to eq '1'
+ expect(find_field('Sound').find('option[selected]').value).to eq 'bike'
end
step 'I click Atlassian Bamboo CI service link' do
@@ -199,9 +199,9 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see Atlassian Bamboo CI service settings saved' do
- find_field('Bamboo url').value.should == 'http://bamboo.example.com'
- find_field('Build key').value.should == 'KEY'
- find_field('Username').value.should == 'user'
+ expect(find_field('Bamboo url').value).to eq 'http://bamboo.example.com'
+ expect(find_field('Build key').value).to eq 'KEY'
+ expect(find_field('Username').value).to eq 'user'
end
step 'I click JetBrains TeamCity CI service link' do
@@ -218,8 +218,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see JetBrains TeamCity CI service settings saved' do
- find_field('Teamcity url').value.should == 'http://teamcity.example.com'
- find_field('Build type').value.should == 'GitlabTest_Build'
- find_field('Username').value.should == 'user'
+ expect(find_field('Teamcity url').value).to eq 'http://teamcity.example.com'
+ expect(find_field('Build type').value).to eq 'GitlabTest_Build'
+ expect(find_field('Username').value).to eq 'user'
end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 343aeb53b11..db8ad08bb9e 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -30,19 +30,19 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I should see "Snippet one" in snippets' do
- page.should have_content "Snippet one"
+ expect(page).to have_content "Snippet one"
end
step 'I should not see "Snippet two" in snippets' do
- page.should_not have_content "Snippet two"
+ expect(page).not_to have_content "Snippet two"
end
step 'I should not see "Snippet one" in snippets' do
- page.should_not have_content "Snippet one"
+ expect(page).not_to have_content "Snippet one"
end
step 'I click link "Edit"' do
- within ".file-title" do
+ page.within ".file-title" do
click_link "Edit"
end
end
@@ -52,37 +52,37 @@ 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"
- within('.file-editor') do
+ 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
click_button "Create snippet"
end
step 'I should see snippet "Snippet three"' do
- page.should have_content "Snippet three"
- page.should have_content "Content of snippet three"
+ expect(page).to have_content "Snippet three"
+ expect(page).to have_content "Content of snippet three"
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
step 'I should see "Snippet new title"' do
- page.should have_content "Snippet new title"
+ expect(page).to have_content "Snippet new title"
end
step 'I leave a comment like "Good snippet!"' do
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
fill_in "note_note", with: "Good snippet!"
click_button "Add Comment"
end
end
step 'I should see comment "Good snippet!"' do
- page.should have_content "Good snippet!"
+ expect(page).to have_content "Good snippet!"
end
step 'I visit snippet page "Snippet one"' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index caf6c73ee06..398c9bf5756 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -5,23 +5,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
include RepoHelpers
step 'I should see files from repository' do
- page.should have_content "VERSION"
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
+ expect(page).to have_content "VERSION"
+ expect(page).to have_content ".gitignore"
+ expect(page).to have_content "LICENSE"
end
step 'I should see files from repository for "6d39438"' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "6d39438")
- page.should have_content ".gitignore"
- page.should have_content "LICENSE"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ expect(page).to have_content ".gitignore"
+ expect(page).to have_content "LICENSE"
end
step 'I see the ".gitignore"' do
- page.should have_content '.gitignore'
+ expect(page).to have_content '.gitignore'
end
step 'I don\'t see the ".gitignore"' do
- page.should_not have_content '.gitignore'
+ expect(page).not_to have_content '.gitignore'
end
step 'I click on ".gitignore" file in repo' do
@@ -29,11 +29,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see its content' do
- page.should have_content old_gitignore_content
+ expect(page).to have_content old_gitignore_content
end
step 'I should see its new content' do
- page.should have_content new_gitignore_content
+ expect(page).to have_content new_gitignore_content
end
step 'I click link "Raw"' do
@@ -41,7 +41,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see raw file content' do
- source.should == sample_blob.data
+ expect(source).to eq sample_blob.data
end
step 'I click button "Edit"' do
@@ -49,16 +49,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I cannot see the edit button' do
- page.should_not have_link 'edit'
+ expect(page).not_to have_link 'edit'
end
step 'The edit button is disabled' do
- page.should have_css '.disabled', text: 'Edit'
+ expect(page).to have_css '.disabled', text: 'Edit'
end
step 'I can edit code' do
set_new_content
- evaluate_script('blob.editor.getValue()').should == new_gitignore_content
+ expect(evaluate_script('blob.editor.getValue()')).to eq new_gitignore_content
end
step 'I edit code' do
@@ -98,7 +98,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I see diff' do
- page.should have_css '.line_holder.new'
+ expect(page).to have_css '.line_holder.new'
end
step 'I click on "new file" link in repo' do
@@ -106,8 +106,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I can see new file page' do
- page.should have_content "New file"
- page.should have_content "Commit message"
+ expect(page).to have_content "New file"
+ expect(page).to have_content "Commit message"
end
step 'I click on files directory' do
@@ -119,25 +119,25 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I see Browse dir link' do
- page.should have_link 'Browse Dir »'
- page.should_not have_link 'Browse Code »'
+ expect(page).to have_link 'Browse Dir »'
+ expect(page).not_to have_link 'Browse Code »'
end
step 'I click on readme file' do
- within '.tree-table' do
+ page.within '.tree-table' do
click_link 'README.md'
end
end
step 'I see Browse file link' do
- page.should have_link 'Browse File »'
- page.should_not have_link 'Browse Code »'
+ expect(page).to have_link 'Browse File »'
+ expect(page).not_to have_link 'Browse Code »'
end
step 'I see Browse code link' do
- page.should have_link 'Browse Code »'
- page.should_not have_link 'Browse File »'
- page.should_not have_link 'Browse Dir »'
+ expect(page).to have_link 'Browse Code »'
+ expect(page).not_to have_link 'Browse File »'
+ expect(page).not_to have_link 'Browse Dir »'
end
step 'I click on Permalink' do
@@ -145,7 +145,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I am redirected to the files URL' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, 'master')
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, 'master')
end
step 'I am redirected to the ".gitignore"' do
diff --git a/features/steps/project/source/git_blame.rb b/features/steps/project/source/git_blame.rb
index e29a816c51b..d0a27f47e2a 100644
--- a/features/steps/project/source/git_blame.rb
+++ b/features/steps/project/source/git_blame.rb
@@ -12,8 +12,8 @@ class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps
end
step 'I should see git file blame' do
- page.should have_content "*.rb"
- page.should have_content "Dmitriy Zaporozhets"
- page.should have_content "Initial commit"
+ expect(page).to have_content "*.rb"
+ expect(page).to have_content "Dmitriy Zaporozhets"
+ expect(page).to have_content "Initial commit"
end
end
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 7961fdedad8..c78e86fa1a7 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -13,19 +13,19 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see files from repository in markdown' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
- page.should have_content "README.md"
- page.should have_content "CHANGELOG"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(page).to have_content "README.md"
+ expect(page).to have_content "CHANGELOG"
end
step 'I should see rendered README which contains correct links' do
- page.should have_content "Welcome to GitLab GitLab is a free project and repository management application"
- page.should have_link "GitLab API doc"
- page.should have_link "GitLab API website"
- page.should have_link "Rake tasks"
- page.should have_link "backup and restore procedure"
- page.should have_link "GitLab API doc directory"
- page.should have_link "Maintenance"
+ expect(page).to have_content "Welcome to GitLab GitLab is a free project and repository management application"
+ expect(page).to have_link "GitLab API doc"
+ expect(page).to have_link "GitLab API website"
+ expect(page).to have_link "Rake tasks"
+ expect(page).to have_link "backup and restore procedure"
+ expect(page).to have_link "GitLab API doc directory"
+ expect(page).to have_link "Maintenance"
end
step 'I click on Gitlab API in README' do
@@ -33,8 +33,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- page.should have_content "All API requests require authentication"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(page).to have_content "All API requests require authentication"
end
step 'I click on Rake tasks in README' do
@@ -42,9 +42,9 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct directory rendered' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(page).to have_content "backup_restore.md"
+ expect(page).to have_content "maintenance.md"
end
step 'I click on GitLab API doc directory in README' do
@@ -52,9 +52,9 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct doc/api directory rendered' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
- page.should have_content "README.md"
- page.should have_content "users.md"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ expect(page).to have_content "README.md"
+ expect(page).to have_content "users.md"
end
step 'I click on Maintenance in README' do
@@ -62,41 +62,41 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct maintenance file rendered' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
- page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
+ expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
step 'I click on link "empty" in the README' do
- within('.readme-holder') do
+ page.within('.readme-holder') do
click_link "empty"
end
end
step 'I click on link "id" in the README' do
- within('.readme-holder') do
+ page.within('.readme-holder') do
click_link "#id"
end
end
step 'I navigate to the doc/api/README' do
- within '.tree-table' do
+ page.within '.tree-table' do
click_link "doc"
end
- within '.tree-table' do
+ page.within '.tree-table' do
click_link "api"
end
- within '.tree-table' do
+ page.within '.tree-table' do
click_link "README.md"
end
end
step 'I see correct file rendered' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- page.should have_content "Contents"
- page.should have_link "Users"
- page.should have_link "Rake tasks"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(page).to have_content "Contents"
+ expect(page).to have_link "Users"
+ expect(page).to have_link "Rake tasks"
end
step 'I click on users in doc/api/README' do
@@ -104,8 +104,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct document file' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
- page.should have_content "Get a list of users."
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(page).to have_content "Get a list of users."
end
step 'I click on raketasks in doc/api/README' do
@@ -131,32 +131,32 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see files from repository in markdown branch' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
- page.should have_content "README.md"
- page.should have_content "CHANGELOG"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(page).to have_content "README.md"
+ expect(page).to have_content "CHANGELOG"
end
step 'I see correct file rendered in markdown branch' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- page.should have_content "Contents"
- page.should have_link "Users"
- page.should have_link "Rake tasks"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(page).to have_content "Contents"
+ expect(page).to have_link "Users"
+ expect(page).to have_link "Rake tasks"
end
step 'I should see correct document rendered for markdown branch' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- page.should have_content "All API requests require authentication"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(page).to have_content "All API requests require authentication"
end
step 'I should see correct directory rendered for markdown branch' do
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
- page.should have_content "backup_restore.md"
- page.should have_content "maintenance.md"
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(page).to have_content "backup_restore.md"
+ expect(page).to have_content "maintenance.md"
end
step 'I should see the users document file in markdown branch' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
- page.should have_content "Get a list of users."
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(page).to have_content "Get a list of users."
end
# Expected link contents
@@ -208,7 +208,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I go to wiki page' do
click_link "Wiki"
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
end
step 'I add various links to the wiki page' do
@@ -218,8 +218,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'Wiki page should have added links' do
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
- page.should have_content "test GitLab API doc Rake tasks"
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(page).to have_content "test GitLab API doc Rake tasks"
end
step 'I add a header to the wiki page' do
@@ -237,13 +237,13 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see new wiki page named test' do
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "test")
- page.should have_content "Editing"
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
+ expect(page).to have_content "Editing"
end
When 'I go back to wiki page home' do
visit namespace_project_wiki_path(@project.namespace, @project, "home")
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
end
step 'I click on GitLab API doc link' do
@@ -251,8 +251,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Gitlab API document' do
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "api")
- page.should have_content "Editing"
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
+ expect(page).to have_content "Editing"
end
step 'I click on Rake tasks link' do
@@ -260,13 +260,13 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Rake tasks directory' do
- current_path.should == namespace_project_wiki_path(@project.namespace, @project, "raketasks")
- page.should have_content "Editing"
+ expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
+ expect(page).to have_content "Editing"
end
step 'I go directory which contains README file' do
visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
- current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
end
step 'I click on a relative link in README' do
@@ -274,8 +274,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct markdown' do
- current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
- page.should have_content "List users"
+ expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(page).to have_content "List users"
end
step 'Header "Application details" should have correct id and link' do
diff --git a/features/steps/project/source/multiselect_blob.rb b/features/steps/project/source/multiselect_blob.rb
deleted file mode 100644
index b749ba49371..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
- URI.parse(current_url).fragment.should == fragment
- end
-
- step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do
- ids = Array(range).map { |n| "LC#{n}" }
- extra = false
-
- highlighted = all("#tree-content-holder .highlight .line.hll")
- highlighted.each do |element|
- extra ||= ids.delete(element[:id]).nil?
- end
-
- extra.should 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/source/search_code.rb b/features/steps/project/source/search_code.rb
index b66c5a4123a..feee756d7ec 100644
--- a/features/steps/project/source/search_code.rb
+++ b/features/steps/project/source/search_code.rb
@@ -9,11 +9,11 @@ class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps
end
step 'I should see files from repository containing "coffee"' do
- page.should have_content 'coffee'
- page.should have_content 'CONTRIBUTING.md'
+ expect(page).to have_content 'coffee'
+ expect(page).to have_content 'CONTRIBUTING.md'
end
step 'I should see empty result' do
- page.should have_content "We couldn't find any"
+ expect(page).to have_content "We couldn't find any"
end
end
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index 50cdfd73c34..8b50bfcef04 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
- page.should_not have_content '.star-buttons'
+ expect(page).not_to have_content '.star-buttons'
end
step "The project has 0 stars" do
@@ -26,7 +26,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
end
step 'I redirected to sign in page' do
- current_path.should == new_user_session_path
+ expect(current_path).to eq new_user_session_path
end
protected
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index 09e5af3ef48..97d63016458 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -5,14 +5,14 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
include Select2Helper
step 'I should be able to see myself in team' do
- page.should have_content(@user.name)
- page.should have_content(@user.username)
+ expect(page).to have_content(@user.name)
+ expect(page).to have_content(@user.username)
end
step 'I should see "Dmitriy" in team list' do
user = User.find_by(name: "Dmitriy")
- page.should have_content(user.name)
- page.should have_content(user.username)
+ expect(page).to have_content(user.name)
+ expect(page).to have_content(user.username)
end
step 'I click link "Add members"' do
@@ -22,7 +22,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
- within ".users-project-form" do
+ page.within ".users-project-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
@@ -30,13 +30,13 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I should see "Mike" in team list as "Reporter"' do
- within ".access-reporter" do
- page.should have_content('Mike')
+ page.within ".access-reporter" do
+ expect(page).to have_content('Mike')
end
end
step 'I select "sjobs@apple.com" as "Reporter"' do
- within ".users-project-form" do
+ page.within ".users-project-form" do
select2("sjobs@apple.com", from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
@@ -44,16 +44,16 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
- within ".access-reporter" do
- page.should have_content('sjobs@apple.com')
- page.should have_content('invited')
- page.should have_content('Reporter')
+ page.within ".access-reporter" do
+ expect(page).to have_content('sjobs@apple.com')
+ expect(page).to have_content('invited')
+ expect(page).to have_content('Reporter')
end
end
step 'I should see "Dmitriy" in team list as "Developer"' do
- within ".access-developer" do
- page.should have_content('Dmitriy')
+ page.within ".access-developer" do
+ expect(page).to have_content('Dmitriy')
end
end
@@ -61,7 +61,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
project = Project.find_by(name: "Shop")
user = User.find_by(name: 'Dmitriy')
project_member = project.project_members.find_by(user_id: user.id)
- within "#project_member_#{project_member.id}" do
+ page.within "#project_member_#{project_member.id}" do
click_button "Edit access level"
select "Reporter", from: "project_member_access_level"
click_button "Save"
@@ -69,8 +69,8 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I should see "Dmitriy" in team list as "Reporter"' do
- within ".access-reporter" do
- page.should have_content('Dmitriy')
+ page.within ".access-reporter" do
+ expect(page).to have_content('Dmitriy')
end
end
@@ -80,8 +80,8 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
step 'I should not see "Dmitriy" in team list' do
user = User.find_by(name: "Dmitriy")
- page.should_not have_content(user.name)
- page.should_not have_content(user.username)
+ expect(page).not_to have_content(user.name)
+ expect(page).not_to have_content(user.username)
end
step 'gitlab user "Mike"' do
@@ -123,7 +123,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
project = Project.find_by(name: "Shop")
user = User.find_by(name: 'Dmitriy')
project_member = project.project_members.find_by(user_id: user.id)
- within "#project_member_#{project_member.id}" do
+ page.within "#project_member_#{project_member.id}" do
click_link('Remove user from team')
end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 717132da45d..eebfaee1ede 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -6,13 +6,13 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
include WikiHelper
step 'I click on the Cancel button' do
- within(:css, ".form-actions") do
+ page.within(:css, ".form-actions") do
click_on "Cancel"
end
end
step 'I should be redirected back to the Edit Home Wiki page' do
- current_path.should == namespace_project_wiki_path(project.namespace, project, :home)
+ expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, :home)
end
step 'I create the Wiki Home page' do
@@ -21,11 +21,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the newly created wiki page' do
- page.should have_content "Home"
- page.should have_content "link test"
+ expect(page).to have_content "Home"
+ expect(page).to have_content "link test"
click_link "link test"
- page.should have_content "Editing"
+ expect(page).to have_content "Editing"
end
step 'I have an existing Wiki page' do
@@ -47,11 +47,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the updated content' do
- page.should have_content "Updated Wiki Content"
+ expect(page).to have_content "Updated Wiki Content"
end
step 'I should be redirected back to that Wiki page' do
- current_path.should == namespace_project_wiki_path(project.namespace, project, @page)
+ expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, @page)
end
step 'That page has two revisions' do
@@ -63,9 +63,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see both revisions' do
- page.should have_content current_user.name
- page.should have_content "first commit"
- page.should have_content "second commit"
+ expect(page).to have_content current_user.name
+ expect(page).to have_content "first commit"
+ expect(page).to have_content "second commit"
end
step 'I click on the "Delete this page" button' do
@@ -73,7 +73,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'The page should be deleted' do
- page.should have_content "Page was successfully deleted"
+ expect(page).to have_content "Page was successfully deleted"
end
step 'I click on the "Pages" button' do
@@ -81,8 +81,8 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the existing page in the pages list' do
- page.should have_content current_user.name
- page.should have_content @page.title
+ expect(page).to have_content current_user.name
+ expect(page).to have_content @page.title
end
step 'I have an existing Wiki page with images linked on page' do
@@ -98,30 +98,30 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
file = Gollum::File.new(wiki.wiki)
Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
- page.should have_link('image', href: "image.jpg")
+ expect(page).to have_link('image', href: "image.jpg")
click_on "image"
end
step 'I should see the image from wiki repo' do
- current_path.should match('wikis/image.jpg')
- page.should_not have_xpath('/html') # Page should render the image which means there is no html involved
+ expect(current_path).to match('wikis/image.jpg')
+ expect(page).not_to have_xpath('/html') # Page should render the image which means there is no html involved
Gollum::Wiki.any_instance.unstub(:file)
Gollum::File.any_instance.unstub(:mime_type)
end
step 'Image should be shown on the page' do
- page.should have_xpath("//img[@src=\"image.jpg\"]")
+ expect(page).to have_xpath("//img[@src=\"image.jpg\"]")
end
step 'I click on image link' do
- page.should have_link('image', href: "image.jpg")
+ expect(page).to have_link('image', href: "image.jpg")
click_on "image"
end
step 'I should see the new wiki page form' do
- current_path.should match('wikis/image.jpg')
- page.should have_content('New Wiki Page')
- page.should have_content('Editing - image.jpg')
+ expect(current_path).to match('wikis/image.jpg')
+ expect(page).to have_content('New Wiki Page')
+ expect(page).to have_content('Editing - image.jpg')
end
step 'I create a New page with paths' do
@@ -130,11 +130,21 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
click_on 'Build'
fill_in "wiki_content", with: 'wiki content'
click_on "Create page"
- current_path.should include 'one/two/three'
+ expect(current_path).to include 'one/two/three'
+ end
+
+ step 'I create a New page with an invalid name' do
+ click_on 'New Page'
+ fill_in 'Page slug', with: 'invalid name'
+ click_on 'Build'
+ end
+
+ step 'I should see an error message' do
+ expect(page).to have_content "The page slug is invalid"
end
step 'I should see non-escaped link in the pages list' do
- page.should have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']")
+ expect(page).to have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']")
end
step 'I edit the Wiki page with a path' do
@@ -143,11 +153,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see a non-escaped path' do
- current_path.should include 'one/two/three'
+ expect(current_path).to include 'one/two/three'
end
step 'I should see the Editing page' do
- page.should have_content('Editing')
+ expect(page).to have_content('Editing')
end
step 'I view the page history of a Wiki page that has a path' do
@@ -156,7 +166,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the page history' do
- page.should have_content('History for')
+ expect(page).to have_content('History for')
end
step 'I search for Wiki content' do
diff --git a/features/steps/search.rb b/features/steps/search.rb
index 8197cd410aa..87893aa0205 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -24,35 +24,37 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I click "Issues" link' do
- within '.search-filter' do
+ page.within '.search-filter' do
click_link 'Issues'
end
end
step 'I click project "Shop" link' do
- within '.project-filter' do
+ page.within '.project-filter' do
click_link project.name_with_namespace
end
end
step 'I click "Merge requests" link' do
- within '.search-filter' do
+ page.within '.search-filter' do
click_link 'Merge requests'
end
end
step 'I click "Wiki" link' do
- within '.search-filter' do
+ page.within '.search-filter' do
click_link 'Wiki'
end
end
step 'I should see "Shop" project link' do
- page.should have_link "Shop"
+ expect(page).to have_link "Shop"
end
step 'I should see code results for project "Shop"' do
- page.should have_content 'Update capybara, rspec-rails, poltergeist to recent versions'
+ 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,15 +73,19 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I should see "Foo" link in the search results' do
- find(:css, '.search-results').should 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
- find(:css, '.search-results').should_not have_link 'Bar'
+ expect(find(:css, '.search-results')).not_to have_link 'Bar'
end
step 'I should see "test_wiki" link in the search results' do
- find(:css, '.search-results').should 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/active_tab.rb b/features/steps/shared/active_tab.rb
index 9beb688bd16..72d873caa57 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -2,27 +2,27 @@ module SharedActiveTab
include Spinach::DSL
def ensure_active_main_tab(content)
- find('.nav-sidebar > li.active').should have_content(content)
+ expect(find('.nav-sidebar > li.active')).to have_content(content)
end
def ensure_active_sub_tab(content)
- find('div.content ul.nav-tabs li.active').should have_content(content)
+ expect(find('div.content ul.nav-tabs li.active')).to have_content(content)
end
def ensure_active_sub_nav(content)
- find('.sidebar-subnav > li.active').should have_content(content)
+ expect(find('.sidebar-subnav > li.active')).to have_content(content)
end
step 'no other main tabs should be active' do
- page.should have_selector('.nav-sidebar > li.active', count: 1)
+ expect(page).to have_selector('.nav-sidebar > li.active', count: 1)
end
step 'no other sub tabs should be active' do
- page.should have_selector('div.content ul.nav-tabs li.active', count: 1)
+ expect(page).to have_selector('div.content ul.nav-tabs li.active', count: 1)
end
step 'no other sub navs should be active' do
- page.should have_selector('.sidebar-subnav > li.active', count: 1)
+ expect(page).to have_selector('.sidebar-subnav > li.active', count: 1)
end
step 'the active main tab should be Home' 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 ac8a3df6bb9..735e0ef6108 100644
--- a/features/steps/shared/authentication.rb
+++ b/features/steps/shared/authentication.rb
@@ -21,13 +21,17 @@ module SharedAuthentication
end
step 'I should be redirected to sign in page' do
- current_path.should == new_user_session_path
+ expect(current_path).to eq new_user_session_path
end
step "I logout" do
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 510e0f0f938..27a95aeb19a 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -3,7 +3,7 @@ module SharedDiffNote
include RepoHelpers
step 'I cancel the diff comment' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
find(".js-close-discussion-note-form").click
end
end
@@ -14,154 +14,206 @@ module SharedDiffNote
end
step 'I haven\'t written any diff comment text' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
fill_in "note[note]", with: ""
end
end
step 'I leave a diff comment like "Typo, please fix"' do
- click_diff_line(sample_commit.line_code)
- within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
- fill_in "note[note]", with: "Typo, please fix"
+ 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
+
+ step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
+ click_parallel_diff_line(sample_commit.line_code, 'old')
+ page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ fill_in "note[note]", with: "Old comment"
+ find(".js-comment-button").trigger("click")
+ end
+ end
+
+ step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
+ click_parallel_diff_line(sample_commit.line_code, 'new')
+ page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click")
- sleep 0.05
end
end
step 'I preview a diff comment text like "Should fix it :smile:"' do
- click_diff_line(sample_commit.line_code)
- within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
- fill_in "note[note]", with: "Should fix it :smile:"
- find('.js-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)
- 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
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
fill_in "note[note]", with: ":-1: I don\'t like this"
end
end
step 'I submit the diff comment' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
click_button("Add Comment")
end
end
step 'I should not see the diff comment form' do
- within(diff_file_selector) do
- page.should_not have_css("form.new_note")
+ page.within(diff_file_selector) do
+ expect(page).not_to have_css("form.new_note")
end
end
step 'The diff comment preview tab should say there is nothing to do' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should not see the diff comment text field' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
expect(find('.js-note-text')).not_to be_visible
end
end
step 'I should only see one diff form' do
- within(diff_file_selector) do
- page.should have_css("form.new_note", count: 1)
+ page.within(diff_file_selector) do
+ expect(page).to have_css("form.new_note", count: 1)
end
end
step 'I should see a diff comment form with ":-1: I don\'t like this"' do
- within(diff_file_selector) do
- page.should have_field("note[note]", with: ":-1: I don\'t like this")
+ page.within(diff_file_selector) do
+ expect(page).to have_field("note[note]", with: ":-1: I don\'t like this")
end
end
step 'I should see a diff comment saying "Typo, please fix"' do
- within("#{diff_file_selector} .note") do
- page.should have_content("Typo, please fix")
+ page.within("#{diff_file_selector} .note") do
+ expect(page).to have_content("Typo, please fix")
+ end
+ end
+
+ step 'I should see a diff comment on the left side saying "Old comment"' do
+ page.within("#{diff_file_selector} .notes_content.parallel.old") do
+ expect(page).to have_content("Old comment")
+ end
+ end
+
+ step 'I should see a diff comment on the right side saying "New comment"' do
+ page.within("#{diff_file_selector} .notes_content.parallel.new") do
+ expect(page).to have_content("New comment")
end
end
step 'I should see a discussion reply button' do
- within(diff_file_selector) do
- page.should have_button('Reply')
+ page.within(diff_file_selector) do
+ expect(page).to have_button('Reply')
end
end
step 'I should see a temporary diff comment form' do
- within(diff_file_selector) do
- page.should have_css(".js-temp-notes-holder form.new_note")
+ page.within(diff_file_selector) do
+ expect(page).to have_css(".js-temp-notes-holder form.new_note")
end
end
step 'I should see add a diff comment button' do
- page.should have_css('.js-add-diff-note-button', visible: true)
+ expect(page).to have_css('.js-add-diff-note-button', visible: true)
end
step 'I should see an empty diff comment form' do
- within(diff_file_selector) do
- page.should have_field("note[note]", with: "")
+ page.within(diff_file_selector) do
+ expect(page).to have_field("note[note]", with: "")
end
end
step 'I should see the cancel comment button' do
- within("#{diff_file_selector} form") do
- page.should have_css(".js-close-discussion-note-form", text: "Cancel")
+ page.within("#{diff_file_selector} form") do
+ expect(page).to have_css(".js-close-discussion-note-form", text: "Cancel")
end
end
step 'I should see the diff comment preview' do
- within("#{diff_file_selector} form") do
+ page.within("#{diff_file_selector} form") do
expect(page).to have_css('.js-md-preview', visible: true)
end
end
step 'I should see the diff comment write tab' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
expect(page).to have_css('.js-md-write-button', visible: true)
end
end
step 'The diff comment preview tab should display rendered Markdown' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I should see two separate previews' do
- within(diff_file_selector) do
+ page.within(diff_file_selector) do
expect(page).to have_css('.js-md-preview', visible: true, count: 2)
expect(page).to have_content('Should fix it')
expect(page).to have_content('DRY this up')
end
end
+ step 'I click side-by-side diff button' do
+ click_link "Side-by-side"
+ end
+
+ step 'I see side-by-side diff button' do
+ expect(page).to have_content "Side-by-side"
+ end
+
def diff_file_selector
- ".diff-file:nth-of-type(1)"
+ '.diff-file:nth-of-type(1)'
end
def click_diff_line(code)
find("button[data-line-code='#{code}']").click
end
+
+ def click_parallel_diff_line(code, line_type)
+ find("button[data-line-code='#{code}'][data-line-type='#{line_type}']").trigger('click')
+ end
end
diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb
index 1b225dd61a6..2d17fb34ccb 100644
--- a/features/steps/shared/group.rb
+++ b/features/steps/shared/group.rb
@@ -22,11 +22,11 @@ module SharedGroup
end
step 'I should see group "TestGroup"' do
- page.should have_content "TestGroup"
+ expect(page).to have_content "TestGroup"
end
step 'I should not see group "TestGroup"' do
- page.should_not have_content "TestGroup"
+ expect(page).not_to have_content "TestGroup"
end
protected
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/markdown.rb b/features/steps/shared/markdown.rb
index 943640007a9..56b36f7c46c 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -3,11 +3,11 @@ module SharedMarkdown
def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki")
node = find("#{parent} h#{level} a##{id}")
- node[:href].should == "##{id}"
+ expect(node[:href]).to eq "##{id}"
# Work around a weird Capybara behavior where calling `parent` on a node
# returns the whole document, not the node's actual parent element
- find(:xpath, "#{node.path}/..").text.should == text
+ expect(find(:xpath, "#{node.path}/..").text).to eq text
end
step 'Header "Description header" should have correct id and link' do
@@ -19,7 +19,7 @@ module SharedMarkdown
end
step 'The Markdown preview tab should say there is nothing to do' do
- within('.gfm-form') do
+ page.within('.gfm-form') do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
@@ -38,7 +38,7 @@ module SharedMarkdown
end
step 'The Markdown preview tab should display rendered Markdown' do
- within('.gfm-form') do
+ page.within('.gfm-form') do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
@@ -49,7 +49,7 @@ module SharedMarkdown
end
step 'I preview a description text like "Bug fixed :smile:"' do
- within('.gfm-form') do
+ page.within('.gfm-form') do
fill_in 'Description', with: 'Bug fixed :smile:'
find('.js-md-preview-button').click
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 2f66e61b214..f6aabfefeff 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -2,113 +2,114 @@ 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
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in "note[note]", with: ""
end
end
step 'I leave a comment like "XML attached"' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in "note[note]", with: "XML attached"
click_button "Add Comment"
- sleep 0.05
end
end
step 'I preview a comment text like "Bug fixed :smile:"' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:"
find('.js-md-preview-button').click
end
end
step 'I submit the comment' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
click_button "Add Comment"
end
end
step 'I write a comment like ":+1: Nice"' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in 'note[note]', with: ':+1: Nice'
end
end
step 'I should not see a comment saying "XML attached"' do
- page.should_not have_css(".note")
+ expect(page).not_to have_css(".note")
end
step 'I should not see the cancel comment button' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
should_not have_link("Cancel")
end
end
step 'I should not see the comment preview' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
expect(find('.js-md-preview')).not_to be_visible
end
end
step 'The comment preview tab should say there is nothing to do' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should not see the comment text field' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
expect(find('.js-note-text')).not_to be_visible
end
end
step 'I should see a comment saying "XML attached"' do
- within(".note") do
- page.should have_content("XML attached")
+ page.within(".note") do
+ expect(page).to have_content("XML attached")
end
end
step 'I should see an empty comment text field' do
- within(".js-main-target-form") do
- page.should have_field("note[note]", with: "")
+ page.within(".js-main-target-form") do
+ expect(page).to have_field("note[note]", with: "")
end
end
step 'I should see the comment write tab' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-write-button', visible: true)
end
end
step 'The comment preview tab should be display rendered Markdown' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I should see the comment preview' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-preview', visible: true)
end
end
step 'I should see comment "XML attached"' do
- within(".note") do
- page.should have_content("XML attached")
+ page.within(".note") do
+ expect(page).to have_content("XML attached")
end
end
# Markdown
step 'I leave a comment with a header containing "Comment with a header"' do
- within(".js-main-target-form") do
+ page.within(".js-main-target-form") do
fill_in "note[note]", with: "# Comment with a header"
click_button "Add Comment"
sleep 0.05
@@ -116,26 +117,27 @@ module SharedNote
end
step 'The comment with the header should not have an ID' do
- within(".note-body > .note-text") do
- page.should have_content("Comment with a header")
- page.should_not have_css("#comment-with-a-header")
+ page.within(".note-body > .note-text") do
+ expect(page).to have_content("Comment with a header")
+ expect(page).not_to have_css("#comment-with-a-header")
end
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
- within(".current-note-edit-form") do
+ page.within(".current-note-edit-form") do
fill_in 'note[note]', with: '+1 Awesome!'
click_button 'Save Comment'
- sleep 0.05
end
end
step 'I should see +1 in the description' do
- within(".note") do
- page.should have_content("+1 Awesome!")
+ page.within(".note") do
+ expect(page).to have_content("+1 Awesome!")
end
end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index d9401bd540c..4cc01443c8b 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
# ----------------------------------------
@@ -92,7 +92,7 @@ module SharedPaths
end
step 'I should be redirected to the dashboard groups page' do
- current_path.should == dashboard_groups_path
+ expect(current_path).to eq dashboard_groups_path
end
step 'I visit dashboard starred projects page' do
@@ -123,8 +123,8 @@ module SharedPaths
visit profile_keys_path
end
- step 'I visit profile design page' do
- visit design_profile_path
+ step 'I visit profile preferences page' do
+ visit profile_preferences_path
end
step 'I visit profile history page' do
@@ -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
@@ -227,6 +227,10 @@ module SharedPaths
visit namespace_project_merge_requests_path(@project.namespace, @project)
end
+ step "I visit my project's members page" do
+ visit namespace_project_project_members_path(@project.namespace, @project)
+ end
+
step "I visit my project's wiki page" do
visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
@@ -268,11 +272,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
@@ -288,11 +292,11 @@ module SharedPaths
end
step 'I am on the new file page' do
- current_path.should eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+ expect(current_path).to eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
end
step 'I am on the ".gitignore" edit file page' do
- current_path.should eq(namespace_project_edit_blob_path(
+ expect(current_path).to eq(namespace_project_edit_blob_path(
@project.namespace, @project, File.join(root_ref, '.gitignore')))
end
@@ -353,6 +357,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
@@ -414,13 +423,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
@@ -455,6 +464,6 @@ module SharedPaths
# ----------------------------------------
step 'page status code should be 404' do
- status_code.should == 404
+ expect(status_code).to eq 404
end
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 24136fe421c..9ee2e5dfbed 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -14,6 +14,11 @@ module SharedProject
@project.team << [@user, :master]
end
+ step 'I disable snippets in project' do
+ @project.snippets_enabled = false
+ @project.save
+ end
+
step 'I disable issues and merge requests in project' do
@project.issues_enabled = false
@project.merge_requests_enabled = false
@@ -74,13 +79,13 @@ module SharedProject
step 'I should see project "Shop" activity feed' do
project = Project.find_by(name: "Shop")
- page.should have_content "#{@user.name} pushed new branch fix at #{project.name_with_namespace}"
+ expect(page).to have_content "#{@user.name} pushed new branch fix at #{project.name_with_namespace}"
end
step 'I should see project settings' do
- current_path.should == edit_namespace_project_path(@project.namespace, @project)
- page.should have_content("Project name")
- page.should have_content("Features:")
+ expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
+ expect(page).to have_content("Project name")
+ expect(page).to have_content("Features:")
end
def current_project
@@ -96,11 +101,11 @@ module SharedProject
end
step 'I should see project "Enterprise"' do
- page.should have_content "Enterprise"
+ expect(page).to have_content "Enterprise"
end
step 'I should not see project "Enterprise"' do
- page.should_not have_content "Enterprise"
+ expect(page).not_to have_content "Enterprise"
end
step 'internal project "Internal"' do
@@ -108,11 +113,11 @@ module SharedProject
end
step 'I should see project "Internal"' do
- page.should have_content "Internal"
+ expect(page).to have_content "Internal"
end
step 'I should not see project "Internal"' do
- page.should_not have_content "Internal"
+ expect(page).not_to have_content "Internal"
end
step 'public project "Community"' do
@@ -120,11 +125,11 @@ module SharedProject
end
step 'I should see project "Community"' do
- page.should have_content "Community"
+ expect(page).to have_content "Community"
end
step 'I should not see project "Community"' do
- page.should_not have_content "Community"
+ expect(page).not_to have_content "Community"
end
step '"John Doe" owns private project "Enterprise"' do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index c5aed19331c..3b94b7d8621 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -28,6 +28,10 @@ module SharedProjectTab
ensure_active_main_tab('Issues')
end
+ step 'the active main tab should be Members' do
+ ensure_active_main_tab('Members')
+ end
+
step 'the active main tab should be Merge Requests' do
ensure_active_main_tab('Merge Requests')
end
@@ -41,8 +45,8 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- within '.nav-sidebar' do
- page.should have_content('Back to project')
+ page.within '.nav-sidebar' do
+ expect(page).to have_content('Back to project')
end
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/snippet_search.rb b/features/steps/snippet_search.rb
index 669c7186c1b..cf999879579 100644
--- a/features/steps/snippet_search.rb
+++ b/features/steps/snippet_search.rb
@@ -18,39 +18,39 @@ class Spinach::Features::SnippetSearch < Spinach::FeatureSteps
end
step 'I should see "line seven" in results' do
- page.should have_content 'line seven'
+ expect(page).to have_content 'line seven'
end
step 'I should see "line four" in results' do
- page.should have_content 'line four'
+ expect(page).to have_content 'line four'
end
step 'I should see "line ten" in results' do
- page.should have_content 'line ten'
+ expect(page).to have_content 'line ten'
end
step 'I should not see "line eleven" in results' do
- page.should_not have_content 'line eleven'
+ expect(page).not_to have_content 'line eleven'
end
step 'I should not see "line three" in results' do
- page.should_not have_content 'line three'
+ expect(page).not_to have_content 'line three'
end
step 'I should see "Personal snippet one" in results' do
- page.should have_content 'Personal snippet one'
+ expect(page).to have_content 'Personal snippet one'
end
step 'I should see "Personal snippet private" in results' do
- page.should have_content 'Personal snippet private'
+ expect(page).to have_content 'Personal snippet private'
end
step 'I should not see "Personal snippet one" in results' do
- page.should_not have_content 'Personal snippet one'
+ expect(page).not_to have_content 'Personal snippet one'
end
step 'I should not see "Personal snippet private" in results' do
- page.should_not have_content 'Personal snippet private'
+ expect(page).not_to have_content 'Personal snippet private'
end
end
diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb
index 2667c1e3d44..76379d09d02 100644
--- a/features/steps/snippets/discover.rb
+++ b/features/steps/snippets/discover.rb
@@ -4,15 +4,15 @@ class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps
include SharedSnippet
step 'I should see "Personal snippet one" in snippets' do
- page.should have_content "Personal snippet one"
+ expect(page).to have_content "Personal snippet one"
end
step 'I should see "Personal snippet internal" in snippets' do
- page.should have_content "Personal snippet internal"
+ expect(page).to have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet private" in snippets' do
- page.should_not have_content "Personal snippet private"
+ expect(page).not_to have_content "Personal snippet private"
end
def snippet
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
index 67669dc0a69..2ebdca5ed30 100644
--- a/features/steps/snippets/public_snippets.rb
+++ b/features/steps/snippets/public_snippets.rb
@@ -4,11 +4,11 @@ class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
include SharedSnippet
step 'I should see snippet "Personal snippet one"' do
- page.should have_no_xpath("//i[@class='public-snippet']")
+ expect(page).to have_no_xpath("//i[@class='public-snippet']")
end
step 'I should see raw snippet "Personal snippet one"' do
- page.should have_text(snippet.content)
+ expect(page).to have_text(snippet.content)
end
step 'I visit snippet page "Personal snippet one"' do
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index de936db85ee..6ff48e0c6b8 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -9,11 +9,11 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
step 'I should not see "Personal snippet one" in snippets' do
- page.should_not have_content "Personal snippet one"
+ expect(page).not_to have_content "Personal snippet one"
end
step 'I click link "Edit"' do
- within ".file-title" do
+ page.within ".file-title" do
click_link "Edit"
end
end
@@ -23,26 +23,38 @@ 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"
- within('.file-editor') do
+ 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
- page.should have_content "Personal snippet three"
- page.should have_content "Content of snippet three"
+ 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
step 'I should see "Personal snippet new title"' do
- page.should have_content "Personal snippet new title"
+ expect(page).to have_content "Personal snippet new title"
end
step 'I uncheck "Private" checkbox' do
@@ -51,14 +63,22 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
step 'I should see "Personal snippet one" public' do
- page.should have_no_xpath("//i[@class='public-snippet']")
+ expect(page).to have_no_xpath("//i[@class='public-snippet']")
end
step 'I visit snippet page "Personal snippet one"' do
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/steps/snippets/user.rb b/features/steps/snippets/user.rb
index 146cc535d88..007fcb2893f 100644
--- a/features/steps/snippets/user.rb
+++ b/features/steps/snippets/user.rb
@@ -8,43 +8,43 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
end
step 'I should see "Personal snippet one" in snippets' do
- page.should have_content "Personal snippet one"
+ expect(page).to have_content "Personal snippet one"
end
step 'I should see "Personal snippet private" in snippets' do
- page.should have_content "Personal snippet private"
+ expect(page).to have_content "Personal snippet private"
end
step 'I should see "Personal snippet internal" in snippets' do
- page.should have_content "Personal snippet internal"
+ expect(page).to have_content "Personal snippet internal"
end
step 'I should not see "Personal snippet one" in snippets' do
- page.should_not have_content "Personal snippet one"
+ expect(page).not_to have_content "Personal snippet one"
end
step 'I should not see "Personal snippet private" in snippets' do
- page.should_not have_content "Personal snippet private"
+ expect(page).not_to have_content "Personal snippet private"
end
step 'I should not see "Personal snippet internal" in snippets' do
- page.should_not have_content "Personal snippet internal"
+ expect(page).not_to have_content "Personal snippet internal"
end
step 'I click "Internal" filter' do
- within('.nav-tabs') do
+ page.within('.nav-tabs') do
click_link "Internal"
end
end
step 'I click "Private" filter' do
- within('.nav-tabs') do
+ page.within('.nav-tabs') do
click_link "Private"
end
end
step 'I click "Public" filter' do
- within('.nav-tabs') do
+ page.within('.nav-tabs') do
click_link "Public"
end
end
diff --git a/features/steps/user.rb b/features/steps/user.rb
index 10cae692a88..3230234cb6d 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -28,13 +28,13 @@ class Spinach::Features::User < Spinach::FeatureSteps
end
step 'I should see contributed projects' do
- within '.contributed-projects' do
- page.should have_content(@contributed_project.name)
+ page.within '.contributed-projects' do
+ expect(page).to have_content(@contributed_project.name)
end
end
step 'I should see contributions calendar' do
- page.should have_css('.cal-heatmap-container')
+ expect(page).to have_css('.cal-heatmap-container')
end
def contributed_project
diff --git a/features/support/env.rb b/features/support/env.rb
index f34302721ed..672251af084 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -9,7 +9,6 @@ end
ENV['RAILS_ENV'] = 'test'
require './config/environment'
-require 'rspec'
require 'rspec/expectations'
require 'sidekiq/testing/inline'
@@ -26,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/commits.rb b/lib/api/commits.rb
index 23270b1c0f4..f4efb651eb6 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -62,7 +62,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! 'Commit' unless commit
- notes = Note.where(commit_id: commit.id)
+ notes = Note.where(commit_id: commit.id).order(:created_at)
present paginate(notes), with: Entities::CommitNote
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b23eff3661c..14a8f929d76 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
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e0ea6d7dd1d..c7b30cf2f07 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -3,6 +3,26 @@ module API
class Files < Grape::API
before { authenticate! }
+ helpers do
+ def commit_params(attrs)
+ {
+ file_path: attrs[:file_path],
+ current_branch: attrs[:branch_name],
+ target_branch: attrs[:branch_name],
+ commit_message: attrs[:commit_message],
+ file_content: attrs[:content],
+ file_content_encoding: attrs[:encoding]
+ }
+ end
+
+ def commit_response(attrs)
+ {
+ file_path: attrs[:file_path],
+ branch_name: attrs[:branch_name],
+ }
+ end
+ end
+
resource :projects do
# Get file from repository
# File content is Base64 encoded
@@ -73,17 +93,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(201)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
@@ -105,17 +119,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status)
@@ -138,17 +146,11 @@ module API
required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f768c750402..e88b6e31775 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -62,7 +62,7 @@ module API
delete ":id" do
group = find_group(params[:id])
authorize! :admin_group, group
- group.destroy
+ DestroyGroupService.new(group, current_user).execute
end
# Transfer a project to the Group namespace
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 2216a12a87a..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]
@@ -137,7 +137,6 @@ module API
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - ID of MR
- # source_branch - The source branch
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
@@ -148,9 +147,14 @@ module API
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
+ 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?
+ render_api_error!('Source branch cannot be changed', 400)
+ end
# Validate label names in advance
if (errors = validate_label_params(params)).any?
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index b90ed6af5fb..50d3729449e 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,10 +1,7 @@
module API
# namespaces API
class Namespaces < Grape::API
- before do
- authenticate!
- authenticated_as_admin!
- end
+ before { authenticate! }
resource :namespaces do
# Get a namespaces list
@@ -12,7 +9,11 @@ module API
# Example Request:
# GET /namespaces
get do
- @namespaces = Namespace.all
+ @namespaces = if current_user.admin
+ Namespace.all
+ else
+ current_user.namespaces
+ end
@namespaces = @namespaces.search(params[:search]) if params[:search].present?
@namespaces = paginate @namespaces
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/users.rb b/lib/api/users.rb
index 032a5d76e43..9b268cfe8bc 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -194,7 +194,7 @@ module API
user = User.find_by(id: params[:id])
if user
- user.destroy
+ DeleteUserService.new(current_user).execute(user)
else
not_found!('User')
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index b69aebf9fe1..6fa2079d1a8 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -46,7 +46,8 @@ module Backup
connection = ::Fog::Storage.new(connection_settings)
directory = connection.directories.get(remote_directory)
- if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
+ if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
+ multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size)
$progress.puts "done".green
else
puts "uploading backup to #{remote_directory} failed".red
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/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 050b5ba29dd..03cef30c97d 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,4 +1,3 @@
-require_relative 'rack_attack_helpers'
require_relative 'shell_env'
module Grack
diff --git a/lib/gitlab/backend/rack_attack_helpers.rb b/lib/gitlab/backend/rack_attack_helpers.rb
deleted file mode 100644
index 8538f3f6eca..00000000000
--- a/lib/gitlab/backend/rack_attack_helpers.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# rack-attack v4.2.0 doesn't yet support clearing of keys.
-# Taken from https://github.com/kickstarter/rack-attack/issues/113
-class Rack::Attack::Allow2Ban
- def self.reset(discriminator, options)
- findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option"
-
- cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime)
- cache.delete("#{key_prefix}:ban:#{discriminator}")
- end
-end
-
-class Rack::Attack::Cache
- def reset_count(unprefixed_key, period)
- epoch_time = Time.now.to_i
- # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
- expires_in = period - (epoch_time % period) + 1
- key = "#{(epoch_time / period).to_i}:#{unprefixed_key}"
- delete(key)
- end
-
- def delete(unprefixed_key)
- store.delete("#{prefix}:#{unprefixed_key}")
- end
-end
-
-class Rack::Attack::StoreProxy::RedisStoreProxy
- def delete(key, options={})
- self.del(key)
- rescue Redis::BaseError
- end
-end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 530f9d93de4..172d4902add 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -244,6 +244,16 @@ module Gitlab
end
end
+ # Check if such directory exists in repositories.
+ #
+ # Usage:
+ # exists?('gitlab')
+ # exists?('gitlab/cookies.git')
+ #
+ def exists?(dir_name)
+ File.exists?(full_path(dir_name))
+ end
+
protected
def gitlab_shell_path
@@ -264,10 +274,6 @@ module Gitlab
File.join(repos_path, dir_name)
end
- def exists?(dir_name)
- File.exists?(full_path(dir_name))
- end
-
def gitlab_shell_projects_path
File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
end
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/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index ab184d95c05..aeec595782c 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -8,7 +8,7 @@ module Gitlab
def closed_by_message(message)
return [] if message.nil?
-
+
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
map { |ref| ref[0] }.join(" ")
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index d8f696d247b..931d51c55d3 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -21,7 +21,8 @@ module Gitlab
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
- max_attachment_size: Settings.gitlab['max_attachment_size']
+ max_attachment_size: Settings.gitlab['max_attachment_size'],
+ session_expire_delay: Settings.gitlab['session_expire_delay']
)
end
end
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/gitorious_import.rb b/lib/gitlab/gitorious_import.rb
new file mode 100644
index 00000000000..8d0132a744c
--- /dev/null
+++ b/lib/gitlab/gitorious_import.rb
@@ -0,0 +1,5 @@
+module Gitlab
+ module GitoriousImport
+ GITORIOUS_HOST = "https://gitorious.org"
+ end
+end
diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb
index 1fa89dba448..99fe5bdebfc 100644
--- a/lib/gitlab/gitorious_import/client.rb
+++ b/lib/gitlab/gitorious_import/client.rb
@@ -1,7 +1,5 @@
module Gitlab
module GitoriousImport
- GITORIOUS_HOST = "https://gitorious.org"
-
class Client
attr_reader :repo_list
diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb
index f702797dc6e..c88f1ae358d 100644
--- a/lib/gitlab/gitorious_import/repository.rb
+++ b/lib/gitlab/gitorious_import/repository.rb
@@ -1,7 +1,5 @@
module Gitlab
module GitoriousImport
- GITORIOUS_HOST = "https://gitorious.org"
-
Repository = Struct.new(:full_name) do
def id
Digest::SHA1.hexdigest(full_name)
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index c0fb22e7f36..889decc9b48 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -11,6 +11,7 @@ module Gitlab
autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter'
autoload :EmojiFilter, 'gitlab/markdown/emoji_filter'
autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter'
+ autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter'
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
@@ -53,9 +54,12 @@ module Gitlab
current_user: current_user
)
- pipeline = HTML::Pipeline.new(filters)
+ @pipeline ||= HTML::Pipeline.new(filters)
context = {
+ # SanitizationFilter
+ pipeline: options[:pipeline],
+
# EmojiFilter
asset_root: Gitlab.config.gitlab.url,
asset_host: Gitlab::Application.config.asset_host,
@@ -75,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]
@@ -103,6 +107,7 @@ module Gitlab
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
+ Gitlab::Markdown::ExternalLinkFilter,
Gitlab::Markdown::UserReferenceFilter,
Gitlab::Markdown::IssueReferenceFilter,
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index 8764f7e474f..61591a9914b 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -19,7 +19,7 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(COMMIT_RANGE_PATTERN) do |match|
+ text.gsub(CommitRange.reference_pattern) do |match|
yield match, $~[:commit_range], $~[:project]
end
end
@@ -30,13 +30,8 @@ module Gitlab
@commit_map = {}
end
- # Pattern used to extract commit range references from text
- #
- # This pattern supports cross-project references.
- COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>#{CommitRange::PATTERN})/
-
def call
- replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content|
+ replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
commit_range_link_filter(content)
end
end
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index b20b29f5d0c..f6932e76e70 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -19,20 +19,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(COMMIT_PATTERN) do |match|
+ text.gsub(Commit.reference_pattern) do |match|
yield match, $~[:commit], $~[:project]
end
end
- # Pattern used to extract commit references from text
- #
- # The SHA1 sum can be between 6 and 40 hex characters.
- #
- # This pattern supports cross-project references.
- COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit>\h{6,40})/
-
def call
- replace_text_nodes_matching(COMMIT_PATTERN) do |content|
+ replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
end
end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
index c436fabd658..66c256c5104 100644
--- a/lib/gitlab/markdown/cross_project_reference.rb
+++ b/lib/gitlab/markdown/cross_project_reference.rb
@@ -3,9 +3,6 @@ module Gitlab
# Common methods for ReferenceFilters that support an optional cross-project
# reference.
module CrossProjectReference
- NAMING_PATTERN = Gitlab::Regex::NAMESPACE_REGEX_STR
- PROJECT_PATTERN = "(?<project>#{NAMING_PATTERN}/#{NAMING_PATTERN})"
-
# Given a cross-project reference string, get the Project record
#
# Defaults to value of `context[:project]` if:
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index 0fc3f4cca06..afd28dd8cf3 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -16,19 +16,16 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(ISSUE_PATTERN) do |match|
+ text.gsub(ExternalIssue.reference_pattern) do |match|
yield match, $~[:issue]
end
end
- # Pattern used to extract `JIRA-123` issue references from text
- ISSUE_PATTERN = /(?<issue>([A-Z\-]+-)\d+)/
-
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || project.default_issues_tracker?
- replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+ replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
issue_link_filter(content)
end
end
@@ -51,7 +48,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{issue}</a>)
+ class="#{klass}">#{match}</a>)
end
end
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb
new file mode 100644
index 00000000000..c539e0fb823
--- /dev/null
+++ b/lib/gitlab/markdown/external_link_filter.rb
@@ -0,0 +1,33 @@
+require 'html/pipeline/filter'
+
+module Gitlab
+ module Markdown
+ # HTML Filter to add a `rel="nofollow"` attribute to external links
+ #
+ class ExternalLinkFilter < HTML::Pipeline::Filter
+ def call
+ doc.search('a').each do |node|
+ next unless node.has_attribute?('href')
+
+ link = node.attribute('href').value
+
+ # Skip non-HTTP(S) links
+ next unless link.start_with?('http')
+
+ # Skip internal links
+ next if link.start_with?(internal_url)
+
+ node.set_attribute('rel', 'nofollow')
+ end
+
+ doc
+ end
+
+ private
+
+ def internal_url
+ @internal_url ||= Gitlab.config.gitlab.url
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
index 1e885615163..dea04761ead 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/gitlab/markdown/issue_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(ISSUE_PATTERN) do |match|
+ text.gsub(Issue.reference_pattern) do |match|
yield match, $~[:issue].to_i, $~[:project]
end
end
- # Pattern used to extract `#123` issue references from text
- #
- # This pattern supports cross-project references.
- ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?<issue>([a-zA-Z\-]+-)?\d+)/
-
def call
- replace_text_nodes_matching(ISSUE_PATTERN) do |content|
+ replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content)
end
end
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}##{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index a357f28458d..e022ca69c91 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/label_reference_filter.rb
@@ -15,26 +15,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(LABEL_PATTERN) do |match|
+ text.gsub(Label.reference_pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name]
end
end
- # Pattern used to extract label references from text
- #
- # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad).
- LABEL_PATTERN = %r{
- ~(
- (?<label_id>\d+) | # Integer-based label ID, or
- (?<label_name>
- [A-Za-z0-9_-]+ | # String-based single-word label title
- ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes
- )
- )
- }x
-
def call
- replace_text_nodes_matching(LABEL_PATTERN) do |content|
+ replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
end
end
@@ -84,11 +71,10 @@ module Gitlab
#
# Returns a Hash.
def label_params(id, name)
- if id > 0
- { id: id }
+ if name
+ { name: name.tr('"', '') }
else
- # TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding.
- { name: name.tr('\'"', '') }
+ { id: id }
end
end
end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index 740d72abb36..80779819485 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(MERGE_REQUEST_PATTERN) do |match|
+ text.gsub(MergeRequest.reference_pattern) do |match|
yield match, $~[:merge_request].to_i, $~[:project]
end
end
- # Pattern used to extract `!123` merge request references from text
- #
- # This pattern supports cross-project references.
- MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?<merge_request>\d+)/
-
def call
- replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content|
+ replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content)
end
end
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}!#{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index a4303d96bef..a84bacd3d4f 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/string/output_safety'
-require 'html/pipeline'
+require 'html/pipeline/filter'
module Gitlab
module Markdown
@@ -25,12 +25,18 @@ module Gitlab
ERB::Util.html_escape_once(html)
end
- # Don't look for references in text nodes that are children of these
- # elements.
- IGNORE_PARENTS = %w(pre code a style).to_set
+ def ignore_parents
+ @ignore_parents ||= begin
+ # Don't look for references in text nodes that are children of these
+ # elements.
+ parents = %w(pre code a style)
+ parents << 'blockquote' if context[:ignore_blockquotes]
+ parents.to_set
+ end
+ end
def ignored_ancestry?(node)
- has_ancestor?(node, IGNORE_PARENTS)
+ has_ancestor?(node, ignore_parents)
end
def project
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index 88781fea0c8..74b3a8d274f 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -8,33 +8,54 @@ module Gitlab
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist
- whitelist = super
+ # Descriptions are more heavily sanitized, allowing only a few elements.
+ # See http://git.io/vkuAN
+ if pipeline == :description
+ whitelist = LIMITED
+ whitelist[:elements] -= %w(pre code img ol ul li)
+ else
+ whitelist = super
+ end
+
+ customize_whitelist(whitelist)
+
+ whitelist
+ end
+ private
+
+ def pipeline
+ context[:pipeline] || :default
+ end
+
+ def customized?(transformers)
+ transformers.last.source_location[0] == __FILE__
+ end
+
+ def customize_whitelist(whitelist)
# Only push these customizations once
- unless customized?(whitelist[:transformers])
- # Allow code highlighting
- whitelist[:attributes]['pre'] = %w(class)
- whitelist[:attributes]['span'] = %w(class)
+ return if customized?(whitelist[:transformers])
- # Allow table alignment
- whitelist[:attributes]['th'] = %w(style)
- whitelist[:attributes]['td'] = %w(style)
+ # Allow code highlighting
+ whitelist[:attributes]['pre'] = %w(class)
+ whitelist[:attributes]['span'] = %w(class)
- # Allow span elements
- whitelist[:elements].push('span')
+ # Allow table alignment
+ whitelist[:attributes]['th'] = %w(style)
+ whitelist[:attributes]['td'] = %w(style)
- # Remove `rel` attribute from `a` elements
- whitelist[:transformers].push(remove_rel)
+ # Allow span elements
+ whitelist[:elements].push('span')
- # Remove `class` attribute from non-highlight spans
- whitelist[:transformers].push(clean_spans)
- end
+ # Remove `rel` attribute from `a` elements
+ whitelist[:transformers].push(remove_rel)
+
+ # Remove `class` attribute from non-highlight spans
+ whitelist[:transformers].push(clean_spans)
whitelist
end
- private
-
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
@@ -53,10 +74,6 @@ module Gitlab
end
end
end
-
- def customized?(transformers)
- transformers.last.source_location[0] == __FILE__
- end
end
end
end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
index 64a0a2696f7..174ba58af6c 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/gitlab/markdown/snippet_reference_filter.rb
@@ -20,18 +20,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(SNIPPET_PATTERN) do |match|
+ text.gsub(Snippet.reference_pattern) do |match|
yield match, $~[:snippet].to_i, $~[:project]
end
end
- # Pattern used to extract `$123` snippet references from text
- #
- # This pattern supports cross-project references.
- SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?<snippet>\d+)/
-
def call
- replace_text_nodes_matching(SNIPPET_PATTERN) do |content|
+ replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content)
end
end
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}$#{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index 28ec041b1d4..c9972957182 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -16,16 +16,13 @@ module Gitlab
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(USER_PATTERN) do |match|
+ text.gsub(User.reference_pattern) do |match|
yield match, $~[:user]
end
end
- # Pattern used to extract `@user` user references from text
- USER_PATTERN = /@(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})/
-
def call
- replace_text_nodes_matching(USER_PATTERN) do |content|
+ replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
end
end
@@ -68,7 +65,8 @@ module Gitlab
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@all</a>)
+ text = User.reference_prefix + 'all'
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_namespace(namespace)
@@ -86,7 +84,8 @@ module Gitlab
url = urls.group_url(group, only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@#{group}</a>)
+ text = Group.reference_prefix + group
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_user(user, namespace)
@@ -94,7 +93,8 @@ module Gitlab
url = urls.user_url(user, only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@#{user}</a>)
+ text = User.reference_prefix + user
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def user_can_reference_group?(group)
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/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index ba5caed6131..17ce4d4b174 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -46,6 +46,10 @@ module Gitlab
def gl_user
@user ||= find_by_uid_and_provider
+ if auto_link_ldap_user?
+ @user ||= find_or_create_ldap_user
+ end
+
if signup_enabled?
@user ||= build_new_user
end
@@ -55,6 +59,47 @@ module Gitlab
protected
+ def find_or_create_ldap_user
+ return unless ldap_person
+
+ # If a corresponding person exists with same uid in a LDAP server,
+ # set up a Gitlab user with dual LDAP and Omniauth identities.
+ if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider)
+ # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
+ user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ else
+ # No account in Gitlab yet: create it and add the LDAP identity
+ user = build_new_user
+ user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
+ end
+
+ user
+ end
+
+ def auto_link_ldap_user?
+ Gitlab.config.omniauth.auto_link_ldap_user
+ end
+
+ def creating_linked_ldap_user?
+ auto_link_ldap_user? && ldap_person
+ end
+
+ def ldap_person
+ return @ldap_person if defined?(@ldap_person)
+
+ # Look for a corresponding person with same uid in any of the configured LDAP providers
+ Gitlab::LDAP::Config.providers.each do |provider|
+ adapter = Gitlab::LDAP::Adapter.new(provider)
+ @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
+ break if @ldap_person
+ end
+ @ldap_person
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
+ end
+
def needs_blocking?
new? && block_after_signup?
end
@@ -64,7 +109,11 @@ module Gitlab
end
def block_after_signup?
- Gitlab.config.omniauth.block_auto_created_users
+ if creating_linked_ldap_user?
+ ldap_config.block_auto_created_users
+ else
+ Gitlab.config.omniauth.block_auto_created_users
+ end
end
def auth_hash=(auth_hash)
@@ -84,10 +133,19 @@ module Gitlab
end
def user_attributes
+ # Give preference to LDAP for sensitive information when creating a linked account
+ if creating_linked_ldap_user?
+ username = ldap_person.username
+ email = ldap_person.email.first
+ else
+ username = auth_hash.username
+ email = auth_hash.email
+ end
+
{
name: auth_hash.name,
- username: ::Namespace.clean_path(auth_hash.username),
- email: auth_hash.email,
+ username: ::Namespace.clean_path(username),
+ email: email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_automatically_set: true
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index f97784f5abb..d010ade704e 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -27,7 +27,7 @@ module Gitlab
# Get latest 20 commits ASC
commits_limited = commits.last(20)
-
+
# For performance purposes maximum 20 latest commits
# will be passed as post receive hook data.
commit_attrs = commits_limited.map(&:hook_attrs)
@@ -70,8 +70,11 @@ module Gitlab
end
def checkout_sha(repository, newrev, ref)
+ # Checkout sha is nil when we remove branch or tag
+ return if Gitlab::Git.blank_ref?(newrev)
+
# Find sha for tag, except when it was deleted.
- if Gitlab::Git.tag_ref?(ref) && !Gitlab::Git.blank_ref?(newrev)
+ if Gitlab::Git.tag_ref?(ref)
tag_name = Gitlab::Git.ref_name(ref)
tag = repository.find_tag(tag_name)
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index e35f848fa6e..e836b05ff25 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,7 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :project, :current_user, :references
+ attr_accessor :project, :current_user
def initialize(project, current_user = nil)
@project = project
@@ -9,48 +9,31 @@ module Gitlab
end
def analyze(text)
- @_text = text.dup
+ references.clear
+ @text = markdown.render(text.dup)
end
- def users
- result = pipeline_result(:user)
- result.uniq
+ %i(user label issue merge_request snippet commit commit_range).each do |type|
+ define_method("#{type}s") do
+ references[type]
+ end
end
- def labels
- result = pipeline_result(:label)
- result.uniq
- end
-
- def issues
- # TODO (rspeicher): What about external issues?
-
- result = pipeline_result(:issue)
- result.uniq
- end
-
- def merge_requests
- result = pipeline_result(:merge_request)
- result.uniq
- end
+ private
- def snippets
- result = pipeline_result(:snippet)
- result.uniq
+ def markdown
+ @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, GitlabMarkdownHelper::MARKDOWN_OPTIONS)
end
- def commits
- result = pipeline_result(:commit)
- result.uniq
- end
+ def references
+ @references ||= Hash.new do |references, type|
+ type = type.to_sym
+ return references[type] if references.has_key?(type)
- def commit_ranges
- result = pipeline_result(:commit_range)
- result.uniq
+ references[type] = pipeline_result(type).uniq
+ end
end
- private
-
# Instantiate and call HTML::Pipeline with a single reference filter type,
# returning the result
#
@@ -65,11 +48,12 @@ module Gitlab
project: project,
current_user: current_user,
# We don't actually care about the links generated
- only_path: true
+ only_path: true,
+ ignore_blockquotes: true
}
pipeline = HTML::Pipeline.new([filter], context)
- result = pipeline.call(@_text)
+ result = pipeline.call(@text)
result[:references][filter_type]
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/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb
deleted file mode 100644
index 0d37b9dea85..00000000000
--- a/lib/gitlab/satellite/files/delete_file_action.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- class DeleteFileAction < FileAction
- # Deletes file and creates a new commit for it
- #
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
-
- # update the file in the satellite's working dir
- file_path_in_satellite = File.join(repo.working_dir, file_path)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- File.delete(file_path_in_satellite)
-
- # add removed file
- repo.remove(file_path_in_satellite)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
-
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- repo.git.push({ raise: true, timeout: true }, :origin, ref)
-
- # everything worked
- true
- end
- rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
deleted file mode 100644
index 3cb9c0b5ecb..00000000000
--- a/lib/gitlab/satellite/files/edit_file_action.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- # GitLab server-side file update and commit
- class EditFileAction < FileAction
- # Updates the files content and creates a new commit for it
- #
- # Returns false if the ref has been updated while editing the file
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message, encoding, new_branch = nil)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- begin
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(CheckoutFailed, ex.message)
- end
-
- # update the file in the satellite's working dir
- file_path_in_satellite = File.join(repo.working_dir, file_path)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- # Write file
- write_file(file_path_in_satellite, content, encoding)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- begin
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(CommitFailed, ex.message)
- end
-
-
- target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- begin
- repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(PushFailed, ex.message)
- end
-
- # everything worked
- true
- end
- end
-
- private
-
- def log_and_raise(errorClass, message)
- Gitlab::GitLogger.error(message)
- raise(errorClass, message)
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb
deleted file mode 100644
index 6446b14568a..00000000000
--- a/lib/gitlab/satellite/files/file_action.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Gitlab
- module Satellite
- class FileAction < Action
- attr_accessor :file_path, :ref
-
- def initialize(user, project, ref, file_path)
- super user, project
- @file_path = file_path
- @ref = ref
- end
-
- def safe_path?(path)
- File.absolute_path(path) == path
- end
-
- def write_file(abs_file_path, content, file_encoding = 'text')
- if file_encoding == 'base64'
- File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) }
- else
- File.open(abs_file_path, 'w') { |f| f.write(content) }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb
deleted file mode 100644
index 724dfa0d042..00000000000
--- a/lib/gitlab/satellite/files/new_file_action.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- class NewFileAction < FileAction
- # Updates the files content and creates a new commit for it
- #
- # Returns false if the ref has been updated while editing the file
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message, encoding, new_branch = nil)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- current_ref =
- if @project.empty_repo?
- # skip this step if we want to add first file to empty repo
- Satellite::PARKING_BRANCH
- else
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
- ref
- end
-
- file_path_in_satellite = File.join(repo.working_dir, file_path)
- dir_name_in_satellite = File.dirname(file_path_in_satellite)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- # Create dir if not exists
- FileUtils.mkdir_p(dir_name_in_satellite)
-
- # Write file
- write_file(file_path_in_satellite, content, encoding)
-
- # add new file
- repo.add(file_path_in_satellite)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
-
- target_branch = if new_branch.present? && !@project.empty_repo?
- "#{ref}:#{new_branch}"
- else
- "#{current_ref}:#{ref}"
- end
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
-
- # everything worked
- true
- end
- rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
deleted file mode 100644
index e5a1f1b44d9..00000000000
--- a/lib/gitlab/theme.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module Gitlab
- class Theme
- BASIC = 1 unless const_defined?(:BASIC)
- MARS = 2 unless const_defined?(:MARS)
- MODERN = 3 unless const_defined?(:MODERN)
- GRAY = 4 unless const_defined?(:GRAY)
- COLOR = 5 unless const_defined?(:COLOR)
- BLUE = 6 unless const_defined?(:BLUE)
-
- def self.classes
- @classes ||= {
- BASIC => 'ui_basic',
- MARS => 'ui_mars',
- MODERN => 'ui_modern',
- GRAY => 'ui_gray',
- COLOR => 'ui_color',
- BLUE => 'ui_blue'
- }
- end
-
- def self.css_class_by_id(id)
- id ||= Gitlab.config.gitlab.default_theme
- classes[id]
- end
-
- def self.types
- @types ||= {
- BASIC => 'light_theme',
- MARS => 'dark_theme',
- MODERN => 'dark_theme',
- GRAY => 'dark_theme',
- COLOR => 'dark_theme',
- BLUE => 'light_theme'
- }
- end
-
- def self.type_css_class_by_id(id)
- id ||= Gitlab.config.gitlab.default_theme
- types[id]
- end
-
- # Convenience method to get a space-separated String of all the theme
- # classes that might be applied to the `body` element
- #
- # Returns a String
- def self.body_classes
- (classes.values + types.values).uniq.join(' ')
- end
- end
-end
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
new file mode 100644
index 00000000000..5209df92795
--- /dev/null
+++ b/lib/gitlab/themes.rb
@@ -0,0 +1,67 @@
+module Gitlab
+ # Module containing GitLab's application theme definitions and helper methods
+ # for accessing them.
+ module Themes
+ # Theme ID used when no `default_theme` configuration setting is provided.
+ APPLICATION_DEFAULT = 2
+
+ # Struct class representing a single Theme
+ Theme = Struct.new(:id, :name, :css_class)
+
+ # All available Themes
+ THEMES = [
+ Theme.new(1, 'Graphite', 'ui_graphite'),
+ Theme.new(2, 'Charcoal', 'ui_charcoal'),
+ Theme.new(3, 'Green', 'ui_green'),
+ Theme.new(4, 'Gray', 'ui_gray'),
+ Theme.new(5, 'Violet', 'ui_violet'),
+ Theme.new(6, 'Blue', 'ui_blue')
+ ].freeze
+
+ # Convenience method to get a space-separated String of all the theme
+ # classes that might be applied to the `body` element
+ #
+ # Returns a String
+ def self.body_classes
+ THEMES.collect(&:css_class).uniq.join(' ')
+ end
+
+ # Get a Theme by its ID
+ #
+ # If the ID is invalid, returns the default Theme.
+ #
+ # id - Integer ID
+ #
+ # Returns a Theme
+ def self.by_id(id)
+ THEMES.detect { |t| t.id == id } || default
+ end
+
+ # Get the default Theme
+ #
+ # Returns a Theme
+ def self.default
+ by_id(default_id)
+ end
+
+ # Iterate through each Theme
+ #
+ # Yields the Theme object
+ def self.each(&block)
+ THEMES.each(&block)
+ end
+
+ private
+
+ def self.default_id
+ id = Gitlab.config.gitlab.default_theme.to_i
+
+ # Prevent an invalid configuration setting from causing an infinite loop
+ if id < THEMES.first.id || id > THEMES.last.id
+ APPLICATION_DEFAULT
+ else
+ id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index 0570c2fbeb5..cf040971c6e 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -43,10 +43,15 @@ module Gitlab
end
def latest_version_raw
+ git_tags = fetch_git_tags
+ git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ }
+ git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) }
+ "v#{git_versions.sort.last.to_s}"
+ end
+
+ def fetch_git_tags
remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git))
- git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
- git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ }
- last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s
+ remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
end
def update_commands
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 7dcecc2ecf6..2f7aff03c2a 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -10,6 +10,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
@options = options.dup
@options.reverse_merge!(
+ # Handled further down the line by Gitlab::Markdown::SanitizationFilter
+ escape_html: false,
project: @template.instance_variable_get("@project")
)
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index b066a1a6935..946902e2f6d 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -35,13 +35,14 @@ pid_path="$app_root/tmp/pids"
socket_path="$app_root/tmp/sockets"
web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
+shell_path="/bin/bash"
# Read configuration variable file if it is present
test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ "$USER" != "$app_user" ]; then
- eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit;
+ eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
fi
# Switch to the gitlab path, exit on failure.
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 9951bacedf5..cf7f4198cbf 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -29,3 +29,8 @@ web_server_pid_path="$pid_path/unicorn.pid"
# sidekiq_pid_path defines the path in which to create the pid file for sidekiq
# The default is "$pid_path/sidekiq.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
+
+# shell_path defines the path of shell for "$app_user" in case you are using
+# shell other than "bash"
+# The default is "/bin/bash"
+shell_path="/bin/bash"
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 62a4276536c..edb987875df 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -1,10 +1,16 @@
## GitLab
-## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM
##
## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
##
##################################
+## CONTRIBUTING ##
+##################################
+##
+## If you change this file in a Merge Request, please also create
+## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+##
+##################################
## CHUNKED TRANSFER ##
##################################
##
@@ -34,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 2aefc944698..766559b49f6 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -1,5 +1,4 @@
## GitLab
-## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM
##
## Modified from nginx http version
## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/
@@ -9,6 +8,13 @@
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
##
##################################
+## CONTRIBUTING ##
+##################################
+##
+## If you change this file in a Merge Request, please also create
+## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+##
+##################################
## CHUNKED TRANSFER ##
##################################
##
@@ -38,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
@@ -67,7 +77,7 @@ server {
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
- ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
+ ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
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 1a6303b6c82..aed84226a2f 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -1,7 +1,6 @@
namespace :gitlab do
- desc "GITLAB | Check the configuration of GitLab and its environment"
- task check: %w{gitlab:env:check
- gitlab:gitlab_shell:check
+ desc "GitLab | Check the configuration of GitLab and its environment"
+ task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check
gitlab:ldap:check
gitlab:app:check}
@@ -9,11 +8,12 @@ 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"
+ check_git_config
check_database_config_exists
check_database_is_not_sqlite
check_migrations_are_up
@@ -38,6 +38,36 @@ namespace :gitlab do
# Checks
########################
+ def check_git_config
+ print "Git configured with autocrlf=input? ... "
+
+ options = {
+ "core.autocrlf" => "input"
+ }
+
+ correct_options = options.map do |name, value|
+ run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
+ end
+
+ if correct_options.all?
+ puts "yes".green
+ else
+ print "Trying to fix Git error automatically. ..."
+
+ if auto_fix_git_config(options)
+ puts "Success".green
+ else
+ puts "Failed".red
+ try_fixing_it(
+ sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab"
+ )
+ end
+ end
+ end
+
def check_database_config_exists
print "Database config exists? ... "
@@ -298,60 +328,8 @@ namespace :gitlab do
end
end
-
-
- namespace :env do
- desc "GITLAB | Check the configuration of the environment"
- task check: :environment do
- warn_user_is_not_gitlab
- start_checking "Environment"
-
- check_gitlab_git_config
-
- finished_checking "Environment"
- end
-
-
- # Checks
- ########################
-
- def check_gitlab_git_config
- print "Git configured for #{gitlab_user} user? ... "
-
- options = {
- "user.name" => "GitLab",
- "user.email" => Gitlab.config.gitlab.email_from,
- "core.autocrlf" => "input"
- }
- correct_options = options.map do |name, value|
- run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
- end
-
- if correct_options.all?
- puts "yes".green
- else
- print "Trying to fix Git error automatically. ..."
- if auto_fix_git_config(options)
- puts "Success".green
- else
- puts "Failed".red
- try_fixing_it(
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
- )
- for_more_information(
- see_installation_guide_section "GitLab"
- )
- end
- end
- end
- end
-
-
-
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"
@@ -596,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"
@@ -689,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 3c9802a0be4..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']
@@ -51,7 +51,7 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path
all_dirs = Dir.glob(git_base_path + '/*')
- global_projects = Project.where(namespace_id: nil).pluck(:path)
+ global_projects = Project.in_namespace(nil).pluck(:path)
puts git_base_path.yellow
puts "Looking for global repos to 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/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 e835d6cb9b7..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
@@ -59,6 +59,9 @@ namespace :gitlab do
# Launch installation process
system(*%W(bin/install))
+
+ # (Re)create hooks
+ system(*%W(bin/create-hooks))
end
# Required for debian packaging with PKGR: Setup .ssh/environment with
@@ -72,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/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index 14a130be2ca..c95b6540ebc 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -118,9 +118,9 @@ namespace :gitlab do
# Returns true if all subcommands were successfull (according to their exit code)
# Returns false if any or all subcommands failed.
def auto_fix_git_config(options)
- if !@warned_user_not_gitlab && options['user.email'] != 'example@example.com' # default email should be overridden?
+ if !@warned_user_not_gitlab
command_success = options.map do |name, value|
- system(%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
+ system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
end
command_success.all?
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index b4c0ae3ff79..cbb3d61af04 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -1,5 +1,5 @@
namespace :gitlab do
- desc "GITLAB | Run all tests"
+ desc "GitLab | Run all tests"
task :test do
cmds = [
%W(rake brakeman),
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index f9f586db93c..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']
@@ -51,11 +51,11 @@ namespace :gitlab do
if namespace_path.blank?
Project
elsif namespace_path == '/'
- Project.where(namespace_id: nil)
+ Project.in_namespace(nil)
else
namespace = Namespace.where(path: namespace_path).first
if namespace
- Project.where(namespace_id: namespace.id)
+ Project.in_namespace(namespace.id)
else
puts "Namespace not found: #{namespace_path}".red
exit 2
diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake
deleted file mode 100644
index 9e2cceffa19..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['spec:javascript'].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..b9f9c72c91f 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"
+ desc "GitLab | Run all tests on CI with simplecov"
task :test_ci => [:rubocop, :brakeman, 'jasmine:ci', :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
new file mode 100755
index 00000000000..dca5e1c5db3
--- /dev/null
+++ b/scripts/prepare_build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+if [ -f /.dockerinit ]; then
+ wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
+ 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 postgresql-client mysql-client
+
+ cp config/database.yml.mysql config/database.yml
+ sed -i 's/username:.*/username: root/g' config/database.yml
+ sed -i 's/password:.*/password:/g' config/database.yml
+ sed -i 's/# socket:.*/host: mysql/g' config/database.yml
+
+ cp config/resque.yml.example config/resque.yml
+ sed -i 's/localhost/redis/g' config/resque.yml
+ FLAGS=(--deployment --path /cache)
+ export FLAGS
+else
+ export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
+ cp config/database.yml.mysql config/database.yml
+ sed "s/username\:.*$/username\: runner/" -i config/database.yml
+ sed "s/password\:.*$/password\: 'password'/" -i config/database.yml
+ sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml
+fi
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
new file mode 100644
index 00000000000..f27e861e175
--- /dev/null
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -0,0 +1,24 @@
+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
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 186239d3096..55851befc8c 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -30,4 +30,44 @@ describe ApplicationController do
controller.send(:check_password_expiration)
end
end
+
+ describe 'check labels authorization' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:controller) { ApplicationController.new }
+
+ before do
+ project.team << [user, :guest]
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(controller).to receive(:project).and_return(project)
+ end
+
+ it 'should succeed if issues and MRs are enabled' do
+ project.issues_enabled = true
+ project.merge_requests_enabled = true
+ controller.send(:authorize_read_label!)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should succeed if issues are enabled, MRs are disabled' do
+ project.issues_enabled = true
+ project.merge_requests_enabled = false
+ controller.send(:authorize_read_label!)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should succeed if issues are disabled, MRs are enabled' do
+ project.issues_enabled = false
+ project.merge_requests_enabled = true
+ controller.send(:authorize_read_label!)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should fail if issues and MRs are disabled' do
+ project.issues_enabled = false
+ project.merge_requests_enabled = false
+ expect(controller).to receive(:access_denied!)
+ controller.send(:authorize_read_label!)
+ end
+ end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index a0909cec3bd..9ad9cb41cc1 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -15,9 +15,9 @@ describe AutocompleteController do
let(:body) { JSON.parse(response.body) }
- it { body.should be_kind_of(Array) }
- it { body.size.should eq(1) }
- it { body.first["username"].should == user.username }
+ 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
context 'group members' do
@@ -32,9 +32,9 @@ describe AutocompleteController do
let(:body) { JSON.parse(response.body) }
- it { body.should be_kind_of(Array) }
- it { body.size.should eq(1) }
- it { body.first["username"].should == user.username }
+ 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
context 'all users' do
@@ -45,7 +45,7 @@ describe AutocompleteController do
let(:body) { JSON.parse(response.body) }
- it { body.should be_kind_of(Array) }
- it { body.size.should eq(User.count) }
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq User.count }
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..db3b64babcd 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" }
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
new file mode 100644
index 00000000000..8f02003992a
--- /dev/null
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Profiles::PreferencesController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ allow(subject).to receive(:current_user).and_return(user)
+ end
+
+ describe 'GET show' do
+ it 'renders' do
+ get :show
+ expect(response).to render_template :show
+ end
+
+ it 'assigns user' do
+ get :show
+ expect(assigns[:user]).to eq user
+ end
+ end
+
+ describe 'PATCH update' do
+ def go(params: {}, format: :js)
+ params.reverse_merge!(
+ color_scheme_id: '1',
+ dashboard: 'stars',
+ theme_id: '1'
+ )
+
+ patch :update, user: params, format: format
+ end
+
+ context 'on successful update' do
+ it 'sets the flash' do
+ go
+ expect(flash[:notice]).to eq 'Preferences saved.'
+ end
+
+ it "changes the user's preferences" do
+ prefs = {
+ color_scheme_id: '1',
+ dashboard: 'stars',
+ theme_id: '2'
+ }.with_indifferent_access
+
+ expect(user).to receive(:update_attributes).with(prefs)
+
+ go params: prefs
+ end
+ end
+
+ context 'on failed update' do
+ it 'sets the flash' do
+ expect(user).to receive(:update_attributes).and_return(false)
+
+ go
+
+ expect(flash[:alert]).to eq('Failed to save preferences.')
+ end
+ end
+
+ context 'on invalid dashboard setting' do
+ it 'sets the flash' do
+ prefs = { dashboard: 'invalid' }
+
+ go params: prefs
+
+ expect(flash[:alert]).to match(/\AFailed to save preferences \(.+\)\.\z/)
+ end
+ end
+
+ context 'as js' do
+ it 'renders' do
+ go
+ expect(response).to render_template :update
+ end
+ end
+
+ context 'as html' do
+ it 'redirects' do
+ go format: :html
+ expect(response).to redirect_to(profile_preferences_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index f05d1f5fbe1..aa09f1a758d 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -11,8 +11,11 @@ describe Profiles::TwoFactorAuthsController do
describe 'GET new' do
let(:user) { create(:user) }
- it 'generates otp_secret' do
- expect { get :new }.to change { user.otp_secret }
+ it 'generates otp_secret for user' do
+ expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once
+
+ get :new
+ get :new # Second hit shouldn't re-generate it
end
it 'assigns qr_code' do
@@ -37,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
@@ -106,13 +109,13 @@ describe Profiles::TwoFactorAuthsController do
let!(:codes) { user.generate_otp_backup_codes! }
it 'clears all 2FA-related fields' do
- expect(user.otp_required_for_login).to eq true
+ expect(user).to be_two_factor_enabled
expect(user.otp_backup_codes).not_to be_nil
expect(user.encrypted_otp_secret).not_to be_nil
delete :destroy
- expect(user.otp_required_for_login).to eq false
+ expect(user).not_to be_two_factor_enabled
expect(user.otp_backup_codes).to be_nil
expect(user.encrypted_otp_secret).to be_nil
end
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/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/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
new file mode 100644
index 00000000000..abbbf6855fc
--- /dev/null
+++ b/spec/controllers/root_controller_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe RootController do
+ describe 'GET show' do
+ context 'with a user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ allow(subject).to receive(:current_user).and_return(user)
+ end
+
+ context 'who has customized their dashboard setting' do
+ before do
+ user.update_attribute(:dashboard, 'stars')
+ end
+
+ it 'redirects to their specified dashboard' do
+ get :show
+ expect(response).to redirect_to starred_dashboard_projects_path
+ end
+ end
+
+ context 'who uses the default dashboard setting' do
+ it 'renders the default dashboard' do
+ get :show
+ expect(response).to render_template 'dashboard/show'
+ end
+ end
+ end
+ end
+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 26e8a795fa4..578a2e4dc69 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -2,23 +2,23 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
sequence :sentence, aliases: [:title, :content] do
- Faker::Lorem.sentence
+ FFaker::Lorem.sentence
end
sequence :name do
- Faker::Name.name
+ FFaker::Name.name
end
sequence :file_name do
- Faker::Internet.user_name
+ FFaker::Internet.user_name
end
- sequence(:url) { Faker::Internet.uri('http') }
+ sequence(:url) { FFaker::Internet.uri('http') }
factory :user, aliases: [:author, :assignee, :owner, :creator] do
- email { Faker::Internet.email }
+ email { FFaker::Internet.email }
name
- sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" }
+ sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
password "12345678"
confirmed_at { Time.now }
confirmation_token { nil }
@@ -30,8 +30,8 @@ FactoryGirl.define do
trait :two_factor do
before(:create) do |user|
- user.otp_required_for_login = true
- user.otp_secret = User.generate_otp_secret
+ user.two_factor_enabled = true
+ user.otp_secret = User.generate_otp_secret(32)
end
end
@@ -122,12 +122,12 @@ FactoryGirl.define do
factory :email do
user
email do
- Faker::Internet.email('alias')
+ FFaker::Internet.email('alias')
end
factory :another_email do
email do
- Faker::Internet.email('another.alias')
+ FFaker::Internet.email('another.alias')
end
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_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 25862614d28..7265cdac7a7 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -12,7 +12,7 @@ describe "Admin::Hooks", feature: true do
describe "GET /admin/hooks" do
it "should be ok" do
visit admin_root_path
- within ".sidebar-wrapper" do
+ page.within ".sidebar-wrapper" do
click_on "Hooks"
end
expect(current_path).to eq(admin_hooks_path)
@@ -26,7 +26,7 @@ describe "Admin::Hooks", feature: true do
describe "New Hook" do
before do
- @url = Faker::Internet.uri("http")
+ @url = FFaker::Internet.uri("http")
visit admin_hooks_path
fill_in "hook_url", with: @url
expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1)
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 133beba7b98..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.iid}\n\nask @#{fred.username} 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 }
@@ -25,25 +26,25 @@ describe "GitLab Flavored Markdown", feature: true do
it "should render title in commits#index" do
visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
it "should render title in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
it "should render description in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
- expect(page).to have_link("@#{fred.username}")
+ expect(page).to have_link(fred.to_reference)
end
it "should render title in repositories#branches" do
visit namespace_project_branches_path(project.namespace, project)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
end
@@ -57,20 +58,20 @@ describe "GitLab Flavored Markdown", feature: true do
author: @user,
assignee: @user,
project: project,
- title: "fix ##{@other_issue.iid}",
- description: "ask @#{fred.username} for details")
+ title: "fix #{@other_issue.to_reference}",
+ description: "ask #{fred.to_reference} for details")
end
it "should render subject in issues#index" do
visit namespace_project_issues_path(project.namespace, project)
- expect(page).to have_link("##{@other_issue.iid}")
+ expect(page).to have_link(@other_issue.to_reference)
end
it "should render subject in issues#show" do
visit namespace_project_issue_path(project.namespace, project, @issue)
- expect(page).to have_link("##{@other_issue.iid}")
+ expect(page).to have_link(@other_issue.to_reference)
end
it "should render details in issues#show" do
@@ -83,19 +84,19 @@ describe "GitLab Flavored Markdown", feature: true do
describe "for merge requests" do
before do
- @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.iid}")
+ @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}")
end
it "should render title in merge_requests#index" do
visit namespace_project_merge_requests_path(project.namespace, project)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
it "should render title in merge_requests#show" do
visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
end
@@ -104,26 +105,26 @@ describe "GitLab Flavored Markdown", feature: true do
before do
@milestone = create(:milestone,
project: project,
- title: "fix ##{issue.iid}",
- description: "ask @#{fred.username} for details")
+ title: "fix #{issue.to_reference}",
+ description: "ask #{fred.to_reference} for details")
end
it "should render title in milestones#index" do
visit namespace_project_milestones_path(project.namespace, project)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
it "should render title in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
- expect(page).to have_link("##{issue.iid}")
+ expect(page).to have_link(issue.to_reference)
end
it "should render description in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
- expect(page).to have_link("@#{fred.username}")
+ expect(page).to have_link(fred.to_reference)
end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
new file mode 100644
index 00000000000..edc1c63a0aa
--- /dev/null
+++ b/spec/features/groups_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+feature 'Group' do
+ describe 'description' do
+ let(:group) { create(:group) }
+ let(:path) { group_path(group) }
+
+ before do
+ login_as(:admin)
+ end
+
+ it 'parses Markdown' do
+ group.update_attribute(:description, 'This is **my** group')
+ visit path
+ expect(page).to have_css('.description > p > strong')
+ end
+
+ it 'passes through html-pipeline' do
+ group.update_attribute(:description, 'This group is the :poop:')
+ visit path
+ expect(page).to have_css('.description > p > img')
+ end
+
+ it 'sanitizes unwanted tags' do
+ group.update_attribute(:description, '# Group Description')
+ visit path
+ expect(page).not_to have_css('.description h1')
+ end
+
+ it 'permits `rel` attribute on links' do
+ group.update_attribute(:description, 'https://google.com/')
+ visit path
+ expect(page).to have_css('.description a[rel]')
+ end
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 66d73b2505c..808a6eeb958 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
@@ -220,7 +218,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 +257,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 +293,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}"
@@ -311,10 +309,10 @@ describe 'Issues', feature: true do
end
def first_issue
- all('ul.issues-list li').first.text
+ page.all('ul.issues-list li').first.text
end
def last_issue
- all('ul.issues-list li').last.text
+ page.all('ul.issues-list li').last.text
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 8f3dfc8d5a9..902968cebcb 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -18,11 +18,13 @@ require 'erb'
# -> `gfm_with_options` helper
# -> HTML::Pipeline
# -> Sanitize
+# -> RelativeLink
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
+# -> ExternalLink
# -> References
# -> TaskList
# -> `html_safe`
@@ -66,6 +68,10 @@ describe 'GitLab Markdown' do
@doc.at_css("##{id}").parent.next_element
end
+ # Sometimes it can be useful to see the parsed output of the Markdown document
+ # for debugging. Uncomment this block to write the output to
+ # tmp/capybara/markdown_spec.html.
+ #
# it 'writes to a file' do
# File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file|
# file.puts @md
@@ -145,7 +151,7 @@ describe 'GitLab Markdown' do
it 'removes `rel` attribute from links' do
body = get_section('sanitizationfilter')
- expect(body).not_to have_selector('a[rel]')
+ expect(body).not_to have_selector('a[rel="bookmark"]')
end
it "removes `href` from `a` elements if it's fishy" do
@@ -233,6 +239,18 @@ describe 'GitLab Markdown' do
end
end
+ describe 'ExternalLinkFilter' do
+ let(:links) { get_section('externallinkfilter').next_element }
+
+ it 'adds nofollow to external link' do
+ expect(links.css('a').first.to_html).to match 'nofollow'
+ end
+
+ it 'ignores internal link' do
+ expect(links.css('a').last.to_html).not_to match 'nofollow'
+ end
+ end
+
describe 'ReferenceFilter' do
it 'handles references in headers' do
header = @doc.at_css('#reference-filters-eg-1').parent
@@ -344,13 +362,13 @@ class MarkdownFeature
end
def commit
- @commit ||= project.repository.commit
+ @commit ||= project.commit
end
def commit_range
unless @commit_range
- commit2 = project.repository.commit('HEAD~3')
- @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}")
+ commit2 = project.commit('HEAD~3')
+ @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range
@@ -376,11 +394,6 @@ class MarkdownFeature
@xproject
end
- # Shortcut to "cross-reference/project"
- def xref
- xproject.path_with_namespace
- end
-
def xissue
@xissue ||= create(:issue, project: xproject)
end
@@ -394,13 +407,13 @@ class MarkdownFeature
end
def xcommit
- @xcommit ||= xproject.repository.commit
+ @xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
- xcommit2 = xproject.repository.commit('HEAD~2')
- @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}")
+ xcommit2 = xproject.commit('HEAD~2')
+ @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
@xcommit_range
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index c47368b1fda..ad37b589b84 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -22,20 +22,20 @@ describe 'Comments' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
expect(find('.js-main-target-form input[type=submit]').value).
to eq('Add Comment')
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
expect(page).not_to have_link('Cancel')
end
end
describe 'with text' do
before do
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
fill_in 'note[note]', with: 'This is awesome'
end
end
it 'should have enable submit button and preview button' do
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
expect(page).not_to have_css('.js-comment-button[disabled]')
expect(page).to have_css('.js-md-preview-button', visible: true)
end
@@ -45,7 +45,7 @@ describe 'Comments' do
describe 'when posting a note' do
before do
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
fill_in 'note[note]', with: 'This is awsome!'
find('.js-md-preview-button').click
click_button 'Add Comment'
@@ -54,11 +54,11 @@ describe 'Comments' do
it 'should be added and form reset' do
is_expected.to have_content('This is awsome!')
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
expect(page).to have_no_field('note[note]', with: 'This is awesome!')
expect(page).to have_css('.js-md-preview', visible: :hidden)
end
- within('.js-main-target-form') do
+ page.within('.js-main-target-form') do
is_expected.to have_css('.js-note-text', visible: true)
end
end
@@ -66,7 +66,7 @@ describe 'Comments' do
describe 'when editing a note', js: true do
it 'should contain the hidden edit form' do
- within("#note_#{note.id}") do
+ page.within("#note_#{note.id}") do
is_expected.to have_css('.note-edit-form', visible: false)
end
end
@@ -78,7 +78,7 @@ describe 'Comments' do
end
it 'should show the note edit form and hide the note body' do
- within("#note_#{note.id}") do
+ page.within("#note_#{note.id}") do
expect(find('.current-note-edit-form', visible: true)).to be_visible
expect(find('.note-edit-form', visible: true)).to be_visible
expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible
@@ -86,21 +86,21 @@ 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
- #find(".js-note-text", visible: false).text.should == 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
- within('.current-note-edit-form') do
+ page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'Some new content'
find('.btn-save').click
end
- within("#note_#{note.id}") do
+ page.within("#note_#{note.id}") do
is_expected.to have_css('.note_edited_ago')
expect(find('.note_edited_ago').text).
to match(/less than a minute ago/)
@@ -115,7 +115,7 @@ describe 'Comments' do
end
it 'shows the delete link' do
- within('.note-attachment') do
+ page.within('.note-attachment') do
is_expected.to have_css('.js-note-attachment-delete')
end
end
@@ -150,7 +150,7 @@ describe 'Comments' do
it { is_expected.to have_css('.js-temp-notes-holder') }
it 'has .new_note css class' do
- within('.js-temp-notes-holder') do
+ page.within('.js-temp-notes-holder') do
expect(subject).to have_css('.new_note')
end
end
@@ -166,7 +166,7 @@ describe 'Comments' do
end
it 'should be removed when canceled' do
- within(".diff-file form[rel$='#{line_code}']") do
+ page.within(".diff-file form[rel$='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
@@ -186,11 +186,11 @@ describe 'Comments' do
describe 'previewing them separately' do
before do
# add two separate texts and trigger previews on both
- within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
+ page.within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
fill_in 'note[note]', with: 'One comment on line 7'
find('.js-md-preview-button').click
end
- within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
+ page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in 'note[note]', with: 'Another comment on line 10'
find('.js-md-preview-button').click
end
@@ -199,7 +199,7 @@ describe 'Comments' do
describe 'posting a note' do
before do
- within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
+ page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in 'note[note]', with: 'Another comment on line 10'
click_button('Add Comment')
end
@@ -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/profile_spec.rb b/spec/features/profile_spec.rb
index 3d36a3c02d0..9fe2e610555 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -9,7 +9,8 @@ describe 'Profile account page', feature: true do
describe 'when signup is enabled' do
before do
- ApplicationSetting.any_instance.stub(signup_enabled?: true)
+ allow_any_instance_of(ApplicationSetting).
+ to receive(:signup_enabled?).and_return(true)
visit profile_account_path
end
@@ -23,7 +24,8 @@ describe 'Profile account page', feature: true do
describe 'when signup is disabled' do
before do
- ApplicationSetting.any_instance.stub(signup_enabled?: false)
+ allow_any_instance_of(ApplicationSetting).
+ to receive(:signup_enabled?).and_return(false)
visit profile_account_path
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
new file mode 100644
index 00000000000..03e78c533db
--- /dev/null
+++ b/spec/features/profiles/preferences_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe 'Profile > Preferences' do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ visit profile_preferences_path
+ end
+
+ describe 'User changes their application theme', js: true do
+ let(:default) { Gitlab::Themes.default }
+ let(:theme) { Gitlab::Themes.by_id(5) }
+
+ it 'creates a flash message' do
+ choose "user_theme_id_#{theme.id}"
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ choose "user_theme_id_#{theme.id}"
+
+ allowing_for_delay do
+ visit page.current_path
+ expect(page).to have_checked_field("user_theme_id_#{theme.id}")
+ end
+ end
+
+ it 'reflects the changes immediately' do
+ expect(page).to have_selector("body.#{default.css_class}")
+
+ choose "user_theme_id_#{theme.id}"
+
+ expect(page).not_to have_selector("body.#{default.css_class}")
+ expect(page).to have_selector("body.#{theme.css_class}")
+ end
+ end
+
+ describe 'User changes their syntax highlighting theme', js: true do
+ it 'creates a flash message' do
+ choose 'user_color_scheme_id_5'
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ choose 'user_color_scheme_id_5'
+
+ allowing_for_delay do
+ visit page.current_path
+ expect(page).to have_checked_field('user_color_scheme_id_5')
+ end
+ end
+ end
+
+ describe 'User changes their default dashboard' do
+ it 'creates a flash message' do
+ select 'Starred Projects', from: 'user_dashboard'
+ click_button 'Save'
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ select 'Starred Projects', from: 'user_dashboard'
+ click_button 'Save'
+
+ click_link 'Dashboard'
+ expect(page.current_path).to eq starred_dashboard_projects_path
+
+ click_link 'Your Projects'
+ expect(page.current_path).to eq dashboard_path
+ end
+ end
+
+ def expect_preferences_saved_message
+ page.within('.flash-container') do
+ expect(page).to have_content('Preferences saved.')
+ end
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index cae11be7cdd..f8eea70ec4a 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,32 +1,57 @@
require 'spec_helper'
-describe "Projects", feature: true, js: true do
- before { login_as :user }
+feature 'Project' do
+ describe 'description' do
+ let(:project) { create(:project) }
+ let(:path) { namespace_project_path(project.namespace, project) }
- describe "DELETE /projects/:id" do
before do
- @project = create(:project, namespace: @user.namespace)
- @project.team << [@user, :master]
- visit edit_namespace_project_path(@project.namespace, @project)
+ login_as(:admin)
end
- it "should remove project" do
- expect { remove_project }.to change {Project.count}.by(-1)
+ it 'parses Markdown' do
+ project.update_attribute(:description, 'This is **my** project')
+ visit path
+ expect(page).to have_css('.project-home-desc > p > strong')
+ end
+
+ it 'passes through html-pipeline' do
+ project.update_attribute(:description, 'This project is the :poop:')
+ visit path
+ expect(page).to have_css('.project-home-desc > p > img')
+ end
+
+ it 'sanitizes unwanted tags' do
+ project.update_attribute(:description, '# Project Description')
+ visit path
+ expect(page).not_to have_css('.project-home-desc h1')
+ end
+
+ it 'permits `rel` attribute on links' do
+ project.update_attribute(:description, 'https://google.com/')
+ visit path
+ expect(page).to have_css('.project-home-desc a[rel]')
end
+ end
+
+ describe 'removal', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
- it 'should delete the project from disk' do
- expect(GitlabShellWorker).to(
- receive(:perform_async).with(:remove_repository,
- /#{@project.path_with_namespace}/)
- ).twice
+ before do
+ login_with(user)
+ project.team << [user, :master]
+ visit edit_namespace_project_path(project.namespace, project)
+ end
- remove_project
+ it 'should remove project' do
+ expect { remove_project }.to change {Project.count}.by(-1)
end
end
def remove_project
click_link "Remove project"
- fill_in 'confirm_name_input', with: @project.path
+ fill_in 'confirm_name_input', with: project.path
click_button 'Confirm'
end
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 73987739a7a..84c036e59c0 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -7,7 +7,7 @@ describe "Search", feature: true do
@project.team << [@user, :reporter]
visit search_path
- within '.search-holder' do
+ page.within '.search-holder' do
fill_in "search", with: @project.name[0..3]
click_button "Search"
end
@@ -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 2512a9c0e3d..8f7a9606262 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
@@ -36,8 +36,8 @@ describe "Profile access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
- describe "GET /profile/design" do
- subject { design_profile_path }
+ describe "GET /profile/preferences" do
+ subject { profile_preferences_path }
it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
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 93d2b18b5fc..a4c3dfe9205 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -27,4 +27,25 @@ feature 'Users' do
user.reload
expect(user.reset_password_token).to be_nil
end
+
+ let!(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') }
+ scenario 'Should show one error if email is already taken' do
+ visit new_user_session_path
+ fill_in 'user_name', with: 'Another user name'
+ fill_in 'user_username', with: 'anotheruser'
+ fill_in 'user_email', with: user.email
+ fill_in 'user_password_sign_up', with: '12341234'
+ expect { click_button 'Sign up' }.to change { User.count }.by(0)
+ expect(page).to have_text('Email has already been taken')
+ expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}'
+ end
+
+ def errors_on_page(page)
+ page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n")
+ end
+
+ def number_of_errors_on_page(page)
+ page.find('#error_explanation').find('ul').all('li').count
+ end
+
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 69bac387d20..db20b23f87d 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -26,37 +26,37 @@ describe IssuesFinder do
context 'scope: all' do
it 'should filter by all' do
params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(3)
end
it 'should filter by assignee id' do
params = { scope: "all", assignee_id: user.id, state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(2)
end
it 'should filter by author id' do
params = { scope: "all", author_id: user2.id, state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues).to eq([issue3])
end
it 'should filter by milestone id' do
params = { scope: "all", milestone_title: milestone.title, state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues).to eq([issue1])
end
it 'should be empty for unauthorized user' do
params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(nil, params)
+ issues = IssuesFinder.new(nil, params).execute
expect(issues.size).to be_zero
end
it 'should not include unauthorized issues' do
params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user2, params)
+ issues = IssuesFinder.new(user2, params).execute
expect(issues.size).to eq(2)
expect(issues).not_to include(issue1)
expect(issues).to include(issue2)
@@ -67,13 +67,13 @@ describe IssuesFinder do
context 'personal scope' do
it 'should filter by assignee' do
params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(2)
end
it 'should filter by project' do
params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new.execute(user, params)
+ issues = IssuesFinder.new(user, params).execute
expect(issues.size).to eq(1)
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 8536377a7f0..bc385fd0d69 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -20,13 +20,13 @@ describe MergeRequestsFinder do
describe "#execute" do
it 'should filter by scope' do
params = { scope: 'authored', state: 'opened' }
- merge_requests = MergeRequestsFinder.new.execute(user, params)
+ merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(2)
end
it 'should filter by project' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
- merge_requests = MergeRequestsFinder.new.execute(user, params)
+ merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(1)
end
end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 64817ec6700..02ab46c905a 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -79,7 +79,7 @@ As permissive as it is, we've allowed even more stuff:
<span>span tag</span>
-<a href="#" rel="nofollow">This is a link with a defined rel attribute, which should be removed</a>
+<a href="#" rel="bookmark">This is a link with a defined rel attribute, which should be removed</a>
<a href="javascript:alert('Hi')">This is a link trying to be sneaky. It gets its link removed entirely.</a>
@@ -127,61 +127,68 @@ But it shouldn't autolink text inside certain tags:
- <a>http://about.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd>
-### Reference Filters (e.g., #<%= issue.iid %>)
+### ExternalLinkFilter
-References should be parseable even inside _!<%= merge_request.iid %>_ emphasis.
+External links get a `rel="nofollow"` attribute:
+
+- [Google](https://google.com/)
+- [GitLab Root](<%= Gitlab.config.gitlab.url %>)
+
+### Reference Filters (e.g., <%= issue.to_reference %>)
+
+References should be parseable even inside _<%= merge_request.to_reference %>_ emphasis.
#### UserReferenceFilter
- All: @all
-- User: @<%= user.username %>
-- Group: @<%= group.name %>
-- Ignores invalid: @fake_user
-- Ignored in code: `@<%= user.username %>`
-- Ignored in links: [Link to @<%= user.username %>](#user-link)
+- User: <%= user.to_reference %>
+- Group: <%= group.to_reference %>
+- Ignores invalid: <%= User.reference_prefix %>fake_user
+- Ignored in code: `<%= user.to_reference %>`
+- Ignored in links: [Link to <%= user.to_reference %>](#user-link)
#### IssueReferenceFilter
-- Issue: #<%= issue.iid %>
-- Issue in another project: <%= xref %>#<%= xissue.iid %>
-- Ignored in code: `#<%= issue.iid %>`
-- Ignored in links: [Link to #<%= issue.iid %>](#issue-link)
+- Issue: <%= issue.to_reference %>
+- Issue in another project: <%= xissue.to_reference(project) %>
+- Ignored in code: `<%= issue.to_reference %>`
+- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
#### MergeRequestReferenceFilter
-- Merge request: !<%= merge_request.iid %>
-- Merge request in another project: <%= xref %>!<%= xmerge_request.iid %>
-- Ignored in code: `!<%= merge_request.iid %>`
-- Ignored in links: [Link to !<%= merge_request.iid %>](#merge-request-link)
+- Merge request: <%= merge_request.to_reference %>
+- Merge request in another project: <%= xmerge_request.to_reference(project) %>
+- Ignored in code: `<%= merge_request.to_reference %>`
+- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
#### SnippetReferenceFilter
-- Snippet: $<%= snippet.id %>
-- Snippet in another project: <%= xref %>$<%= xsnippet.id %>
-- Ignored in code: `$<%= snippet.id %>`
-- Ignored in links: [Link to $<%= snippet.id %>](#snippet-link)
+- Snippet: <%= snippet.to_reference %>
+- Snippet in another project: <%= xsnippet.to_reference(project) %>
+- Ignored in code: `<%= snippet.to_reference %>`
+- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
#### CommitRangeReferenceFilter
-- Range: <%= commit_range %>
-- Range in another project: <%= xref %>@<%= xcommit_range %>
-- Ignored in code: `<%= commit_range %>`
-- Ignored in links: [Link to <%= commit_range %>](#commit-range-link)
+- Range: <%= commit_range.to_reference %>
+- Range in another project: <%= xcommit_range.to_reference(project) %>
+- Ignored in code: `<%= commit_range.to_reference %>`
+- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
#### CommitReferenceFilter
-- Commit: <%= commit.id %>
-- Commit in another project: <%= xref %>@<%= xcommit.id %>
-- Ignored in code: `<%= commit.id %>`
-- Ignored in links: [Link to <%= commit.id %>](#commit-link)
+- Commit: <%= commit.to_reference %>
+- Commit in another project: <%= xcommit.to_reference(project) %>
+- Ignored in code: `<%= commit.to_reference %>`
+- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
#### LabelReferenceFilter
-- Label by ID: ~<%= simple_label.id %>
-- Label by name: ~<%= simple_label.name %>
-- Label by name in quotes: ~"<%= label.name %>"
-- Ignored in code: `~<%= simple_label.name %>`
-- Ignored in links: [Link to ~<%= simple_label.id %>](#label-link)
+- Label by ID: <%= simple_label.to_reference %>
+- Label by name: <%= Label.reference_prefix %><%= simple_label.name %>
+- Label by name in quotes: <%= label.to_reference(:name) %>
+- Ignored in code: `<%= simple_label.to_reference %>`
+- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
### Task Lists
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 3307ac776fc..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 '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
- 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
+ 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)
- 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")
+ user = create(:user, avatar: File.open(avatar_file_path))
+
+ 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 '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 '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 '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'])
@@ -185,79 +206,70 @@ describe ApplicationHelper do
end
end
- describe 'user_color_scheme_class' do
- context 'with current_user is nil' do
- it 'should return a string' do
- allow(self).to receive(:current_user).and_return(nil)
- expect(user_color_scheme_class).to be_kind_of(String)
- end
- end
-
- context 'with a current_user' do
- (1..5).each do |color_scheme_id|
- context "with color_scheme_id == #{color_scheme_id}" do
- it 'should return a string' do
- 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
- end
- end
- end
- end
-
describe 'simple_sanitize' 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 '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 'returns a time element' do
+ expect(element.name).to eq 'time'
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 '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 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 datetime attribute' do
+ expect(element.attr('datetime')).to eq '2015-07-02T08:00:00Z'
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 'has a formatted title attribute' do
+ expect(element.attr('title')).to eq 'Jul 02, 2015 8:00am'
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 'includes a default js-timeago class' do
+ expect(element.attr('class')).to eq 'time_ago js-timeago'
+ end
+
+ 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
@@ -266,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/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
new file mode 100644
index 00000000000..e49e4e6d5d8
--- /dev/null
+++ b/spec/helpers/blob_helper_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe BlobHelper do
+ describe 'highlight' do
+ let(:blob_name) { 'test.lisp' }
+ let(:no_context_content) { ":type \"assem\"))" }
+ let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
+ let(:split_content) { blob_content.split("\n") }
+
+ it 'should return plaintext for unknown lexer context' do
+ result = highlight(blob_name, no_context_content, nowrap: true, continue: false)
+ expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
+ end
+
+ it 'should highlight single block' do
+ expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
+<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
+
+ expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected)
+ end
+
+ it 'should highlight continued blocks' do
+ # Both lines have LC1 as ID since formatter doesn't support continue at the moment
+ expected = [
+ '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
+ '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
+ ]
+
+ result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
+ expect(result).to eq(expected)
+ 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 0d0418f84a7..14c8c29d008 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -26,7 +26,7 @@ describe GitlabMarkdownHelper do
end
describe "referencing multiple objects" do
- let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" }
+ let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -50,7 +50,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do
- actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
+ actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
@@ -63,7 +63,7 @@ describe GitlabMarkdownHelper do
# First issue link
expect(doc.css('a')[1].attr('href')).
to eq namespace_project_issue_path(project.namespace, project, issues[0])
- expect(doc.css('a')[1].text).to eq "##{issues[0].iid}"
+ expect(doc.css('a')[1].text).to eq issues[0].to_reference
# Internal commit link
expect(doc.css('a')[2].attr('href')).to eq commit_path
@@ -72,7 +72,7 @@ describe GitlabMarkdownHelper do
# Second issue link
expect(doc.css('a')[3].attr('href')).
to eq namespace_project_issue_path(project.namespace, project, issues[1])
- expect(doc.css('a')[3].text).to eq "##{issues[1].iid}"
+ expect(doc.css('a')[3].text).to eq issues[1].to_reference
# Trailing commit link
expect(doc.css('a')[4].attr('href')).to eq commit_path
@@ -90,10 +90,16 @@ describe GitlabMarkdownHelper do
end
it "escapes HTML passed in as the body" do
- actual = "This is a <h1>test</h1> - see ##{issues[0].iid}"
+ actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
end
+
+ it 'ignores reference links when they are the entire body' do
+ text = issues[0].to_reference
+ act = link_to_gfm(text, '/foo')
+ expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
+ end
end
describe '#render_wiki_content' do
@@ -127,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 'Tip: 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/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0b7e3b1d11f..0c8d06b7059 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -1,6 +1,70 @@
require 'spec_helper'
describe LabelsHelper do
- it { expect(text_color_for_bg('#EEEEEE')).to eq('#333333') }
- it { expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') }
+ describe 'link_to_label' do
+ let(:project) { create(:empty_project) }
+ let(:label) { create(:label, project: project) }
+
+ context 'with @project set' do
+ before do
+ @project = project
+ end
+
+ it 'uses the instance variable' do
+ expect(label).not_to receive(:project)
+ link_to_label(label)
+ end
+ end
+
+ context 'without @project set' do
+ it "uses the label's project" do
+ expect(label).to receive(:project).and_return(project)
+ link_to_label(label)
+ end
+ end
+
+ context 'with a named project argument' do
+ it 'uses the provided project' do
+ arg = double('project')
+ expect(arg).to receive(:namespace).and_return('foo')
+ expect(arg).to receive(:to_param).and_return('foo')
+
+ link_to_label(label, project: arg)
+ end
+
+ it 'takes precedence over other types' do
+ @project = project
+ expect(@project).not_to receive(:namespace)
+ expect(label).not_to receive(:project)
+
+ arg = double('project', namespace: 'foo', to_param: 'foo')
+ link_to_label(label, project: arg)
+ end
+ end
+
+ context 'with block' do
+ it 'passes the block to link_to' do
+ link = link_to_label(label) { 'Foo' }
+ expect(link).to match('Foo')
+ end
+ end
+
+ context 'without block' do
+ it 'uses render_colored_label as the link content' do
+ expect(self).to receive(:render_colored_label).
+ with(label).and_return('Foo')
+ expect(link_to_label(label)).to match('Foo')
+ end
+ end
+ end
+
+ describe 'text_color_for_bg' do
+ it 'uses light text on dark backgrounds' do
+ expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF')
+ end
+
+ it 'uses dark text on light backgrounds' do
+ expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
+ end
+ end
end
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
new file mode 100644
index 00000000000..d814b562113
--- /dev/null
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe PreferencesHelper do
+ describe 'user_application_theme' do
+ context 'with a user' do
+ it "returns user's theme's css_class" do
+ user = double('user', theme_id: 3)
+ allow(self).to receive(:current_user).and_return(user)
+ expect(user_application_theme).to eq 'ui_green'
+ end
+
+ it 'returns the default when id is invalid' do
+ user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
+
+ allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
+ allow(self).to receive(:current_user).and_return(user)
+
+ expect(user_application_theme).to eq 'ui_charcoal'
+ end
+ end
+
+ context 'without a user' do
+ before do
+ allow(self).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns the default theme' do
+ expect(user_application_theme).to eq Gitlab::Themes.default.css_class
+ end
+ end
+ end
+
+ describe 'dashboard_choices' do
+ it 'raises an exception when defined choices may be missing' do
+ expect(User).to receive(:dashboards).and_return(foo: 'foo')
+ expect { dashboard_choices }.to raise_error(RuntimeError)
+ end
+
+ it 'raises an exception when defined choices may be using the wrong key' do
+ expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
+ expect { dashboard_choices }.to raise_error(KeyError)
+ end
+
+ it 'provides better option descriptions' do
+ expect(dashboard_choices).to match_array [
+ ['Your Projects (default)', 'projects'],
+ ['Starred Projects', 'stars']
+ ]
+ end
+ end
+
+ describe 'user_color_scheme_class' do
+ context 'with current_user is nil' do
+ it 'should return a string' do
+ allow(self).to receive(:current_user).and_return(nil)
+ expect(user_color_scheme_class).to be_kind_of(String)
+ end
+ end
+
+ context 'with a current_user' do
+ (1..5).each do |color_scheme_id|
+ context "with color_scheme_id == #{color_scheme_id}" do
+ it 'should return a string' do
+ 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
+ end
+ 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/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/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee
new file mode 100644
index 00000000000..4ceac619422
--- /dev/null
+++ b/spec/javascripts/extensions/array_spec.js.coffee
@@ -0,0 +1,12 @@
+#= require extensions/array
+
+describe 'Array extensions', ->
+ describe 'first', ->
+ it 'returns the first item', ->
+ arr = [0, 1, 2, 3, 4, 5]
+ expect(arr.first()).toBe(0)
+
+ describe 'last', ->
+ it 'returns the last item', ->
+ arr = [0, 1, 2, 3, 4, 5]
+ expect(arr.last()).toBe(5)
diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee
new file mode 100644
index 00000000000..b10e16b7d01
--- /dev/null
+++ b/spec/javascripts/extensions/jquery_spec.js.coffee
@@ -0,0 +1,34 @@
+#= require extensions/jquery
+
+describe 'jQuery extensions', ->
+ describe 'disable', ->
+ beforeEach ->
+ fixture.set '<input type="text" />'
+
+ it 'adds the disabled attribute', ->
+ $input = $('input').first()
+
+ $input.disable()
+ expect($input).toHaveAttr('disabled', 'disabled')
+
+ it 'adds the disabled class', ->
+ $input = $('input').first()
+
+ $input.disable()
+ expect($input).toHaveClass('disabled')
+
+ describe 'enable', ->
+ beforeEach ->
+ fixture.set '<input type="text" disabled="disabled" class="disabled" />'
+
+ it 'removes the disabled attribute', ->
+ $input = $('input').first()
+
+ $input.enable()
+ expect($input).not.toHaveAttr('disabled')
+
+ it 'removes the disabled class', ->
+ $input = $('input').first()
+
+ $input.enable()
+ expect($input).not.toHaveClass('disabled')
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/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml
new file mode 100644
index 00000000000..42ab4aa68b1
--- /dev/null
+++ b/spec/javascripts/fixtures/issuable.html.haml
@@ -0,0 +1,2 @@
+%form.js-main-target-form
+ %textarea#note_note
diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml
new file mode 100644
index 00000000000..0aecc7334fd
--- /dev/null
+++ b/spec/javascripts/fixtures/issue_note.html.haml
@@ -0,0 +1,12 @@
+%ul
+ %li.note
+ .js-task-list-container
+ .note-text
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ .note-edit-form
+ %form
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
new file mode 100644
index 00000000000..7e8b2a64351
--- /dev/null
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -0,0 +1,13 @@
+%a.btn-close
+
+.issue-details
+ .description.js-task-list-container
+ .wiki
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
+
+%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..15ad1d8968f
--- /dev/null
+++ b/spec/javascripts/fixtures/line_highlighter.html.haml
@@ -0,0 +1,9 @@
+#tree-content-holder
+ .file-content
+ .line-numbers
+ - 1.upto(25) do |i|
+ %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}= 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
new file mode 100644
index 00000000000..f0c622935f8
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests_show.html.haml
@@ -0,0 +1,13 @@
+%a.btn-close
+
+.merge-request-details
+ .description.js-task-list-container
+ .wiki
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
+
+%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml
new file mode 100644
index 00000000000..e867e4de2b9
--- /dev/null
+++ b/spec/javascripts/fixtures/zen_mode.html.haml
@@ -0,0 +1,9 @@
+.zennable
+ %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ .zen-backdrop
+ %textarea#note_note.js-gfm-input.markdown-area{placeholder: 'Leave a comment'}
+ %a.zen-enter-link{tabindex: '-1'}
+ %i.fa.fa-expand
+ Edit in fullscreen
+ %a.zen-leave-link
+ %i.fa.fa-compress
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 13b25862f57..268e4c68c31 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,34 +1,20 @@
-#= require jquery
-#= require jasmine-fixture
#= require issue
describe 'Issue', ->
describe 'task lists', ->
- selectors = {
- container: '.issue-details .description.js-task-list-container'
- item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
- form: 'form.js-issue-update[action="/foo"]'
- close: 'a.btn-close'
- }
+ fixture.preload('issues_show.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
-
- # Task lists don't get initialized unless this button exists. Not ideal.
- $container.append(affix(selectors.close))
-
- # This form is used to get the `update` URL. Not ideal.
- $container.append(affix(selectors.form))
-
+ fixture.load('issues_show.html')
@issue = new Issue()
+ it 'modifies the Markdown field', ->
+ spyOn(jQuery, 'ajax').and.stub()
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
+
it 'submits an ajax request on tasklist:changed', ->
- spyOn($, 'ajax').and.callFake (req) ->
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
expect(req.type).toBe('PATCH')
expect(req.url).toBe('/foo')
expect(req.data.issue.description).not.toBe(null)
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
new file mode 100644
index 00000000000..14fa487ff7f
--- /dev/null
+++ b/spec/javascripts/line_highlighter_spec.js.coffee
@@ -0,0 +1,150 @@
+#= 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()
+
+ 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 3ebc4a4eed5..22ebc7039d1 100644
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -1,34 +1,21 @@
-#= require jquery
-#= require jasmine-fixture
#= require merge_request
describe 'MergeRequest', ->
describe 'task lists', ->
- selectors = {
- container: '.merge-request-details .description.js-task-list-container'
- item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
- form: 'form.js-merge-request-update[action="/foo"]'
- close: 'a.btn-close'
- }
+ fixture.preload('merge_requests_show.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
-
- # Task lists don't get initialized unless this button exists. Not ideal.
- $container.append(affix(selectors.close))
+ fixture.load('merge_requests_show.html')
+ @merge = new MergeRequest({})
- # This form is used to get the `update` URL. Not ideal.
- $container.append(affix(selectors.form))
+ it 'modifies the Markdown field', ->
+ spyOn(jQuery, 'ajax').and.stub()
- @merge = new MergeRequest({})
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
it 'submits an ajax request on tasklist:changed', ->
- spyOn($, 'ajax').and.callFake (req) ->
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
expect(req.type).toBe('PATCH')
expect(req.url).toBe('/foo')
expect(req.data.merge_request.description).not.toBe(null)
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/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
index de2e8e7f6c8..050b6e362c6 100644
--- a/spec/javascripts/notes_spec.js.coffee
+++ b/spec/javascripts/notes_spec.js.coffee
@@ -1,5 +1,3 @@
-#= require jquery
-#= require jasmine-fixture
#= require notes
window.gon = {}
@@ -7,21 +5,18 @@ window.disableButtonIfEmptyField = -> null
describe 'Notes', ->
describe 'task lists', ->
- selectors = {
- container: 'li.note .js-task-list-container'
- item: '.note-text ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.note-edit-form form textarea.js-task-list-field{- [ ] Task List Item}'
- }
+ fixture.preload('issue_note.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
+ fixture.load('issue_note.html')
+ $('form').on 'submit', (e) -> e.preventDefault()
@notes = new Notes()
+ it 'modifies the Markdown field', ->
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
+
it 'submits the form on tasklist:changed', ->
submitted = false
$('form').on 'submit', (e) -> submitted = true; e.preventDefault()
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
index 57dcc2161d3..a01ad7140dd 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js.coffee
+++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee
@@ -1,10 +1,10 @@
-#= require jquery
-#= require jasmine-fixture
-
#= require shortcuts_issuable
describe 'ShortcutsIssuable', ->
+ fixture.preload('issuable.html')
+
beforeEach ->
+ fixture.load('issuable.html')
@shortcut = new ShortcutsIssuable()
describe '#replyWithSelectedText', ->
@@ -14,7 +14,6 @@ describe 'ShortcutsIssuable', ->
beforeEach ->
@selector = 'form.js-main-target-form textarea#note_note'
- affix(@selector)
describe 'with empty selection', ->
it 'does nothing', ->
diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
new file mode 100644
index 00000000000..47b41dd2c81
--- /dev/null
+++ b/spec/javascripts/spec_helper.coffee
@@ -0,0 +1,46 @@
+# PhantomJS (Teaspoons default driver) doesn't have support for
+# Function.prototype.bind, which has caused confusion. Use this polyfill to
+# avoid the confusion.
+
+#= require support/bind-poly
+
+# You can require your own javascript files here. By default this will include
+# everything in application, however you may get better load performance if you
+# require the specific files that are being used in the spec that tests them.
+
+#= require jquery
+#= require bootstrap
+#= require underscore
+
+# Teaspoon includes some support files, but you can use anything from your own
+# support path too.
+
+# require support/jasmine-jquery-1.7.0
+# require support/jasmine-jquery-2.0.0
+#= require support/jasmine-jquery-2.1.0
+# require support/sinon
+# require support/your-support-file
+
+# Deferring execution
+
+# If you're using CommonJS, RequireJS or some other asynchronous library you can
+# defer execution. Call Teaspoon.execute() after everything has been loaded.
+# Simple example of a timeout:
+
+# Teaspoon.defer = true
+# setTimeout(Teaspoon.execute, 1000)
+
+# Matching files
+
+# By default Teaspoon will look for files that match
+# _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
+# and it'll be included in the default suite automatically. If you want to
+# customize suites, check out the configuration in teaspoon_env.rb
+
+# Manifest
+
+# If you'd rather require your spec files manually (to control order for
+# instance) you can disable the suite matcher in the configuration and use this
+# file as a manifest.
+
+# For more information: http://github.com/modeset/teaspoon
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
index ee90892eb48..dbafe782b77 100644
--- a/spec/javascripts/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/stat_graph_contributors_util_spec.js
@@ -118,9 +118,11 @@ describe("ContributorsStatGraphUtil", function () {
describe("#add_author", function () {
it("adds an author field to the collection", function () {
var fake_author = { author_name: "Author", author_email: 'fake@email.com' }
- var fake_collection = {}
- ContributorsStatGraphUtil.add_author(fake_author, fake_collection)
- expect(fake_collection[fake_author.author_name].author_name).toEqual("Author")
+ var fake_author_collection = {}
+ var fake_email_collection = {}
+ ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection)
+ expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author")
+ expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author")
})
})
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
deleted file mode 100644
index 168c9618643..00000000000
--- a/spec/javascripts/support/jasmine.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# path to parent directory of spec_files
-# relative path from Rails.root
-#
-# Alternatively accept an array of directory to include external spec files
-# spec_dir:
-# - spec/javascripts
-# - ../engine/spec/javascripts
-#
-# defaults to spec/javascripts
-spec_dir: spec/javascripts
-
-# list of file expressions to include as specs into spec runner
-# relative path from spec_dir
-spec_files:
- - "**/*[Ss]pec.{js.coffee,js,coffee}"
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
deleted file mode 100644
index 4d73aec5a31..00000000000
--- a/spec/javascripts/support/jasmine_helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-#Use this file to set/override Jasmine configuration options
-#You can remove it if you don't need it.
-#This file is loaded *after* jasmine.yml is interpreted.
-#
-#Example: using a different boot file.
-#Jasmine.configure do |config|
-# config.boot_dir = '/absolute/path/to/boot_dir'
-# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
-#end
-#
-#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
-#Jasmine.configure do |config|
-# config.prevent_phantom_js_auto_install = true
-#end
-#
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
new file mode 100644
index 00000000000..1f4ea58ad48
--- /dev/null
+++ b/spec/javascripts/zen_mode_spec.js.coffee
@@ -0,0 +1,52 @@
+#= require zen_mode
+
+describe 'ZenMode', ->
+ fixture.preload('zen_mode.html')
+
+ beforeEach ->
+ fixture.load('zen_mode.html')
+
+ # Stub Dropzone.forElement(...).enable()
+ spyOn(Dropzone, 'forElement').and.callFake ->
+ enable: -> true
+
+ @zen = new ZenMode()
+
+ # Set this manually because we can't actually scroll the window
+ @zen.scroll_position = 456
+
+ # Ohmmmmmmm
+ enterZen = ->
+ $('.zen-toggle-comment').prop('checked', true).trigger('change')
+
+ # Wh- what was that?!
+ exitZen = ->
+ $('.zen-toggle-comment').prop('checked', false).trigger('change')
+
+ describe 'on enter', ->
+ it 'pauses Mousetrap', ->
+ spyOn(Mousetrap, 'pause')
+ enterZen()
+ expect(Mousetrap.pause).toHaveBeenCalled()
+
+ describe 'in use', ->
+ beforeEach ->
+ enterZen()
+
+ it 'exits on Escape', ->
+ $(document).trigger(jQuery.Event('keydown', {keyCode: 27}))
+ expect($('.zen-toggle-comment').prop('checked')).toBe(false)
+
+ describe 'on exit', ->
+ beforeEach ->
+ enterZen()
+
+ it 'unpauses Mousetrap', ->
+ spyOn(Mousetrap, 'unpause')
+ exitZen()
+ expect(Mousetrap.unpause).toHaveBeenCalled()
+
+ it 'restores the scroll position', ->
+ spyOn(@zen, 'restoreScroll')
+ exitZen()
+ expect(@zen.restoreScroll).toHaveBeenCalledWith(456)
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..f077c80d478 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -9,13 +9,16 @@ 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'])
+ 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)
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 d0aad54f677..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
@@ -156,7 +156,7 @@ describe Grack::Auth do
end
expect(attempt_login(true)).to eq(200)
- expect(Rack::Attack::Allow2Ban.send(:banned?, ip)).to eq(nil)
+ expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
for n in 0..maxretry do
expect(attempt_login(false)).to eq(401)
diff --git a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb
deleted file mode 100644
index 2ac496fd669..00000000000
--- a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require "spec_helper"
-
-describe 'RackAttackHelpers' do
- describe 'reset' do
- let(:discriminator) { 'test-key'}
- let(:maxretry) { 5 }
- let(:period) { 1.minute }
- let(:options) { { findtime: period, bantime: 60, maxretry: maxretry } }
-
- def do_filter
- for i in 1..maxretry - 1 do
- status = Rack::Attack::Allow2Ban.filter(discriminator, options) { true }
- expect(status).to eq(false)
- end
- end
-
- def do_reset
- Rack::Attack::Allow2Ban.reset(discriminator, options)
- end
-
- before do
- do_reset
- end
-
- after do
- do_reset
- end
-
- it 'user is not banned after n - 1 retries' do
- do_filter
- do_reset
- do_filter
- end
- end
-end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 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/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index cb7b0fbb890..5d7ff4f6122 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,131 +1,131 @@
require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:iid1) { issue.iid }
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:reference) { issue.to_reference }
subject { described_class.new(project, project.creator) }
describe "#closed_by_message" do
context 'with a single reference' do
it do
- message = "Awesome commit (Closes ##{iid1})"
+ message = "Awesome commit (Closes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Awesome commit (closes ##{iid1})"
+ message = "Awesome commit (closes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Closed ##{iid1}"
+ message = "Closed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "closed ##{iid1}"
+ message = "closed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Closing ##{iid1}"
+ message = "Closing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "closing ##{iid1}"
+ message = "closing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Close ##{iid1}"
+ message = "Close #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "close ##{iid1}"
+ message = "close #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Awesome commit (Fixes ##{iid1})"
+ message = "Awesome commit (Fixes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Awesome commit (fixes ##{iid1})"
+ message = "Awesome commit (fixes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Fixed ##{iid1}"
+ message = "Fixed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "fixed ##{iid1}"
+ message = "fixed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Fixing ##{iid1}"
+ message = "Fixing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "fixing ##{iid1}"
+ message = "fixing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Fix ##{iid1}"
+ message = "Fix #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "fix ##{iid1}"
+ message = "fix #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Awesome commit (Resolves ##{iid1})"
+ message = "Awesome commit (Resolves #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Awesome commit (resolves ##{iid1})"
+ message = "Awesome commit (resolves #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Resolved ##{iid1}"
+ message = "Resolved #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "resolved ##{iid1}"
+ message = "resolved #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Resolving ##{iid1}"
+ message = "Resolving #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "resolving ##{iid1}"
+ message = "resolving #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "Resolve ##{iid1}"
+ message = "Resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
- message = "resolve ##{iid1}"
+ message = "resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
end
@@ -133,40 +133,40 @@ describe Gitlab::ClosingIssueExtractor do
context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
- let(:iid2) { other_issue.iid }
- let(:iid3) { third_issue.iid }
+ let(:reference2) { other_issue.to_reference }
+ let(:reference3) { third_issue.to_reference }
it 'fetches issues in single line message' do
- message = "Closes ##{iid1} and fix ##{iid2}"
+ message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches comma-separated issues references in single line message' do
- message = "Closes ##{iid1}, closes ##{iid2}"
+ message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches comma-separated issues numbers in single line message' do
- message = "Closes ##{iid1}, ##{iid2} and ##{iid3}"
+ message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
- message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}"
+ message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches issues in hybrid message' do
- message = "Awesome commit (closes ##{iid1})\n"\
- "Also fixing issues ##{iid2}, ##{iid3} and #4"
+ message = "Awesome commit (closes #{reference})\n"\
+ "Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
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 2189e313d6a..c38f212b405 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -8,30 +8,39 @@ 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 }
it "should block user in GitLab" do
access.allowed?
- user.should be_blocked
+ expect(user).to be_blocked
end
end
context 'and has no disabled flag in active diretory' do
before do
user.block
-
- Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
+
+ allow(Gitlab::LDAP::Person).
+ to receive(:disabled_via_active_directory?).and_return(false)
end
it { is_expected.to be_truthy }
@@ -39,32 +48,35 @@ 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
access.allowed?
- user.should be_blocked
+ expect(user).to be_blocked
end
end
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
access.allowed?
- user.should_not be_blocked
+ expect(user).not_to be_blocked
end
end
end
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 0bbdc11a979..982be0782c9 100644
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
@@ -2,11 +2,9 @@ require 'spec_helper'
module Gitlab::Markdown
describe AutolinkFilter do
- let(:link) { 'http://about.gitlab.com/' }
+ include FilterSpecHelper
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ let(:link) { 'http://about.gitlab.com/' }
it 'does nothing when :autolink is false' do
exp = act = link
@@ -50,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
@@ -91,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/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index 7274cb309a0..e8391cc7aca 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -2,40 +2,42 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitRangeReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
+ let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
+ let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
+
it 'requires project context' do
- expect { described_class.call('Commit Range 1c002d..d200c1', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
+ exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
- let(:reference) { "#{commit1.id}...#{commit2.id}" }
- let(:reference2) { "#{commit1.id}..#{commit2.id}" }
+ let(:reference) { range.to_reference }
+ let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do
doc = filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id)
+ to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id)
+ to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
end
it 'links to a valid short ID' do
@@ -51,7 +53,7 @@ module Gitlab::Markdown
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
- exp = Regexp.escape("#{commit1.short_id}...#{commit2.short_id}")
+ exp = Regexp.escape(range.to_s)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
@@ -65,7 +67,7 @@ module Gitlab::Markdown
it 'includes a title attribute' do
doc = filter("See #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}"
+ expect(doc.css('a').first.attr('title')).to eq range.reference_title
end
it 'includes default classes' do
@@ -95,9 +97,11 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
- let(:commit1) { project.commit }
- let(:commit2) { project.commit("HEAD~2") }
- let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
+ let(:reference) { range.to_reference(project) }
+
+ before do
+ range.project = project2
+ end
context 'when user can access reference' do
before { allow_cross_reference! }
@@ -106,21 +110,21 @@ module Gitlab::Markdown
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id)
+ to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
- exp = Regexp.escape("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}")
+ exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}"
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
- exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}"
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index cc32a4fcf03..a10d43c9a02 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -2,14 +2,13 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:commit) { project.commit }
it 'requires project context' do
- expect { described_class.call('Commit 1c002d', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
@@ -47,10 +46,11 @@ module Gitlab::Markdown
end
it 'ignores invalid commit IDs' do
- exp = act = "See #{reference.reverse}"
+ invalid = invalidate_reference(reference)
+ exp = act = "See #{invalid}"
expect(project).to receive(:valid_repo?).and_return(true)
- expect(project.repository).to receive(:commit).with(reference.reverse)
+ expect(project.repository).to receive(:commit).with(invalid)
expect(filter(act).to_html).to eq exp
end
@@ -93,8 +93,8 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
- let(:commit) { project.commit }
- let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" }
+ let(:commit) { project2.commit }
+ let(:reference) { commit.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
@@ -109,12 +109,12 @@ module Gitlab::Markdown
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
- exp = Regexp.escape(project2.path_with_namespace)
+ exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}"
+ exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
index 18d55c4818f..11efd9bb4cd 100644
--- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe EmojiFilter do
- def filter(html, contexts = {})
- described_class.call(html, contexts)
- end
+ include FilterSpecHelper
before do
ActionController::Base.asset_host = 'https://foo.com'
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
index b19bc125b92..f16095bc2b2 100644
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -2,26 +2,25 @@ require 'spec_helper'
module Gitlab::Markdown
describe ExternalIssueReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
def helper
IssuesHelper
end
let(:project) { create(:jira_project) }
- let(:issue) { double('issue', iid: 123) }
context 'JIRA issue references' do
- let(:reference) { "JIRA-#{issue.iid}" }
+ let(:issue) { ExternalIssue.new('JIRA-123', project) }
+ let(:reference) { issue.to_reference }
it 'requires project context' do
- expect { described_class.call('Issue JIRA-123', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Issue JIRA-#{issue.iid}</#{elem}>"
+ exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
@@ -33,13 +32,6 @@ module Gitlab::Markdown
expect(filter(act).to_html).to eq exp
end
- %w(pre code a style).each do |elem|
- it "ignores references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
-
it 'links to a valid reference' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('href'))
diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
new file mode 100644
index 00000000000..a040b34577b
--- /dev/null
+++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe ExternalLinkFilter do
+ include FilterSpecHelper
+
+ it 'ignores elements without an href attribute' do
+ exp = act = %q(<a id="ignored">Ignore Me</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'ignores non-HTTP(S) links' do
+ exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'skips internal links' do
+ internal = Gitlab.config.gitlab.url
+ exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds rel="nofollow" to external links' do
+ act = %q(<a href="https://google.com/">Google</a>)
+ doc = filter(act)
+
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to eq 'nofollow'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 08382b3e7e8..fa43d33794d 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe IssueReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
def helper
IssuesHelper
@@ -12,24 +12,23 @@ module Gitlab::Markdown
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
- expect { described_class.call('Issue #123', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Issue ##{issue.iid}</#{elem}>"
+ exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
- let(:reference) { "##{issue.iid}" }
+ let(:reference) { issue.to_reference }
it 'ignores valid references when using non-default tracker' do
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
- exp = act = "Issue ##{issue.iid}"
+ exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
@@ -46,9 +45,9 @@ module Gitlab::Markdown
end
it 'ignores invalid issue IDs' do
- exp = act = "Fixed ##{issue.iid + 1}"
+ invalid = invalidate_reference(reference)
+ exp = act = "Fixed #{invalid}"
- expect(project).to receive(:get_issue).with(issue.iid + 1).and_return(nil)
expect(filter(act).to_html).to eq exp
end
@@ -92,7 +91,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
- let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
+ let(:reference) { issue.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
@@ -101,7 +100,7 @@ module Gitlab::Markdown
expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
- exp = act = "Issue ##{issue.iid}"
+ exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
@@ -118,7 +117,7 @@ module Gitlab::Markdown
end
it 'ignores invalid issue IDs on the referenced project' do
- exp = act = "Fixed #{project2.path_with_namespace}##{issue.iid + 1}"
+ exp = act = "Fixed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index 9f898837466..e9f8ed277a5 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -3,15 +3,14 @@ require 'html/pipeline'
module Gitlab::Markdown
describe LabelReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) }
- let(:reference) { "~#{label.id}" }
+ let(:reference) { label.to_reference }
it 'requires project context' do
- expect { described_class.call('Label ~123', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
@@ -36,7 +35,7 @@ module Gitlab::Markdown
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true)
+ expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
end
it 'adds to the results hash' do
@@ -70,7 +69,7 @@ module Gitlab::Markdown
end
it 'ignores invalid label IDs' do
- exp = act = "Label ~#{label.id + 1}"
+ exp = act = "Label #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
@@ -78,7 +77,7 @@ module Gitlab::Markdown
context 'String-based single-word references' do
let(:label) { create(:label, name: 'gfm', project: project) }
- let(:reference) { "~#{label.name}" }
+ let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
@@ -94,59 +93,40 @@ module Gitlab::Markdown
end
it 'ignores invalid label names' do
- exp = act = "Label ~#{label.name.reverse}"
+ exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
expect(filter(act).to_html).to eq exp
end
end
context 'String-based multi-word references in quotes' do
- let(:label) { create(:label, name: 'gfm references', project: project) }
+ let(:label) { create(:label, name: 'gfm references', project: project) }
+ let(:reference) { label.to_reference(:name) }
- context 'in single quotes' do
- let(:reference) { "~'#{label.name}'" }
-
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).to eq urls.
- namespace_project_issues_url(project.namespace, project, label_name: label.name)
- expect(doc.text).to eq 'See gfm references'
- end
-
- it 'links with adjacent text' do
- doc = filter("Label (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- end
-
- it 'ignores invalid label names' do
- exp = act = "Label ~'#{label.name.reverse}'"
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- expect(filter(act).to_html).to eq exp
- end
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See gfm references'
end
- context 'in double quotes' do
- let(:reference) { %(~"#{label.name}") }
-
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).to eq urls.
- namespace_project_issues_url(project.namespace, project, label_name: label.name)
- expect(doc.text).to eq 'See gfm references'
- end
+ it 'links with adjacent text' do
+ doc = filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ end
- it 'links with adjacent text' do
- doc = filter("Label (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- end
+ it 'ignores invalid label names' do
+ exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
- it 'ignores invalid label names' do
- exp = act = %(Label ~"#{label.name.reverse}")
+ expect(filter(act).to_html).to eq exp
+ end
+ end
- expect(filter(act).to_html).to eq exp
- end
+ describe 'edge cases' do
+ 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
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index d6e745114f2..5945302a2da 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -2,25 +2,24 @@ require 'spec_helper'
module Gitlab::Markdown
describe MergeRequestReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
- expect { described_class.call('MergeRequest !123', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Merge !#{merge.iid}</#{elem}>"
+ exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
- let(:reference) { "!#{merge.iid}" }
+ let(:reference) { merge.to_reference }
it 'links to a valid reference' do
doc = filter("See #{reference}")
@@ -35,7 +34,7 @@ module Gitlab::Markdown
end
it 'ignores invalid merge IDs' do
- exp = act = "Merge !#{merge.iid + 1}"
+ exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
@@ -80,7 +79,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
- let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
+ let(:reference) { merge.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
@@ -99,7 +98,7 @@ module Gitlab::Markdown
end
it 'ignores invalid merge IDs on the referenced project' do
- exp = act = "Merge #{project2.path_with_namespace}!#{merge.iid + 1}"
+ exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
index 4a1aa766149..e50c82d0b3c 100644
--- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe SanitizationFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
describe 'default whitelist' do
it 'sanitizes tags that are not whitelisted' do
@@ -42,6 +40,13 @@ module Gitlab::Markdown
end
describe 'custom whitelist' do
+ it 'customizes the whitelist only once' do
+ instance = described_class.new('Foo')
+ 3.times { instance.whitelist }
+
+ expect(instance.whitelist[:transformers].size).to eq 4
+ end
+
it 'allows syntax highlighting' do
exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
expect(filter(act).to_html).to eq exp
@@ -87,5 +92,27 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to be_nil
end
end
+
+ context 'when pipeline is :description' do
+ it 'uses a stricter whitelist' do
+ doc = filter('<h1>Description</h1>', pipeline: :description)
+ expect(doc.to_html.strip).to eq 'Description'
+ end
+
+ %w(pre code img ol ul li).each do |elem|
+ it "removes '#{elem}' elements" do
+ act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, pipeline: :description).to_html.strip).
+ to eq 'Description'
+ end
+ end
+
+ %w(b i strong em a ins del sup sub p).each do |elem|
+ it "still allows '#{elem}' elements" do
+ exp = act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, pipeline: :description).to_html).to eq exp
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index a4b331157af..38619a3c07f 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -2,15 +2,14 @@ require 'spec_helper'
module Gitlab::Markdown
describe SnippetReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:snippet) { create(:project_snippet, project: project) }
- let(:reference) { "$#{snippet.id}" }
+ let(:reference) { snippet.to_reference }
it 'requires project context' do
- expect { described_class.call('Snippet $123', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
@@ -34,7 +33,7 @@ module Gitlab::Markdown
end
it 'ignores invalid snippet IDs' do
- exp = act = "Snippet $#{snippet.id + 1}"
+ exp = act = "Snippet #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
@@ -79,7 +78,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
- let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
+ let(:reference) { snippet.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
@@ -97,7 +96,7 @@ module Gitlab::Markdown
end
it 'ignores invalid snippet IDs on the referenced project' do
- exp = act = "See #{project2.path_with_namespace}$#{snippet.id + 1}"
+ exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
index f383a5850d5..ddf583a72c1 100644
--- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
@@ -4,9 +4,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe TableOfContentsFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
def header(level, text)
"<h#{level}>#{text}</h#{level}>\n"
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
index 2a1e1cc5127..94f39cc966e 100644
--- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe TaskListFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
it 'does not apply `task-list` class to non-task lists' do
exp = act = %(<ul><li>Item</li></ul>)
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index 922502ada33..08e6941028f 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -2,67 +2,65 @@ require 'spec_helper'
module Gitlab::Markdown
describe UserReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
- let(:project) { create(:empty_project) }
- let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:reference) { user.to_reference }
it 'requires project context' do
- expect { described_class.call('Example @mention', {}) }.
- to raise_error(ArgumentError, /:project/)
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
it 'ignores invalid users' do
- exp = act = 'Hey @somebody'
+ exp = act = "Hey #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Hey @#{user.username}</#{elem}>"
+ exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'mentioning @all' do
+ let(:reference) { User.reference_prefix + 'all' }
+
before do
project.team << [project.creator, :developer]
end
it 'supports a special @all mention' do
- doc = filter("Hey @all")
+ doc = filter("Hey #{reference}")
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
end
it 'adds to the results hash' do
- result = pipeline_result('Hey @all')
+ result = pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator]
end
end
context 'mentioning a user' do
- let(:reference) { "@#{user.username}" }
-
it 'links to a User' do
doc = filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
- # TODO (rspeicher): This test might be overkill
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
- doc = filter("Hey @#{user.username}")
+ doc = filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
- # TODO (rspeicher): This test might be overkill
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
- doc = filter("Hey @#{user.username}")
+ doc = filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
@@ -73,10 +71,9 @@ module Gitlab::Markdown
end
context 'mentioning a group' do
- let(:group) { create(:group) }
- let(:user) { create(:user) }
-
- let(:reference) { "@#{group.name}" }
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:reference) { group.to_reference }
context 'that the current user can read' do
before do
@@ -108,23 +105,23 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
- skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.'
- doc = filter("Mention me (@#{user.username}.)")
- expect(doc.to_html).to match(/\(<a.+>@#{user.username}<\/a>\.\)/)
+ skip "TODO (rspeicher): Re-enable when usernames can't end in periods."
+ doc = filter("Mention me (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes default classes' do
- doc = filter("Hey @#{user.username}")
+ doc = filter("Hey #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
it 'includes an optional custom class' do
- doc = filter("Hey @#{user.username}", reference_class: 'custom')
+ doc = filter("Hey #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
- doc = filter("Hey @#{user.username}", only_path: true)
+ doc = filter("Hey #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index 448cd0c6880..5826144e66b 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -36,6 +36,7 @@ describe 'Gitlab::NoteDataBuilder' do
let(:note) { create(:note_on_issue, noteable_id: issue.id) }
it 'returns the note and issue-specific data' do
+ data[:issue]["updated_at"] = fixed_time
expect(data).to have_key(:issue)
expect(data[:issue]).to eq(issue.hook_attrs)
end
@@ -46,6 +47,7 @@ describe 'Gitlab::NoteDataBuilder' do
let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
it 'returns the note and merge request data' do
+ data[:merge_request]["updated_at"] = fixed_time
expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs)
end
@@ -56,6 +58,7 @@ describe 'Gitlab::NoteDataBuilder' do
let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) }
it 'returns the note and merge request diff data' do
+ data[:merge_request]["updated_at"] = fixed_time
expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs)
end
@@ -66,6 +69,7 @@ describe 'Gitlab::NoteDataBuilder' do
let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
it 'returns the note and project snippet data' do
+ data[:snippet]["updated_at"] = fixed_time
expect(data).to have_key(:snippet)
expect(data[:snippet]).to eq(snippet.hook_attrs)
end
diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
index 678086ffa14..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
@@ -80,31 +80,31 @@ describe Gitlab::OAuth::AuthHash do
context 'auth_hash constructed with ASCII-8BIT encoding' do
it 'forces utf8 encoding on uid' do
- auth_hash.uid.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.uid.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on provider' do
- auth_hash.provider.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.provider.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on name' do
- auth_hash.name.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.name.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on full_name' do
- auth_hash.full_name.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.full_name.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on username' do
- auth_hash.username.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.username.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on email' do
- auth_hash.email.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.email.encoding).to eql Encoding::UTF_8
end
it 'forces utf8 encoding on password' do
- auth_hash.password.encoding.should eql Encoding::UTF_8
+ expect(auth_hash.password.encoding).to eql Encoding::UTF_8
end
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 44cdd1e4fab..c6cca98a037 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -13,52 +13,137 @@ describe Gitlab::OAuth::User do
email: 'john@mail.com'
}
end
+ let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe :persisted? do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
+ # 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
- context "with allow_single_sign_on enabled" do
- before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+ shared_examples "to verify compliance with allow_single_sign_on" do
+ context "with allow_single_sign_on enabled" do
+ before { stub_omniauth_config(allow_single_sign_on: true) }
- it "creates a user from Omniauth" do
- oauth_user.save
+ it "creates a user from Omniauth" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
- expect(gl_user).to be_valid
- identity = gl_user.identities.first
- expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ context "with allow_single_sign_on disabled (Default)" do
+ before { stub_omniauth_config(allow_single_sign_on: false) }
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
end
end
- context "with allow_single_sign_on disabled (Default)" do
- it "throws an error" do
- expect{ oauth_user.save }.to raise_error StandardError
+ context "with auto_link_ldap_user disabled (default)" do
+ 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 { stub_omniauth_config(auto_link_ldap_user: true) }
+
+ context "and no LDAP provider defined" do
+ 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 { stub_ldap_config(providers: %w(ldapmain)) }
+
+ context "and a corresponding LDAP person" do
+ before do
+ 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'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ 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'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ 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
end
+
end
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' do
+ 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
@@ -68,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
@@ -78,6 +163,64 @@ describe Gitlab::OAuth::User do
end
end
+ context 'signup with linked omniauth and LDAP account' do
+ before do
+ 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 { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context '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') }
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+
+
context 'sign-in' do
before do
oauth_user.save
@@ -85,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
@@ -95,7 +238,27 @@ 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
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ 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/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 9801dc16554..f921dd9cc09 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -16,11 +16,35 @@ describe Gitlab::ReferenceExtractor do
expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
end
+ it 'ignores user mentions inside specific elements' do
+ @u_foo = create(:user, username: 'foo')
+ @u_bar = create(:user, username: 'bar')
+ @u_offteam = create(:user, username: 'offteam')
+
+ project.team << [@u_foo, :reporter]
+ project.team << [@u_bar, :guest]
+
+ subject.analyze(%Q{
+ Inline code: `@foo`
+
+ Code block:
+
+ ```
+ @bar
+ ```
+
+ Quote:
+
+ > @offteam
+ })
+ expect(subject.users).to eq([])
+ end
+
it 'accesses valid issue objects' do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
- subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.")
+ subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
expect(subject.issues).to eq([@i0, @i1])
end
@@ -82,7 +106,7 @@ describe Gitlab::ReferenceExtractor do
end
it 'handles project issue references' do
- subject.analyze("this refers issue #{other_project.path_with_namespace}##{issue.iid}")
+ subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues
expect(extracted.size).to eq(1)
expect(extracted).to eq([issue])
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/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
new file mode 100644
index 00000000000..9c6c3fd8104
--- /dev/null
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::Themes do
+ describe '.body_classes' do
+ it 'returns a space-separated list of class names' do
+ css = described_class.body_classes
+
+ expect(css).to include('ui_graphite')
+ expect(css).to include(' ui_charcoal ')
+ expect(css).to include(' ui_blue')
+ end
+ end
+
+ describe '.by_id' do
+ it 'returns a Theme by its ID' do
+ expect(described_class.by_id(1).name).to eq 'Graphite'
+ expect(described_class.by_id(6).name).to eq 'Blue'
+ end
+ end
+
+ describe '.default' do
+ it 'returns the default application theme' do
+ allow(described_class).to receive(:default_id).and_return(2)
+ expect(described_class.default.id).to eq 2
+ end
+
+ it 'prevents an infinite loop when configuration default is invalid' do
+ default = described_class::APPLICATION_DEFAULT
+ themes = described_class::THEMES
+
+ config = double(default_theme: 0).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+
+ config = double(default_theme: themes.size + 5).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+ end
+ end
+
+ describe '.each' do
+ it 'passes the block to the THEMES Array' do
+ ids = []
+ described_class.each { |theme| ids << theme.id }
+ expect(ids).not_to be_empty
+
+ # TODO (rspeicher): RSpec 3.x
+ # expect(described_class.each).to yield_with_arg(described_class::Theme)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index ce3ea6c260a..8df84665e16 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -10,15 +10,30 @@ 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
+
+ it 'should get the latest version from tags' do
+ allow(upgrader).to receive(:fetch_git_tags).and_return([
+ '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1',
+ 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}',
+ 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4',
+ '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}',
+ '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1',
+ '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}',
+ '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2',
+ 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}',
+ '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4',
+ 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}'])
+ expect(upgrader.latest_version_raw).to eq("v7.11.2")
+ end
end
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/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index af399f3a731..37240d51310 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,4 +1,3 @@
-require 'rspec'
require_relative '../../lib/repository_cache'
describe RepositoryCache do
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 37607b55eb1..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
@@ -185,7 +186,7 @@ describe Notify do
context 'for issues' do
let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) }
- let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) }
+ let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: FFaker::Lorem.sentence) }
describe 'that are new' do
subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
@@ -273,7 +274,7 @@ describe Notify do
context 'for merge requests' do
let(:merge_author) { create(:user) }
let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
- let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) }
+ let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: FFaker::Lorem.sentence) }
describe 'that are new' do
subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -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/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 116c318121d..d648f4078be 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -15,6 +15,7 @@
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# max_attachment_size :integer default(10), not null
+# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 31ee3e99cad..1031af097bd 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -1,6 +1,12 @@
require 'spec_helper'
describe CommitRange do
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Referable) }
+ end
+
let(:sha_from) { 'f3f85602' }
let(:sha_to) { 'e86e1013' }
@@ -8,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
@@ -21,6 +27,23 @@ describe CommitRange do
end
end
+ describe '#to_reference' do
+ let(:project) { double('project', to_reference: 'namespace1/project') }
+
+ before do
+ range.project = project
+ end
+
+ it 'returns a String reference to the object' do
+ expect(range.to_reference).to eq range.to_s
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
+ end
+ end
+
describe '#reference_title' do
it 'returns the correct String for three-dot ranges' do
expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
@@ -37,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 ad2ac143d97..e303a97e6b5 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,8 +1,28 @@
require 'spec_helper'
describe Commit do
- let(:project) { create :project }
- let(:commit) { project.commit }
+ let(:project) { create(:project) }
+ let(:commit) { project.commit }
+
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Mentionable) }
+ it { is_expected.to include_module(Participable) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(StaticModel) }
+ end
+
+ describe '#to_reference' do
+ it 'returns a String reference to the object' do
+ expect(commit.to_reference).to eq commit.id
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}"
+ end
+ end
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
@@ -57,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
@@ -73,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 557c71b4d2c..b6d80451d2e 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -11,12 +11,15 @@ 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) }
it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to ensure_length_of(:title).is_at_least(0).is_at_most(255) }
+ it { is_expected.to validate_length_of(:title).is_at_least(0).is_at_most(255) }
end
describe "Scope" do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index eadb941a3fa..f7f66987b5f 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,14 +1,31 @@
require 'spec_helper'
describe Issue, "Mentionable" do
- describe :mentioned_users do
+ describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') }
- let!(:issue) { create(:issue, description: '@stranger mentioned') }
+ let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") }
subject { issue.mentioned_users }
it { is_expected.to include(user) }
it { is_expected.not_to include(user2) }
end
+
+ describe '#create_cross_references!' do
+ let(:project) { create(:project) }
+ let(:author) { double('author') }
+ let(:commit) { project.commit }
+ let(:commit2) { project.commit }
+
+ let!(:issue) do
+ create(:issue, project: project, description: commit.to_reference)
+ end
+
+ it 'correctly removes already-mentioned Commits' do
+ expect(SystemNoteService).not_to receive(:cross_reference)
+
+ issue.create_cross_references!(project, author, [commit2])
+ end
+ end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 7032b777144..0eb22599d18 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -36,9 +36,7 @@ describe DeployKeysProject do
it "doesn't destroy the deploy key" do
subject.destroy
- expect {
- deploy_key.reload
- }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { deploy_key.reload }.not_to raise_error
end
end
@@ -46,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
@@ -63,9 +59,7 @@ describe DeployKeysProject do
it "doesn't destroy the deploy key" do
subject.destroy
- expect {
- deploy_key.reload
- }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { deploy_key.reload }.not_to raise_error
end
end
end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
new file mode 100644
index 00000000000..7744610db78
--- /dev/null
+++ b/spec/models/external_issue_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe ExternalIssue do
+ let(:project) { double('project', to_reference: 'namespace1/project1') }
+ let(:issue) { described_class.new('EXT-1234', project) }
+
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Referable) }
+ end
+
+ describe '#to_reference' do
+ it 'returns a String reference to the object' do
+ expect(issue.to_reference).to eq issue.id
+ end
+ end
+
+ describe '#title' do
+ it 'returns a title' do
+ expect(issue.title).to eq "External Issue #{issue}"
+ end
+ end
+end
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb
index f2e77fc88c4..4bd5b0be61c 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/external_wiki_service_spec.rb
@@ -52,7 +52,7 @@ describe ExternalWikiService do
it 'should replace the wiki url' do
wiki_path = get_project_wiki_path(project)
- wiki_path.should match('https://gitlab.com')
+ expect(wiki_path).to match('https://gitlab.com')
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/group_spec.rb b/spec/models/group_spec.rb
index 9428224a64f..80638fc8db2 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -18,16 +18,30 @@ require 'spec_helper'
describe Group do
let!(:group) { create(:group) }
- describe "Associations" do
+ describe 'associations' do
it { is_expected.to have_many :projects }
it { is_expected.to have_many :group_members }
end
- it { is_expected.to validate_presence_of :name }
- it { is_expected.to validate_uniqueness_of(:name) }
- it { is_expected.to validate_presence_of :path }
- it { is_expected.to validate_uniqueness_of(:path) }
- it { is_expected.not_to validate_presence_of :owner }
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Referable) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of :name }
+ it { is_expected.to validate_uniqueness_of(:name) }
+ it { is_expected.to validate_presence_of :path }
+ it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.not_to validate_presence_of :owner }
+ end
+
+ describe '#to_reference' do
+ it 'returns a String reference to the object' do
+ expect(group.to_reference).to eq "@#{group.name}"
+ end
+ end
describe :users do
it { expect(group.users).to eq(group.owners) }
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 20d823b40e5..9bac451c28c 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -24,15 +24,30 @@ describe Issue do
it { is_expected.to belong_to(:milestone) }
end
- describe "Mass assignment" do
- end
-
describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(InternalId) }
it { is_expected.to include_module(Issuable) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(Sortable) }
+ it { is_expected.to include_module(Taskable) }
end
subject { create(:issue) }
+ describe '#to_reference' do
+ it 'returns a String reference to the object' do
+ expect(subject.to_reference).to eq "##{subject.iid}"
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(subject.to_reference(cross)).
+ to eq "#{subject.project.to_reference}##{subject.iid}"
+ end
+ end
+
describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do
subject.assignee = create(:user)
@@ -45,11 +60,8 @@ describe Issue do
describe '#is_being_reassigned?' do
it 'returns issues assigned to user' do
- user = create :user
-
- 2.times do
- issue = create :issue, assignee: user
- end
+ user = create(:user)
+ create_list(:issue, 2, assignee: user)
expect(Issue.open_for(user).count).to eq 2
end
@@ -58,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 6eb1208a7f2..fbb9e162952 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -26,8 +26,8 @@ describe Key do
describe "Validation" do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:key) }
- it { is_expected.to ensure_length_of(:title).is_within(0..255) }
- it { is_expected.to ensure_length_of(:key).is_within(0..5000) }
+ it { is_expected.to validate_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_length_of(:key).is_within(0..5000) }
end
describe "Methods" do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 8644ac46605..6518213d71c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -14,30 +14,63 @@ require 'spec_helper'
describe Label do
let(:label) { create(:label) }
- it { expect(label).to be_valid }
- it { is_expected.to belong_to(:project) }
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:label_links).dependent(:destroy) }
+ it { is_expected.to have_many(:issues).through(:label_links).source(:target) }
+ end
+
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Referable) }
+ end
+
+ describe 'validation' do
+ it { is_expected.to validate_presence_of(:project) }
- describe 'Validation' do
it 'should validate color code' do
- expect(build(:label, color: 'G-ITLAB')).not_to be_valid
- expect(build(:label, color: 'AABBCC')).not_to be_valid
- expect(build(:label, color: '#AABBCCEE')).not_to be_valid
- expect(build(:label, color: '#GGHHII')).not_to be_valid
- expect(build(:label, color: '#')).not_to be_valid
- expect(build(:label, color: '')).not_to be_valid
-
- expect(build(:label, color: '#AABBCC')).to be_valid
+ expect(label).not_to allow_value('G-ITLAB').for(:color)
+ expect(label).not_to allow_value('AABBCC').for(:color)
+ expect(label).not_to allow_value('#AABBCCEE').for(:color)
+ expect(label).not_to allow_value('GGHHII').for(:color)
+ expect(label).not_to allow_value('#').for(:color)
+ expect(label).not_to allow_value('').for(:color)
+
+ expect(label).to allow_value('#AABBCC').for(:color)
+ expect(label).to allow_value('#abcdef').for(:color)
end
it 'should validate title' do
- expect(build(:label, title: 'G,ITLAB')).not_to be_valid
- expect(build(:label, title: 'G?ITLAB')).not_to be_valid
- expect(build(:label, title: 'G&ITLAB')).not_to be_valid
- expect(build(:label, title: '')).not_to be_valid
+ expect(label).not_to allow_value('G,ITLAB').for(:title)
+ expect(label).not_to allow_value('G?ITLAB').for(:title)
+ expect(label).not_to allow_value('G&ITLAB').for(:title)
+ expect(label).not_to allow_value('').for(:title)
+
+ expect(label).to allow_value('GITLAB').for(:title)
+ expect(label).to allow_value('gitlab').for(:title)
+ expect(label).to allow_value("customer's request").for(:title)
+ end
+ end
+
+ describe '#to_reference' do
+ context 'using id' do
+ it 'returns a String reference to the object' do
+ expect(label.to_reference).to eq "~#{label.id}"
+ expect(label.to_reference(double('project'))).to eq "~#{label.id}"
+ end
+ end
+
+ context 'using name' do
+ it 'returns a String reference to the object' do
+ expect(label.to_reference(:name)).to eq %(~"#{label.name}")
+ end
- expect(build(:label, title: 'GITLAB')).to be_valid
- expect(build(:label, title: 'gitlab')).to be_valid
+ it 'uses id when name contains double quote' do
+ label = create(:label, name: %q{"irony"})
+ expect(label.to_reference(:name)).to eq "~#{label.id}"
+ end
end
end
end
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 97b8abc49dd..76f6d8c54c4 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -24,22 +24,45 @@
require 'spec_helper'
describe MergeRequest do
- describe "Validation" do
- it { is_expected.to validate_presence_of(:target_branch) }
- it { is_expected.to validate_presence_of(:source_branch) }
+ subject { create(:merge_request) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
+ it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
+
+ it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
end
- describe "Mass assignment" do
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(InternalId) }
+ it { is_expected.to include_module(Issuable) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(Sortable) }
+ it { is_expected.to include_module(Taskable) }
end
- describe "Respond to" do
+ describe 'validation' do
+ it { is_expected.to validate_presence_of(:target_branch) }
+ it { is_expected.to validate_presence_of(:source_branch) }
+ end
+
+ describe 'respond to' do
it { is_expected.to respond_to(:unchecked?) }
it { is_expected.to respond_to(:can_be_merged?) }
it { is_expected.to respond_to(:cannot_be_merged?) }
end
- describe 'modules' do
- it { is_expected.to include_module(Issuable) }
+ describe '#to_reference' do
+ it 'returns a String reference to the object' do
+ expect(subject.to_reference).to eq "!#{subject.iid}"
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}"
+ end
end
describe "#mr_and_commit_notes" do
@@ -57,8 +80,6 @@ describe MergeRequest do
end
end
- subject { create(:merge_request) }
-
describe '#is_being_reassigned?' do
it 'returns true if the merge_request assignee has changed' do
subject.assignee = create(:user)
@@ -90,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
@@ -108,8 +130,9 @@ describe MergeRequest do
it 'detects issues mentioned in the description' do
issue2 = create(:issue, project: subject.project)
- subject.description = "Closes ##{issue2.iid}"
- subject.project.stub(default_branch: subject.target_branch)
+ subject.description = "Closes #{issue2.to_reference}"
+ allow(subject.project).to receive(:default_branch).
+ and_return(subject.target_branch)
expect(subject.closes_issues).to include(issue2)
end
@@ -142,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 45171e1bf64..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
@@ -47,7 +47,7 @@ describe Milestone do
it "should recover from dividing by zero" do
expect(milestone.issues).to receive(:count).and_return(0)
- expect(milestone.percent_complete).to eq(100)
+ expect(milestone.percent_complete).to eq(0)
end
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 ddacba58261..eba33dd510f 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -22,7 +22,7 @@ require 'spec_helper'
describe Note do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:noteable).touch(true) }
it { is_expected.to belong_to(:author).class_name('User') }
end
@@ -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 e5bf9125313..fedc37c9b94 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -21,18 +21,15 @@
require 'spec_helper'
describe GitlabCiService do
- describe "Associations" do
- it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
- end
-
- describe "Mass assignment" do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_one(:service_hook) }
end
describe 'commits methods' do
before do
@service = GitlabCiService.new
- @service.stub(
+ allow(@service).to receive_messages(
service_hook: true,
project_url: 'http://ci.gitlab.org/projects/2',
token: 'verySecret'
@@ -48,6 +45,21 @@ describe GitlabCiService do
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/2ab7834c")}
it { expect(@service.build_page("issue#2", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/issue%232")}
end
+
+ describe "execute" do
+ let(:user) { create(:user, username: 'username') }
+ let(:project) { create(:project, name: 'project') }
+ let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ it "calls ci_yaml_file" do
+ service_hook = double
+ 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
+ end
end
describe "Fork registration" do
@@ -57,7 +69,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 bbaf54488be..4707673269a 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -32,21 +32,44 @@ describe HipchatService do
let(:project) { create(:project, name: 'project') }
let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
let(:project_name) { project.name_with_namespace.gsub(/\s/, '') }
+ let(:token) { 'verySecret' }
+ let(:server_url) { 'https://hipchat.example.com'}
+ 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,
- server: 'https://hipchat.example.com',
- token: 'verySecret'
+ server: server_url,
+ token: token
)
WebMock.stub_request(:post, api_url)
end
- context 'push events' do
- let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ it 'should use v1 if version is provided' do
+ allow(hipchat).to receive(:api_version).and_return('v1')
+ expect(HipChat::Client).to receive(:new).
+ with(token,
+ api_version: 'v1',
+ server_url: server_url).
+ and_return(
+ double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+ it 'should use v2 as the version when nothing is provided' do
+ allow(hipchat).to receive(:api_version).and_return('')
+ expect(HipChat::Client).to receive(:new).
+ with(token,
+ api_version: 'v2',
+ server_url: server_url).
+ and_return(
+ double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ context 'push events' do
it "should call Hipchat API for push events" do
hipchat.execute(push_sample_data)
@@ -218,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 49face26bb4..37690434ec8 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
@@ -43,7 +43,7 @@ describe IrkerService do
let(:_recipients) { 'a b c d' }
it 'should add an error if there is too many recipients' do
subject.send :check_recipients_count
- subject.errors.should_not be_blank
+ expect(subject.errors).not_to be_blank
end
end
@@ -51,7 +51,7 @@ describe IrkerService do
let(:_recipients) { 'a b c' }
it 'should not add an error if there is 3 recipients' do
subject.send :check_recipients_count
- subject.errors.should be_blank
+ expect(subject.errors).to be_blank
end
end
end
@@ -66,7 +66,7 @@ describe IrkerService do
let(:colorize_messages) { '1' }
before do
- irker.stub(
+ allow(irker).to receive_messages(
active: true,
project: project,
project_id: project.id,
@@ -96,11 +96,11 @@ describe IrkerService do
conn = @irker_server.accept
conn.readlines.each do |line|
msg = JSON.load(line.chomp("\n"))
- msg.keys.should match_array(['to', 'privmsg'])
+ expect(msg.keys).to match_array(['to', 'privmsg'])
if msg['to'].is_a?(String)
- msg['to'].should == 'irc://chat.freenode.net/#commits'
+ expect(msg['to']).to eq 'irc://chat.freenode.net/#commits'
else
- msg['to'].should match_array(['irc://chat.freenode.net/#commits'])
+ expect(msg['to']).to match_array(['irc://chat.freenode.net/#commits'])
end
end
conn.close
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 37e21a90818..63091e913ff 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -32,7 +32,7 @@
require 'spec_helper'
describe Project do
- describe 'Associations' do
+ describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:creator).class_name('User') }
@@ -54,22 +54,29 @@ describe Project do
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
end
- describe 'Mass assignment' do
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Gitlab::ConfigHelper) }
+ it { is_expected.to include_module(Gitlab::ShellAdapter) }
+ it { is_expected.to include_module(Gitlab::VisibilityLevel) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(Sortable) }
end
- describe 'Validation' do
+ describe 'validation' do
let!(:project) { create(:project) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
- it { is_expected.to ensure_length_of(:name).is_within(0..255) }
+ it { is_expected.to validate_length_of(:name).is_within(0..255) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
- it { is_expected.to ensure_length_of(:path).is_within(0..255) }
- it { is_expected.to ensure_length_of(:description).is_within(0..2000) }
+ it { is_expected.to validate_length_of(:path).is_within(0..255) }
+ it { is_expected.to validate_length_of(:description).is_within(0..2000) }
it { is_expected.to validate_presence_of(:creator) }
- it { is_expected.to ensure_length_of(:issues_tracker_id).is_within(0..255) }
+ it { is_expected.to validate_length_of(:issues_tracker_id).is_within(0..255) }
it { is_expected.to validate_presence_of(:namespace) }
it 'should not allow new projects beyond user limits' do
@@ -91,6 +98,14 @@ describe Project do
it { is_expected.to respond_to(:path_with_namespace) }
end
+ describe '#to_reference' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns a String reference to the object' do
+ expect(project.to_reference).to eq project.path_with_namespace
+ end
+ end
+
it 'should return valid url to repo' do
project = Project.new(path: 'somewhere')
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
@@ -112,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/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 e37dcc75230..81581838675 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -18,23 +18,46 @@
require 'spec_helper'
describe Snippet do
- describe "Associations" do
- it { is_expected.to belong_to(:author).class_name('User') }
- it { is_expected.to have_many(:notes).dependent(:destroy) }
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Gitlab::VisibilityLevel) }
+ it { is_expected.to include_module(Linguist::BlobHelper) }
+ it { is_expected.to include_module(Participable) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(Sortable) }
end
- describe "Mass assignment" do
+ describe 'associations' do
+ it { is_expected.to belong_to(:author).class_name('User') }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:notes).dependent(:destroy) }
end
- describe "Validation" do
+ describe 'validation' do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to ensure_length_of(:title).is_within(0..255) }
+ 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 ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_length_of(:file_name).is_within(0..255) }
it { is_expected.to validate_presence_of(:content) }
+
+ it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) }
+ end
+
+ describe '#to_reference' do
+ let(:project) { create(:empty_project) }
+ let(:snippet) { create(:snippet, project: project) }
+
+ it 'returns a String reference to the object' do
+ expect(snippet.to_reference).to eq "$#{snippet.id}"
+ end
+
+ it 'supports a cross-project reference' do
+ cross = double('project')
+ expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}"
+ end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0dddcd5bda2..6d2423ae27a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -53,9 +53,10 @@
# 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)
#
require 'spec_helper'
@@ -63,7 +64,17 @@ require 'spec_helper'
describe User do
include Gitlab::CurrentSettings
- describe "Associations" do
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Gitlab::ConfigHelper) }
+ it { is_expected.to include_module(Gitlab::CurrentSettings) }
+ it { is_expected.to include_module(Referable) }
+ it { is_expected.to include_module(Sortable) }
+ it { is_expected.to include_module(TokenAuthenticatable) }
+ end
+
+ describe 'associations' do
it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) }
it { is_expected.to have_many(:project_members).dependent(:destroy) }
@@ -79,9 +90,6 @@ describe User do
it { is_expected.to have_many(:identities).dependent(:destroy) }
end
- describe "Mass assignment" do
- end
-
describe 'validations' do
it { is_expected.to validate_presence_of(:username) }
it { is_expected.to validate_presence_of(:projects_limit) }
@@ -89,7 +97,7 @@ describe User do
it { is_expected.to allow_value(0).for(:projects_limit) }
it { is_expected.not_to allow_value(-1).for(:projects_limit) }
- it { is_expected.to ensure_length_of(:bio).is_within(0..255) }
+ it { is_expected.to validate_length_of(:bio).is_within(0..255) }
describe 'email' do
it 'accepts info@example.com' do
@@ -175,6 +183,14 @@ describe User do
it { is_expected.to respond_to(:private_token) }
end
+ describe '#to_reference' do
+ let(:user) { create(:user) }
+
+ it 'returns a String reference to the object' do
+ expect(user.to_reference).to eq "@#{user.username}"
+ end
+ end
+
describe '#generate_password' do
it "should execute callback when force_random_password specified" do
user = build(:user, force_random_password: true)
@@ -233,6 +249,7 @@ describe User do
it { expect(@user.several_namespaces?).to be_truthy }
it { expect(@user.authorized_groups).to eq([@group]) }
it { expect(@user.owned_groups).to eq([@group]) }
+ it { expect(@user.namespaces).to match_array([@user.namespace, @group]) }
end
describe 'group multiple owners' do
@@ -255,6 +272,7 @@ describe User do
end
it { expect(@user.several_namespaces?).to be_falsey }
+ it { expect(@user.namespaces).to eq([@user.namespace]) }
end
describe 'blocking user' do
@@ -266,18 +284,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 '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 { 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 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
@@ -312,16 +356,35 @@ describe User do
end
describe 'with default overrides' do
- let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: Gitlab::Theme::BASIC) }
+ let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
it "should apply defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
- expect(user.theme_id).to eq(Gitlab::Theme::BASIC)
+ expect(user.theme_id).to eq(1)
end
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') }
@@ -391,21 +454,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
@@ -557,7 +624,6 @@ describe User do
end
describe "#contributed_projects_ids" do
-
subject { create(:user) }
let!(:project1) { create(:project) }
let!(:project2) { create(:project, forked_from_project: project3) }
@@ -583,4 +649,21 @@ describe User do
expect(subject.contributed_projects_ids).not_to include(project2.id)
end
end
+
+ describe :can_be_removed? do
+ subject { create(:user) }
+
+ context 'no owned groups' do
+ it { expect(subject.can_be_removed?).to be_truthy }
+ end
+
+ context 'has owned groups' do
+ before do
+ group = create(:group)
+ group.add_owner(subject)
+ end
+
+ it { expect(subject.can_be_removed?).to be_falsey }
+ end
+ end
end
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/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 9ea60e1a4ad..a1c248c636e 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -9,6 +9,7 @@ describe API::API, api: true do
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
+ let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
before { project.team << [user, :reporter] }
@@ -89,7 +90,7 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq('a comment on a commit')
expect(json_response.first['author']['id']).to eq(user.id)
end
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 bab8888a631..6c7860511e8 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -39,20 +39,16 @@ 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
- Gitlab::Satellite::NewFileAction.any_instance.stub(
- commit!: true,
- )
-
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(201)
expect(json_response['file_path']).to eq('newfile.rb')
@@ -63,10 +59,9 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
- it "should return a 400 if satellite fails to create file" do
- Gitlab::Satellite::NewFileAction.any_instance.stub(
- commit!: false,
- )
+ it "should return a 400 if editor fails to create file" do
+ 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)
@@ -74,20 +69,16 @@ 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
- Gitlab::Satellite::EditFileAction.any_instance.stub(
- commit!: true,
- )
-
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -97,51 +88,18 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/files", user)
expect(response.status).to eq(400)
end
-
- it 'should return a 400 if the checkout fails' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::CheckoutFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(400)
-
- ref = valid_params[:branch_name]
- expect(response.body).to match("ref '#{ref}' could not be checked out")
- end
-
- it 'should return a 409 if the file was not modified' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::CommitFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(409)
- expect(response.body).to match("Maybe there was nothing to commit?")
- end
-
- it 'should return a 409 if the push fails' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::PushFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(409)
- expect(response.body).to match("Maybe the file was changed by another process?")
- end
end
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
- Gitlab::Satellite::DeleteFileAction.any_instance.stub(
- commit!: true,
- )
-
delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -153,9 +111,7 @@ describe API::API, api: true do
end
it "should return a 400 if satellite fails to create file" do
- Gitlab::Satellite::DeleteFileAction.any_instance.stub(
- commit!: 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/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 8770786f49a..5e65ad18c0e 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -196,10 +196,10 @@ describe API::API, api: true do
it 'should return a project issue by iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user)
- response.status.should == 200
- json_response.first['title'].should == issue.title
- json_response.first['id'].should == issue.id
- json_response.first['iid'].should == issue.iid
+ expect(response.status).to eq 200
+ expect(json_response.first['title']).to eq issue.title
+ expect(json_response.first['id']).to eq issue.id
+ expect(json_response.first['iid']).to eq issue.iid
end
it "should return 404 if issue id not found" do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dcd50f73326..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
@@ -118,9 +118,9 @@ describe API::API, api: true do
it 'should return merge_request by iid' do
url = "/projects/#{project.id}/merge_requests?iid=#{merge_request.iid}"
get api(url, user)
- response.status.should == 200
- json_response.first['title'].should == merge_request.title
- json_response.first['id'].should == merge_request.id
+ expect(response.status).to eq 200
+ expect(json_response.first['title']).to eq merge_request.title
+ expect(json_response.first['id']).to eq merge_request.id
end
it "should return a 404 error if merge_request_id not found" 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
@@ -349,10 +355,10 @@ describe API::API, api: true do
expect(json_response['description']).to eq('New description')
end
- it "should return 422 when source_branch and target_branch are renamed the same" do
+ it "should return 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
- expect(response.status).to eq(422)
+ expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 6890dd1f3a7..db0f6e3c0f5 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -32,9 +32,9 @@ describe API::API, api: true do
it 'should return a project milestone by iid' do
get api("/projects/#{project.id}/milestones?iid=#{milestone.iid}", user)
- response.status.should == 200
- json_response.first['title'].should == milestone.title
- json_response.first['id'].should == milestone.id
+ expect(response.status).to eq 200
+ expect(json_response.first['title']).to eq milestone.title
+ expect(json_response.first['id']).to eq milestone.id
end
it 'should return 401 error if user not authenticated' do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 6ddaaa0a6dd..21787fdd895 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
let!(:group1) { create(:group) }
let!(:group2) { create(:group) }
@@ -14,7 +15,7 @@ describe API::API, api: true do
end
end
- context "when authenticated as admin" do
+ context "when authenticated as admin" do
it "admin: should return an array of all namespaces" do
get api("/namespaces", admin)
expect(response.status).to eq(200)
@@ -22,6 +23,32 @@ describe API::API, api: true do
expect(json_response.length).to eq(Namespace.count)
end
+
+ it "admin: should return an array of matched namespaces" do
+ get api("/namespaces?search=#{group1.name}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
+ end
+
+ context "when authenticated as a regular user" do
+ it "user: should return an array of namespaces" do
+ get api("/namespaces", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
+
+ it "admin: should return an array of matched namespaces" do
+ get api("/namespaces?search=#{user.username}", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
end
end
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 aada7febf6c..e9ff832603f 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -57,14 +57,14 @@ describe API::API, api: true do
expect(json_response.first['name']).to eq(project.name)
expect(json_response.first['owner']['username']).to eq(user.username)
end
-
+
it 'should include the project labels as the tag_list' do
get api('/projects', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first.keys.should include('tag_list')
+ expect(response.status).to eq 200
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to include('tag_list')
end
-
+
context 'and using search' do
it 'should return searched project' do
get api('/projects', user), { search: project.name }
@@ -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)
@@ -90,7 +90,7 @@ 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'}
+ 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,14 +155,14 @@ 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
it "should assign attributes to project" do
project = attributes_for(:project, {
path: 'camelCasePath',
- description: Faker::Lorem.sentence,
+ description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false
@@ -257,7 +254,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([
@@ -274,7 +271,7 @@ describe API::API, api: true do
it 'should assign attributes to project' do
project = attributes_for(:project, {
- description: Faker::Lorem.sentence,
+ description: FFaker::Lorem.sentence,
issues_enabled: false,
merge_requests_enabled: false,
wiki_enabled: false
@@ -474,9 +471,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 +545,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 +555,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
@@ -792,11 +789,6 @@ describe API::API, api: true do
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'should remove project' do
- expect(GitlabShellWorker).to(
- receive(:perform_async).with(:remove_repository,
- /#{project.path_with_namespace}/)
- ).twice
-
delete api("/projects/#{project.id}", user)
expect(response.status).to eq(200)
end
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..1a29058f3f1 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
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/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 042352311da..0040718d9be 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -172,7 +172,7 @@ end
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
describe Projects::DeployKeysController, 'routing' do
it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :show, :new, :create] }
+ let(:actions) { [:index, :new, :create] }
let(:controller) { 'deploy_keys' }
end
end
@@ -208,23 +208,31 @@ describe Projects::RefsController, 'routing' do
end
end
-# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
-# automerge_project_merge_request POST /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
-# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
-# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
-# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
-# project_merge_requests GET /:project_id/merge_requests(.:format) projects/merge_requests#index
-# POST /:project_id/merge_requests(.:format) projects/merge_requests#create
-# new_project_merge_request GET /:project_id/merge_requests/new(.:format) projects/merge_requests#new
-# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
-# project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show
-# PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update
-# DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy
+# diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
+# commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits
+# automerge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
+# automerge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
+# ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status
+# toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription
+# branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
+# branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
+# update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches
+# namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index
+# POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create
+# new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new
+# edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
+# namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show
+# PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
+# PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
describe Projects::MergeRequestsController, 'routing' do
it 'to #diffs' do
expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
+ it 'to #commits' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+
it 'to #automerge' do
expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to(
'projects/merge_requests#automerge',
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 953c8dd8ddc..0fda6202a11 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -102,7 +102,6 @@ end
# profile_token GET /profile/token(.:format) profile#token
# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token
# profile GET /profile(.:format) profile#show
-# profile_design GET /profile/design(.:format) profile#design
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
it "to #account" do
@@ -120,9 +119,19 @@ describe ProfilesController, "routing" do
it "to #show" do
expect(get("/profile")).to route_to('profiles#show')
end
+end
- it "to #design" do
- expect(get("/profile/design")).to route_to('profiles#design')
+# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
+# PATCH /profile/preferences(.:format) profiles/preferences#update
+# PUT /profile/preferences(.:format) profiles/preferences#update
+describe Profiles::PreferencesController, 'routing' do
+ it 'to #show' do
+ expect(get('/profile/preferences')).to route_to('profiles/preferences#show')
+ end
+
+ it 'to #update' do
+ expect(put('/profile/preferences')).to route_to('profiles/preferences#update')
+ expect(patch('/profile/preferences')).to route_to('profiles/preferences#update')
end
end
@@ -195,11 +204,9 @@ end
# dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
-# root / dashboard#show
describe DashboardController, "routing" do
it "to #index" do
expect(get("/dashboard")).to route_to('dashboard#show')
- expect(get("/")).to route_to('dashboard#show')
end
it "to #issues" do
@@ -211,6 +218,14 @@ describe DashboardController, "routing" do
end
end
+# root / root#show
+describe RootController, 'routing' do
+ it 'to #show' do
+ expect(get('/')).to route_to('root#show')
+ end
+end
+
+
# new_user_session GET /users/sign_in(.:format) devise/sessions#new
# user_session POST /users/sign_in(.:format) devise/sessions#create
# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
@@ -233,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/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
new file mode 100644
index 00000000000..e28564b3866
--- /dev/null
+++ b/spec/services/destroy_group_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe DestroyGroupService do
+ let!(:user) { create(:user) }
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: group) }
+ let!(:gitlab_shell) { Gitlab::Shell.new }
+ let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+
+ context 'database records' do
+ before do
+ destroy_group(group, user)
+ end
+
+ it { expect(Group.all).not_to include(group) }
+ it { expect(Project.all).not_to include(project) }
+ end
+
+ context 'file system' do
+ context 'Sidekiq inline' do
+ before do
+ # Run sidekiq immediatly to check that renamed dir will be removed
+ Sidekiq::Testing.inline! { destroy_group(group, user) }
+ end
+
+ it { expect(gitlab_shell.exists?(group.path)).to be_falsey }
+ it { expect(gitlab_shell.exists?(remove_path)).to be_falsey }
+ end
+
+ context 'Sidekiq fake' do
+ before do
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_group(group, user) }
+ end
+
+ it { expect(gitlab_shell.exists?(group.path)).to be_falsey }
+ it { expect(gitlab_shell.exists?(remove_path)).to be_truthy }
+ end
+ end
+
+ def destroy_group(group, user)
+ DestroyGroupService.new(group, user).execute
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index e7558f28768..3373b97bfd4 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,9 @@ 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)
+ allow(ApplicationSetting.current_application_settings).
+ to receive(:default_branch_protection).
+ and_return(Gitlab::Access::PROTECTION_NONE)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -133,7 +135,9 @@ 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)
+ allow(ApplicationSetting.current_application_settings).
+ to receive(:default_branch_protection).
+ and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -154,32 +158,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 +195,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 +208,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,21 +226,30 @@ 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
+
+ it "doesn't close issues when external issue tracker is in use" do
+ allow(project).to receive(:default_issues_tracker?).and_return(false)
+
+ # The push still shouldn't create cross-reference notes.
+ expect do
+ service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
+ end.not_to change { Note.where(project_id: project.id, system: true).count }
+ end
end
describe "empty project" do
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/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 0e5ae724bf7..db547ce0d50 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -31,5 +31,15 @@ describe Issues::CloseService do
expect(note.note).to include "Status changed to closed"
end
end
+
+ context "external issue tracker" do
+ before do
+ allow(project).to receive(:default_issues_tracker?).and_return(false)
+ @issue = Issues::CloseService.new(project, user, {}).execute(issue)
+ end
+
+ it { expect(@issue).to be_valid }
+ it { expect(@issue).to be_opened }
+ end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 6fc69e93628..a91be3b4472 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Issues::UpdateService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
- let(:issue) { create(:issue) }
+ let(:issue) { create(:issue, title: 'Old title') }
let(:label) { create(:label) }
let(:project) { issue.project }
@@ -12,7 +12,7 @@ describe Issues::UpdateService do
project.team << [user2, :developer]
end
- describe :execute do
+ describe 'execute' do
context "valid params" do
before do
opts = {
@@ -40,15 +40,32 @@ describe Issues::UpdateService do
expect(email.subject).to include(issue.title)
end
+ def find_note(starting_with)
+ @issue.notes.find do |note|
+ note && note.note.start_with?(starting_with)
+ end
+ end
+
it 'should create system note about issue reassign' do
- note = @issue.notes.last
+ note = find_note('Reassigned to')
+
+ expect(note).not_to be_nil
expect(note.note).to include "Reassigned to \@#{user2.username}"
end
it 'should create system note about issue label edit' do
- note = @issue.notes[1]
+ note = find_note('Added ~')
+
+ expect(note).not_to be_nil
expect(note.note).to include "Added ~#{label.id} label"
end
+
+ it 'creates system note about title change' do
+ note = find_note('Title changed')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Title changed from **Old title** to **New title**'
+ end
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/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 916b01e1c45..c75173c1452 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe MergeRequests::UpdateService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
- let(:merge_request) { create(:merge_request, :simple) }
+ let(:merge_request) { create(:merge_request, :simple, title: 'Old title') }
let(:project) { merge_request.project }
let(:label) { create(:label) }
@@ -12,7 +12,7 @@ describe MergeRequests::UpdateService do
project.team << [user2, :developer]
end
- describe :execute do
+ describe 'execute' do
context 'valid params' do
let(:opts) do
{
@@ -20,7 +20,8 @@ describe MergeRequests::UpdateService do
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close',
- label_ids: [label.id]
+ label_ids: [label.id],
+ target_branch: 'target'
}
end
@@ -39,6 +40,7 @@ describe MergeRequests::UpdateService do
it { expect(@merge_request).to be_closed }
it { expect(@merge_request.labels.count).to eq(1) }
it { expect(@merge_request.labels.first.title).to eq('Bug') }
+ it { expect(@merge_request.target_branch).to eq('target') }
it 'should execute hooks with update action' do
expect(service).to have_received(:execute_hooks).
@@ -51,15 +53,39 @@ describe MergeRequests::UpdateService do
expect(email.subject).to include(merge_request.title)
end
+ def find_note(starting_with)
+ @merge_request.notes.find do |note|
+ note && note.note.start_with?(starting_with)
+ end
+ end
+
it 'should create system note about merge_request reassign' do
- note = @merge_request.notes.last
+ note = find_note('Reassigned to')
+
+ expect(note).not_to be_nil
expect(note.note).to include "Reassigned to \@#{user2.username}"
end
it 'should create system note about merge_request label edit' do
- note = @merge_request.notes[1]
+ note = find_note('Added ~')
+
+ expect(note).not_to be_nil
expect(note.note).to include "Added ~#{label.id} label"
end
+
+ it 'creates system note about title change' do
+ note = find_note('Title changed')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Title changed from **Old title** to **New title**'
+ end
+
+ it 'creates system note about branch change' do
+ note = find_note('Target')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Target branch changed from `master` to `target`'
+ end
end
end
end
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/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
new file mode 100644
index 00000000000..e83eef0b1a2
--- /dev/null
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::DestroyService do
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project, namespace: user.namespace) }
+ let!(:path) { project.repository.path_to_repo }
+ let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
+
+ context 'Sidekiq inline' do
+ before do
+ # Run sidekiq immediatly to check that renamed repository will be removed
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+ end
+
+ it { expect(Project.all).not_to include(project) }
+ it { expect(Dir.exists?(path)).to be_falsey }
+ it { expect(Dir.exists?(remove_path)).to be_falsey }
+ end
+
+ context 'Sidekiq fake' do
+ before do
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
+ end
+
+ it { expect(Project.all).not_to include(project) }
+ it { expect(Dir.exists?(path)).to be_falsey }
+ it { expect(Dir.exists?(remove_path)).to be_truthy }
+ end
+
+ def destroy_project(project, user, params)
+ Projects::DestroyService.new(project, user, params).execute
+ end
+end
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 5650626fb18..79acba78bda 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -20,8 +20,7 @@ describe Projects::TransferService do
@result = transfer_project(project, user, new_namespace_id: nil)
end
- it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil
- it { expect(@result).to be_falsey }
+ it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
end
@@ -30,8 +29,7 @@ describe Projects::TransferService do
@result = transfer_project(project, user, new_namespace_id: group.id)
end
- it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil
- it { expect(@result).to be_falsey }
+ it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index ea5b8813105..0dd6980a44f 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)
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/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 4e4cb6d19ed..2658576640c 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -207,6 +207,41 @@ describe SystemNoteService do
end
end
+ describe '.change_title' do
+ subject { described_class.change_title(noteable, project, author, 'Old title') }
+
+ context 'when noteable responds to `title`' do
+ it_behaves_like 'a system note'
+
+ it 'sets the note text' do
+ expect(subject.note).
+ to eq "Title changed from **Old title** to **#{noteable.title}**"
+ end
+ end
+
+ context 'when noteable does not respond to `title' do
+ let(:noteable) { double('noteable') }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ describe '.change_branch' do
+ subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) }
+ let(:old_branch) { 'old_branch'}
+ let(:new_branch) { 'new_branch'}
+
+ it_behaves_like 'a system note'
+
+ context 'when target branch name changed' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`"
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
@@ -238,13 +273,13 @@ describe SystemNoteService do
let(:mentioner) { project2.repository.commit }
it 'references the mentioning commit' do
- expect(subject.note).to eq "mentioned in commit #{project2.path_with_namespace}@#{mentioner.id}"
+ expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}"
end
end
context 'from non-Commit' do
it 'references the mentioning object' do
- expect(subject.note).to eq "mentioned in issue #{project2.path_with_namespace}##{mentioner.iid}"
+ expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}"
end
end
end
@@ -254,13 +289,13 @@ describe SystemNoteService do
let(:mentioner) { project.repository.commit }
it 'references the mentioning commit' do
- expect(subject.note).to eq "mentioned in commit #{mentioner.id}"
+ expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}"
end
end
context 'from non-Commit' do
it 'references the mentioning object' do
- expect(subject.note).to eq "mentioned in issue ##{mentioner.iid}"
+ expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}"
end
end
end
@@ -270,7 +305,7 @@ describe SystemNoteService do
describe '.cross_reference?' do
it 'is truthy when text begins with expected text' do
- expect(described_class.cross_reference?('mentioned in issue #1')).to be_truthy
+ expect(described_class.cross_reference?('mentioned in something')).to be_truthy
end
it 'is falsey when text does not begin with expected text' do
@@ -303,6 +338,15 @@ describe SystemNoteService do
to be_falsey
end
end
+
+ context 'when notable is an ExternalIssue' do
+ let(:noteable) { ExternalIssue.new('EXT-1234', project) }
+ it 'is truthy' do
+ mentioner = noteable.dup
+ expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
+ to be_truthy
+ end
+ end
end
describe '.cross_reference_exists?' do
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/spec_helper.rb b/spec/spec_helper.rb
index 8fe51cf4add..682a8863bad 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,36 +1,24 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
-
-if ENV['COVERALLS']
- require 'coveralls'
- Coveralls.wear_merged!
-end
-
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
-require 'webmock/rspec'
-require 'email_spec'
+require 'shoulda/matchers'
require 'sidekiq/testing/inline'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
-WebMock.disable_net_connect!(allow_localhost: true)
-
RSpec.configure do |config|
config.use_transactional_fixtures = false
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/api_helpers.rb b/spec/support/api_helpers.rb
index ec9a326a1ea..f63322776d4 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -29,6 +29,6 @@ module ApiHelpers
end
def json_response
- JSON.parse(response.body)
+ @_json_response ||= JSON.parse(response.body)
end
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
new file mode 100644
index 00000000000..a54bf03380c
--- /dev/null
+++ b/spec/support/coverage.rb
@@ -0,0 +1,8 @@
+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/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
new file mode 100644
index 00000000000..755964e9a3d
--- /dev/null
+++ b/spec/support/filter_spec_helper.rb
@@ -0,0 +1,77 @@
+# Helper methods for Gitlab::Markdown filter specs
+#
+# Must be included into specs manually
+module FilterSpecHelper
+ extend ActiveSupport::Concern
+
+ # Perform `call` on the described class
+ #
+ # Automatically passes the current `project` value, if defined, to the context
+ # if none is provided.
+ #
+ # html - HTML String to pass to the filter's `call` method.
+ # contexts - Hash context for the filter. (default: {project: project})
+ #
+ # Returns a Nokogiri::XML::DocumentFragment
+ def filter(html, contexts = {})
+ if defined?(project)
+ contexts.reverse_merge!(project: project)
+ end
+
+ described_class.call(html, contexts)
+ end
+
+ # Run text through HTML::Pipeline with the current filter and return the
+ # result Hash
+ #
+ # body - String text to run through the pipeline
+ # contexts - Hash context for the filter. (default: {project: project})
+ #
+ # Returns the Hash
+ def pipeline_result(body, contexts = {})
+ contexts.reverse_merge!(project: project)
+
+ pipeline = HTML::Pipeline.new([described_class], contexts)
+ pipeline.call(body)
+ end
+
+ # Modify a String reference to make it invalid
+ #
+ # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
+ # their word characters reversed.
+ #
+ # reference - String reference to modify
+ #
+ # Returns a String
+ def invalidate_reference(reference)
+ if reference =~ /\A(.+)?.\d+\z/
+ # Integer-based reference with optional project prefix
+ reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
+ elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+ # SHA-based reference with optional prefix
+ reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+ else
+ reference.gsub(/\w+\z/) { |v| v.reverse }
+ end
+ end
+
+ # Stub CrossProjectReference#user_can_reference_project? to return true for
+ # the current test
+ def allow_cross_reference!
+ allow_any_instance_of(described_class).
+ to receive(:user_can_reference_project?).and_return(true)
+ end
+
+ # Stub CrossProjectReference#user_can_reference_project? to return false for
+ # the current test
+ def disallow_cross_reference!
+ allow_any_instance_of(described_class).
+ to receive(:user_can_reference_project?).and_return(false)
+ end
+
+ # Shortcut to Rails' auto-generated routes helpers, to avoid including the
+ # module
+ def urls
+ Rails.application.routes.url_helpers
+ end
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 791d2a1fd64..ffe30a4246c 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -1,9 +1,25 @@
module LoginHelpers
- # Internal: Create and log in as a user of the specified role
+ # Internal: Log in as a specific user or a new user of a specific role
#
- # role - User role (e.g., :admin, :user)
- def login_as(role)
- @user = create(role)
+ # user_or_role - User object, or a role to create (e.g., :admin, :user)
+ #
+ # Examples:
+ #
+ # # Create a user automatically
+ # login_as(:user)
+ #
+ # # Create an admin automatically
+ # login_as(:admin)
+ #
+ # # Provide an existing User record
+ # user = create(:user)
+ # login_as(user)
+ def login_as(user_or_role)
+ if user_or_role.kind_of?(User)
+ @user = user_or_role
+ else
+ @user = create(user_or_role)
+ end
login_with(@user)
end
@@ -23,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 52b11bd6323..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,44 +46,18 @@ 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
# Extend shoulda-matchers
module Shoulda::Matchers::ActiveModel
- class EnsureLengthOfMatcher
+ class ValidateLengthOfMatcher
# Shortcut for is_at_least and is_at_most
def is_within(range)
is_at_least(range.min) && is_at_most(range.max)
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 53fb6545553..a2a0b6905f9 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) }
let(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
- let(:mentioned_commit) { project.repository.commit }
+ let(:mentioned_commit) { project.commit }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) }
- let(:ext_commit) { ext_proj.repository.commit }
+ let(:ext_commit) { ext_proj.commit }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
@@ -23,21 +23,19 @@ def common_mentionable_setup
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
let(:ref_string) do
- cross = ext_proj.path_with_namespace
-
<<-MSG.strip_heredoc
These references are new:
- Issue: ##{mentioned_issue.iid}
- Merge: !#{mentioned_mr.iid}
- Commit: #{mentioned_commit.id}
+ Issue: #{mentioned_issue.to_reference}
+ Merge: #{mentioned_mr.to_reference}
+ Commit: #{mentioned_commit.to_reference}
This reference is a repeat and should only be mentioned once:
- Repeat: ##{mentioned_issue.iid}
+ Repeat: #{mentioned_issue.to_reference}
These references are cross-referenced:
- Issue: #{cross}##{ext_issue.iid}
- Merge: #{cross}!#{ext_mr.iid}
- Commit: #{cross}@#{ext_commit.short_id}
+ Issue: #{ext_issue.to_reference(project)}
+ Merge: #{ext_mr.to_reference(project)}
+ Commit: #{ext_commit.to_reference(project)}
This is a self-reference and should not be mentioned at all:
Self: #{backref_text}
@@ -82,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
@@ -90,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)
@@ -109,31 +107,38 @@ shared_examples 'an editable mentionable' do
it 'creates new cross-reference notes when the mentionable text is edited' do
subject.save
- cross = ext_proj.path_with_namespace
-
- new_text = <<-MSG
+ new_text = <<-MSG.strip_heredoc
These references already existed:
- Issue: ##{mentioned_issue.iid}
- Commit: #{mentioned_commit.id}
+
+ Issue: #{mentioned_issue.to_reference}
+
+ Commit: #{mentioned_commit.to_reference}
+
+ ---
This cross-project reference already existed:
- Issue: #{cross}##{ext_issue.iid}
+
+ Issue: #{ext_issue.to_reference(project)}
+
+ ---
These two references are introduced in an edit:
- Issue: ##{new_issues[0].iid}
- Cross: #{cross}##{new_issues[1].iid}
+
+ Issue: #{new_issues[0].to_reference}
+
+ Cross: #{new_issues[1].to_reference(project)}
MSG
# 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
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb
deleted file mode 100644
index 06c39e1ada5..00000000000
--- a/spec/support/reference_filter_spec_helper.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# Common methods and setup for Gitlab::Markdown reference filter specs
-#
-# Must be included into specs manually
-module ReferenceFilterSpecHelper
- extend ActiveSupport::Concern
-
- # Shortcut to Rails' auto-generated routes helpers, to avoid including the
- # module
- def urls
- Rails.application.routes.url_helpers
- end
-
- # Perform `call` on the described class
- #
- # Automatically passes the current `project` value to the context if none is
- # provided.
- #
- # html - String text to pass to the filter's `call` method.
- # contexts - Hash context for the filter. (default: {project: project})
- #
- # Returns the String text returned by the filter's `call` method.
- def filter(html, contexts = {})
- contexts.reverse_merge!(project: project)
- described_class.call(html, contexts)
- end
-
- # Run text through HTML::Pipeline with the current filter and return the
- # result Hash
- #
- # body - String text to run through the pipeline
- # contexts - Hash context for the filter. (default: {project: project})
- #
- # Returns the Hash of the pipeline result
- def pipeline_result(body, contexts = {})
- contexts.reverse_merge!(project: project)
-
- pipeline = HTML::Pipeline.new([described_class], contexts)
- pipeline.call(body)
- end
-
- def allow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(true)
- end
-
- def disallow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(false)
- 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..ad86abdbb41
--- /dev/null
+++ b/spec/support/stub_configuration.rb
@@ -0,0 +1,14 @@
+module StubConfiguration
+ def stub_application_setting(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
+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/support/webmock.rb b/spec/support/webmock.rb
new file mode 100644
index 00000000000..af2906b7568
--- /dev/null
+++ b/spec/support/webmock.rb
@@ -0,0 +1,4 @@
+require 'webmock'
+require 'webmock/rspec'
+
+WebMock.disable_net_connect!(allow_localhost: true)
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/teaspoon_env.rb b/spec/teaspoon_env.rb
new file mode 100644
index 00000000000..58f45ff8610
--- /dev/null
+++ b/spec/teaspoon_env.rb
@@ -0,0 +1,178 @@
+Teaspoon.configure do |config|
+ # Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to
+ # `http://localhost:3000/jasmine` to run your tests.
+ config.mount_at = "/teaspoon"
+
+ # Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can
+ # be useful to set this to your engines root (e.g. `Teaspoon::Engine.root`).
+ # Note: Defaults to `Rails.root` if nil.
+ config.root = nil
+
+ # Paths that will be appended to the Rails assets paths
+ # Note: Relative to `config.root`.
+ config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"]
+
+ # Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in these paths will
+ # be rendered as fixtures.
+ config.fixture_paths = ["spec/javascripts/fixtures"]
+
+ # SUITES
+ #
+ # You can modify the default suite configuration and create new suites here. Suites are isolated from one another.
+ #
+ # When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can
+ # omit various directives and the ones defined in the default suite will be used.
+ #
+ # To run a specific suite
+ # - in the browser: http://localhost/teaspoon/[suite_name]
+ # - with the rake task: rake teaspoon suite=[suite_name]
+ # - with the cli: teaspoon --suite=[suite_name]
+ config.suite do |suite|
+ # Specify the framework you would like to use. This allows you to select versions, and will do some basic setup for
+ # you -- which you can override with the directives below. This should be specified first, as it can override other
+ # directives.
+ # Note: If no version is specified, the latest is assumed.
+ #
+ # Versions: 1.3.1, 2.0.3, 2.1.3, 2.2.0
+ suite.use_framework :jasmine, "2.2.0"
+
+ # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These
+ # files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
+ suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
+
+ # Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
+ #suite.javascripts = []
+
+ # You can include your own stylesheets if you want to change how Teaspoon looks.
+ # Note: Spec related CSS can and should be loaded using fixtures.
+ #suite.stylesheets = ["teaspoon"]
+
+ # This suites spec helper, which can require additional support files. This file is loaded before any of your test
+ # files are loaded.
+ suite.helper = "spec_helper"
+
+ # Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating
+ # a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance.
+ #
+ # Available: boot, boot_require_js
+ suite.boot_partial = "boot"
+
+ # Partial to be rendered in the body tag of the runner. You can define your own to create a custom body structure.
+ suite.body_partial = "body"
+
+ # Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a
+ # synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name.
+ #suite.hook :fixtures, &proc{}
+
+ # Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated
+ # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default,
+ # Teaspoon expands all assets to provide more valuable stack traces that reference individual source files.
+ #suite.expand_assets = true
+ end
+
+ # Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also
+ # be run in the default suite -- but can be focused into a more specific suite.
+ #config.suite :targeted do |suite|
+ # suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}"
+ #end
+
+ # CONSOLE RUNNER SPECIFIC
+ #
+ # These configuration directives are applicable only when running via the rake task or command line interface. These
+ # directives can be overridden using the command line interface arguments or with ENV variables when using the rake
+ # task.
+ #
+ # Command Line Interface:
+ # teaspoon --driver=phantomjs --server-port=31337 --fail-fast=true --format=junit --suite=my_suite /spec/file_spec.js
+ #
+ # Rake:
+ # teaspoon DRIVER=phantomjs SERVER_PORT=31337 FAIL_FAST=true FORMATTERS=junit suite=my_suite
+
+ # Specify which headless driver to use. Supports PhantomJS and Selenium Webdriver.
+ #
+ # Available: :phantomjs, :selenium, :capybara_webkit
+ # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
+ # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
+ # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
+ #config.driver = :phantomjs
+
+ # Specify additional options for the driver.
+ #
+ # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
+ # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
+ # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
+ #config.driver_options = nil
+
+ # Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be
+ # considered a failure. This is to avoid issues that can arise where tests stall.
+ #config.driver_timeout = 180
+
+ # Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used.
+ #config.server = nil
+
+ # Specify a port to run on a specific port, otherwise Teaspoon will use a random available port.
+ #config.server_port = nil
+
+ # Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may
+ # want to lower this if you know it shouldn't take long to start.
+ #config.server_timeout = 20
+
+ # Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have
+ # several suites, but in environments like CI this may not be desirable.
+ #config.fail_fast = true
+
+ # Specify the formatters to use when outputting the results.
+ # Note: Output files can be specified by using `"junit>/path/to/output.xml"`.
+ #
+ # Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity
+ #config.formatters = [:dot]
+
+ # Specify if you want color output from the formatters.
+ #config.color = true
+
+ # Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to
+ # remove them, but in verbose applications this may not be desirable.
+ #config.suppress_log = false
+
+ # COVERAGE REPORTS / THRESHOLD ASSERTIONS
+ #
+ # Coverage reports requires Istanbul (https://github.com/gotwarlost/istanbul) to add instrumentation to your code and
+ # display coverage statistics.
+ #
+ # Coverage configurations are similar to suites. You can define several, and use different ones under different
+ # conditions.
+ #
+ # To run with a specific coverage configuration
+ # - with the rake task: rake teaspoon USE_COVERAGE=[coverage_name]
+ # - with the cli: teaspoon --coverage=[coverage_name]
+
+ # Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage
+ # on the CLI.
+ # Set this to "true" or the name of your coverage config.
+ #config.use_coverage = nil
+
+ # You can have multiple coverage configs by passing a name to config.coverage.
+ # e.g. config.coverage :ci do |coverage|
+ # The default coverage config name is :default.
+ config.coverage do |coverage|
+ # Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
+ #
+ # Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
+ #coverage.reports = ["text-summary", "html"]
+
+ # The path that the coverage should be written to - when there's an artifact to write to disk.
+ # Note: Relative to `config.root`.
+ #coverage.output_path = "coverage"
+
+ # Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The
+ # default excludes assets from vendor, gems and support libraries.
+ #coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
+
+ # Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any
+ # aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil.
+ #coverage.statements = nil
+ #coverage.functions = nil
+ #coverage.branches = nil
+ #coverage.lines = nil
+ 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/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
deleted file mode 100755
index 9980aec6ddb..00000000000
--- a/vendor/assets/javascripts/jasmine-fixture.js
+++ /dev/null
@@ -1,433 +0,0 @@
-/* jasmine-fixture - 1.3.1
- * Makes injecting HTML snippets into the DOM easy & clean!
- * https://github.com/searls/jasmine-fixture
- */
-(function() {
- var createHTMLBlock,
- __slice = [].slice;
-
- (function($) {
- var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
- root = (1, eval)('this');
- originalJasmineFixture = root.jasmineFixture;
- originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
- originalAffix = root.affix;
- _ = function(list) {
- return {
- inject: function(iterator, memo) {
- var item, _i, _len, _results;
- _results = [];
- for (_i = 0, _len = list.length; _i < _len; _i++) {
- item = list[_i];
- _results.push(memo = iterator(memo, item));
- }
- return _results;
- }
- };
- };
- root.jasmineFixture = function($) {
- var $whatsTheRootOf, affix, create, jasmineFixture, noConflict;
- affix = function(selectorOptions) {
- return create.call(this, selectorOptions, true);
- };
- create = function(selectorOptions, attach) {
- var $top;
- $top = null;
- _(selectorOptions.split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
- var $el;
- if (elementSelector === ">") {
- return $parent;
- }
- $el = createHTMLBlock($, elementSelector);
- if (attach || $top) {
- $el.appendTo($parent);
- }
- $top || ($top = $el);
- return $el;
- }, $whatsTheRootOf(this));
- return $top;
- };
- noConflict = function() {
- var currentJasmineFixture, _ref1;
- currentJasmineFixture = jasmine.fixture;
- root.jasmineFixture = originalJasmineFixture;
- if ((_ref1 = root.jasmine) != null) {
- _ref1.fixture = originalJasmineDotFixture;
- }
- root.affix = originalAffix;
- return currentJasmineFixture;
- };
- $whatsTheRootOf = function(that) {
- if (that.jquery != null) {
- return that;
- } else if ($('#jasmine_content').length > 0) {
- return $('#jasmine_content');
- } else {
- return $('<div id="jasmine_content"></div>').appendTo('body');
- }
- };
- jasmineFixture = {
- affix: affix,
- create: create,
- noConflict: noConflict
- };
- ewwSideEffects(jasmineFixture);
- return jasmineFixture;
- };
- ewwSideEffects = function(jasmineFixture) {
- var _ref1;
- if ((_ref1 = root.jasmine) != null) {
- _ref1.fixture = jasmineFixture;
- }
- $.fn.affix = root.affix = jasmineFixture.affix;
- return afterEach(function() {
- return $('#jasmine_content').remove();
- });
- };
- if ($) {
- return jasmineFixture = root.jasmineFixture($);
- } else {
- return root.affix = function() {
- var nowJQueryExists;
- nowJQueryExists = window.jQuery || window.$;
- if (nowJQueryExists != null) {
- jasmineFixture = root.jasmineFixture(nowJQueryExists);
- return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
- } else {
- throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
- }
- };
- }
- })(window.jQuery || window.$);
-
- createHTMLBlock = (function() {
- var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
- createHTMLBlock = function($, ZenObject, data, functions, indexes) {
- var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
- if ($.isPlainObject(ZenObject)) {
- ZenCode = ZenObject.main;
- } else {
- ZenCode = ZenObject;
- ZenObject = {
- main: ZenCode
- };
- }
- origZenCode = ZenCode;
- if (indexes === undefined) {
- indexes = {};
- }
- if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
- if ($.isArray(data)) {
- forScope = ZenCode;
- } else {
- obj = parseEnclosure(ZenCode, "!");
- obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
- forScope = parseVariableScope(ZenCode);
- }
- while (forScope.charAt(0) === "@") {
- forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
- }
- zo = ZenObject;
- zo.main = forScope;
- el = $();
- if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
- if (!$.isArray(data) && obj.indexOf(":") > 0) {
- indexName = obj.substring(0, obj.indexOf(":"));
- obj = obj.substr(obj.indexOf(":") + 1);
- }
- arr = ($.isArray(data) ? data : data[obj]);
- zc = zo.main;
- if ($.isArray(arr) || $.isPlainObject(arr)) {
- $.map(arr, function(value, index) {
- var next;
- zo.main = zc;
- if (indexName !== undefined) {
- indexes[indexName] = index;
- }
- if (!$.isPlainObject(value)) {
- value = {
- value: value
- };
- }
- next = createHTMLBlock($, zo, value, functions, indexes);
- if (el.length !== 0) {
- return $.each(next, function(index, value) {
- return el.push(value);
- });
- }
- });
- }
- if (!$.isArray(data)) {
- ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
- } else {
- ZenCode = "";
- }
- } else if (ZenCode.substring(0, 4) === "!if:") {
- result = parseContents("!" + obj + "!", data, indexes);
- if (result !== "undefined" || result !== "false" || result !== "") {
- el = createHTMLBlock($, zo, data, functions, indexes);
- }
- ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
- }
- ZenObject.main = ZenCode;
- } else if (ZenCode.charAt(0) === "(") {
- paren = parseEnclosure(ZenCode, "(", ")");
- inner = paren.substring(1, paren.length - 1);
- ZenCode = ZenCode.substr(paren.length);
- zo = ZenObject;
- zo.main = inner;
- el = createHTMLBlock($, zo, data, functions, indexes);
- } else {
- blocks = ZenCode.match(regZenTagDfn);
- block = blocks[0];
- if (block.length === 0) {
- return "";
- }
- if (block.indexOf("@") >= 0) {
- ZenCode = parseReferences(ZenCode, ZenObject);
- zo = ZenObject;
- zo.main = ZenCode;
- return createHTMLBlock($, zo, data, functions, indexes);
- }
- block = parseContents(block, data, indexes);
- blockClasses = parseClasses($, block);
- if (regId.test(block)) {
- blockId = regId.exec(block)[1];
- }
- blockAttrs = parseAttributes(block, data);
- blockTag = (block.charAt(0) === "{" ? "span" : "div");
- if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
- blockTag = regTag.exec(block)[1];
- }
- if (block.search(regCBrace) !== -1) {
- blockHTML = block.match(regCBrace)[1];
- }
- blockAttrs = $.extend(blockAttrs, {
- id: blockId,
- "class": blockClasses,
- html: blockHTML
- });
- el = $("<" + blockTag + ">", blockAttrs);
- el.attr(blockAttrs);
- el = bindEvents(block, el, functions);
- el = bindData(block, el, data);
- ZenCode = ZenCode.substr(blocks[0].length);
- ZenObject.main = ZenCode;
- }
- if (ZenCode.length > 0) {
- if (ZenCode.charAt(0) === ">") {
- if (ZenCode.charAt(1) === "(") {
- zc = parseEnclosure(ZenCode.substr(1), "(", ")");
- ZenCode = ZenCode.substr(zc.length + 1);
- } else if (ZenCode.charAt(1) === "!") {
- obj = parseEnclosure(ZenCode.substr(1), "!");
- forScope = parseVariableScope(ZenCode.substr(1));
- zc = obj + forScope;
- ZenCode = ZenCode.substr(zc.length + 1);
- } else {
- len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
- zc = ZenCode.substring(1, len);
- ZenCode = ZenCode.substr(len);
- }
- zo = ZenObject;
- zo.main = zc;
- els = $(createHTMLBlock($, zo, data, functions, indexes));
- els.appendTo(el);
- }
- if (ZenCode.charAt(0) === "+") {
- zo = ZenObject;
- zo.main = ZenCode.substr(1);
- el2 = createHTMLBlock($, zo, data, functions, indexes);
- $.each(el2, function(index, value) {
- return el.push(value);
- });
- }
- }
- ret = el;
- return ret;
- };
- bindData = function(ZenCode, el, data) {
- var datas, i, split;
- if (ZenCode.search(regDatas) === 0) {
- return el;
- }
- datas = ZenCode.match(regDatas);
- if (datas === null) {
- return el;
- }
- i = 0;
- while (i < datas.length) {
- split = regData.exec(datas[i]);
- if (split[3] === undefined) {
- $(el).data(split[1], data[split[1]]);
- } else {
- $(el).data(split[1], data[split[3]]);
- }
- i++;
- }
- return el;
- };
- bindEvents = function(ZenCode, el, functions) {
- var bindings, fn, i, split;
- if (ZenCode.search(regEvents) === 0) {
- return el;
- }
- bindings = ZenCode.match(regEvents);
- if (bindings === null) {
- return el;
- }
- i = 0;
- while (i < bindings.length) {
- split = regEvent.exec(bindings[i]);
- if (split[2] === undefined) {
- fn = functions[split[1]];
- } else {
- fn = functions[split[2]];
- }
- $(el).bind(split[1], fn);
- i++;
- }
- return el;
- };
- parseAttributes = function(ZenBlock, data) {
- var attrStrs, attrs, i, parts;
- if (ZenBlock.search(regAttrDfn) === -1) {
- return undefined;
- }
- attrStrs = ZenBlock.match(regAttrDfn);
- attrs = {};
- i = 0;
- while (i < attrStrs.length) {
- parts = regAttr.exec(attrStrs[i]);
- attrs[parts[1]] = "";
- if (parts[3] !== undefined) {
- attrs[parts[1]] = parseContents(parts[3], data);
- }
- i++;
- }
- return attrs;
- };
- parseClasses = function($, ZenBlock) {
- var classes, clsString, i;
- ZenBlock = ZenBlock.match(regTagNotContent)[0];
- if (ZenBlock.search(regClasses) === -1) {
- return undefined;
- }
- classes = ZenBlock.match(regClasses);
- clsString = "";
- i = 0;
- while (i < classes.length) {
- clsString += " " + regClass.exec(classes[i])[1];
- i++;
- }
- return $.trim(clsString);
- };
- parseContents = function(ZenBlock, data, indexes) {
- var html;
- if (indexes === undefined) {
- indexes = {};
- }
- html = ZenBlock;
- if (data === undefined) {
- return html;
- }
- while (regExclamation.test(html)) {
- html = html.replace(regExclamation, function(str, str2) {
- var begChar, fn, val;
- begChar = "";
- if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
- return str;
- }
- if (str.charAt(0) !== "!") {
- begChar = str.charAt(0);
- str = str.substring(2, str.length - 1);
- }
- fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
- val = unescape(fn(data, indexes));
- return begChar + val;
- });
- }
- html = html.replace(/\\./g, function(str) {
- return str.charAt(1);
- });
- return unescape(html);
- };
- parseEnclosure = function(ZenCode, open, close, count) {
- var index, ret;
- if (close === undefined) {
- close = open;
- }
- index = 1;
- if (count === undefined) {
- count = (ZenCode.charAt(0) === open ? 1 : 0);
- }
- if (count === 0) {
- return;
- }
- while (count > 0 && index < ZenCode.length) {
- if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
- count--;
- } else {
- if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
- count++;
- }
- }
- index++;
- }
- ret = ZenCode.substring(0, index);
- return ret;
- };
- parseReferences = function(ZenCode, ZenObject) {
- ZenCode = ZenCode.replace(regReference, function(str) {
- var fn;
- str = str.substr(1);
- fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
- return fn(ZenObject, parseReferences);
- });
- return ZenCode;
- };
- parseVariableScope = function(ZenCode) {
- var forCode, rest, tag;
- if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
- return undefined;
- }
- forCode = parseEnclosure(ZenCode, "!");
- ZenCode = ZenCode.substr(forCode.length);
- if (ZenCode.charAt(0) === "(") {
- return parseEnclosure(ZenCode, "(", ")");
- }
- tag = ZenCode.match(regZenTagDfn)[0];
- ZenCode = ZenCode.substr(tag.length);
- if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
- return tag;
- } else if (ZenCode.charAt(0) === ">") {
- rest = "";
- rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
- return tag + ">" + rest;
- }
- return undefined;
- };
- regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
- regTag = /(\w+)/i;
- regId = /(?:^|\b)#([\w-!]+)/i;
- regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
- /*
- See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
- */
-
- regClasses = /(\.[\w-]+)(?!["\w])/g;
- regClass = /\.([\w-]+)/i;
- regReference = /(@[\w$_][\w$_\d]+)/i;
- regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
- regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
- regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i;
- regCBrace = /\{(([^\}]|\\\})+)\}/i;
- regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
- regEvents = /\~[\w$]+(=[\w$]+)?/g;
- regEvent = /\~([\w$]+)=([\w$]+)/i;
- regDatas = /&[\w$]+(=[\w$]+)?/g;
- regData = /&([\w$]+)(=([\w$]+))?/i;
- return createHTMLBlock;
- })();
-
-}).call(this);