summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml49
-rw-r--r--CHANGELOG117
-rw-r--r--CONTRIBUTING.md16
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile84
-rw-r--r--Gemfile.lock138
-rw-r--r--Guardfile27
-rw-r--r--README.md6
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/application.js.coffee34
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee8
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee22
-rw-r--r--app/assets/javascripts/issue.js.coffee1
-rw-r--r--app/assets/javascripts/merge_request.js.coffee101
-rw-r--r--app/assets/javascripts/notes.js.coffee10
-rw-r--r--app/assets/javascripts/profile.js.coffee4
-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_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/base/mixins.scss16
-rw-r--r--app/assets/stylesheets/base/variables.scss3
-rw-r--r--app/assets/stylesheets/generic/common.scss7
-rw-r--r--app/assets/stylesheets/generic/forms.scss1
-rw-r--r--app/assets/stylesheets/generic/header.scss253
-rw-r--r--app/assets/stylesheets/generic/lists.scss1
-rw-r--r--app/assets/stylesheets/generic/mobile.scss2
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss34
-rw-r--r--app/assets/stylesheets/generic/typography.scss7
-rw-r--r--app/assets/stylesheets/generic/zen.scss10
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/notes.scss46
-rw-r--r--app/assets/stylesheets/pages/profile.scss19
-rw-r--r--app/assets/stylesheets/pages/projects.scss38
-rw-r--r--app/assets/stylesheets/pages/tree.scss8
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss11
-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/hooks_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb30
-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.rb14
-rw-r--r--app/controllers/passwords_controller.rb41
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb50
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb25
-rw-r--r--app/controllers/projects/project_members_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb17
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb65
-rw-r--r--app/controllers/uploads_controller.rb10
-rw-r--r--app/finders/README.md2
-rw-r--r--app/finders/issuable_finder.rb108
-rw-r--r--app/helpers/application_helper.rb47
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb14
-rw-r--r--app/helpers/compare_helper.rb25
-rw-r--r--app/helpers/diff_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb19
-rw-r--r--app/helpers/events_helper.rb6
-rw-r--r--app/helpers/gitlab_markdown_helper.rb231
-rw-r--r--app/helpers/icons_helper.rb6
-rw-r--r--app/helpers/labels_helper.rb38
-rw-r--r--app/helpers/projects_helper.rb28
-rw-r--r--app/helpers/selects_helper.rb5
-rw-r--r--app/helpers/tree_helper.rb8
-rw-r--r--app/helpers/version_check_helper.rb7
-rw-r--r--app/mailers/emails/projects.rb3
-rw-r--r--app/models/ability.rb32
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/commit.rb39
-rw-r--r--app/models/commit_range.rb30
-rw-r--r--app/models/concerns/mentionable.rb19
-rw-r--r--app/models/concerns/participable.rb22
-rw-r--r--app/models/concerns/referable.rb61
-rw-r--r--app/models/external_issue.rb11
-rw-r--r--app/models/group.rb20
-rw-r--r--app/models/group_milestone.rb2
-rw-r--r--app/models/hooks/project_hook.rb2
-rw-r--r--app/models/hooks/service_hook.rb5
-rw-r--r--app/models/hooks/system_hook.rb1
-rw-r--r--app/models/hooks/web_hook.rb18
-rw-r--r--app/models/issue.rb29
-rw-r--r--app/models/label.rb41
-rw-r--r--app/models/merge_request.rb37
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/namespace.rb15
-rw-r--r--app/models/note.rb247
-rw-r--r--app/models/project.rb18
-rw-r--r--app/models/project_services/gitlab_ci_service.rb14
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb47
-rw-r--r--app/models/snippet.rb32
-rw-r--r--app/models/tree.rb6
-rw-r--r--app/models/user.rb57
-rw-r--r--app/services/delete_user_service.rb16
-rw-r--r--app/services/destroy_group_service.rb17
-rw-r--r--app/services/files/base_service.rb7
-rw-r--r--app/services/files/create_service.rb22
-rw-r--r--app/services/files/delete_service.rb15
-rw-r--r--app/services/files/update_service.rb20
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/issuable_base_service.rb17
-rw-r--r--app/services/issues/close_service.rb2
-rw-r--r--app/services/issues/reopen_service.rb2
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/merge_requests/base_service.rb2
-rw-r--r--app/services/merge_requests/refresh_service.rb20
-rw-r--r--app/services/merge_requests/update_service.rb18
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notification_service.rb23
-rw-r--r--app/services/projects/destroy_service.rb67
-rw-r--r--app/services/system_hooks_service.rb6
-rw-r--r--app/services/system_note_service.rb317
-rw-r--r--app/services/test_hook_service.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml20
-rw-r--r--app/views/admin/dashboard/index.html.haml7
-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/users/index.html.haml9
-rw-r--r--app/views/admin/users/show.html.haml24
-rw-r--r--app/views/dashboard/groups/index.html.haml7
-rw-r--r--app/views/dashboard/issues.atom.builder2
-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/dashboard/show.atom.builder2
-rw-r--r--app/views/devise/passwords/new.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml10
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml4
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event_issue.atom.haml2
-rw-r--r--app/views/events/_event_merge_request.atom.haml2
-rw-r--r--app/views/events/_event_note.atom.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/events/event/_note.html.haml2
-rw-r--r--app/views/events/event/_push.html.haml18
-rw-r--r--app/views/groups/group_members/_group_member.html.haml3
-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/show.atom.builder2
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/layouts/_head_panel.html.haml48
-rw-r--r--app/views/layouts/_page.html.haml9
-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.haml4
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml43
-rw-r--r--app/views/layouts/header/_empty.html.haml (renamed from app/views/layouts/_empty_head_panel.html.haml)2
-rw-r--r--app/views/layouts/header/_public.html.haml14
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml4
-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/new_user_email.html.haml4
-rw-r--r--app/views/notify/new_user_email.text.erb2
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml130
-rw-r--r--app/views/profiles/applications.html.haml62
-rw-r--r--app/views/profiles/design.html.haml80
-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/show.html.haml11
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml11
-rw-r--r--app/views/profiles/two_factor_auths/codes.html.haml5
-rw-r--r--app/views/profiles/two_factor_auths/create.html.haml6
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml39
-rw-r--r--app/views/projects/_aside.html.haml159
-rw-r--r--app/views/projects/_dropdown.html.haml37
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_issuable_form.html.haml29
-rw-r--r--app/views/projects/_section.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/_text.html.haml6
-rw-r--r--app/views/projects/blob/diff.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml11
-rw-r--r--app/views/projects/commits/_head.html.haml11
-rw-r--r--app/views/projects/commits/show.atom.builder2
-rw-r--r--app/views/projects/commits/show.html.haml16
-rw-r--r--app/views/projects/compare/_form.html.haml7
-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/_match_line.html.haml8
-rw-r--r--app/views/projects/diffs/_text_file.html.haml4
-rw-r--r--app/views/projects/hooks/index.html.haml9
-rw-r--r--app/views/projects/issues/_discussion.html.haml3
-rw-r--r--app/views/projects/issues/_issue.html.haml3
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/issues/index.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.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml9
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml43
-rw-r--r--app/views/projects/merge_requests/_show.html.haml50
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml35
-rw-r--r--app/views/projects/merge_requests/show/_mr_ci.html.haml12
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml8
-rw-r--r--app/views/projects/milestones/_milestone.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml11
-rw-r--r--app/views/projects/notes/_note.html.haml33
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml2
-rw-r--r--app/views/projects/project_members/_project_member.html.haml5
-rw-r--r--app/views/projects/show.atom.builder2
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_snippet_blob.html.haml11
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_issuable_filter.html.haml27
-rw-r--r--app/views/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/snippets/_blob.html.haml6
-rw-r--r--app/views/shared/snippets/_form.html.haml4
-rw-r--r--app/workers/project_web_hook_worker.rb4
-rw-r--r--app/workers/system_hook_worker.rb4
-rwxr-xr-xbin/guard16
-rwxr-xr-xbin/rake5
-rwxr-xr-xbin/spring11
-rw-r--r--config.ru11
-rw-r--r--config/application.rb2
-rw-r--r--config/gitlab.yml.example17
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/6_rack_profiler.rb3
-rw-r--r--config/initializers/7_omniauth.rb7
-rw-r--r--config/initializers/attr_encrypted_no_db_connection.rb20
-rw-r--r--config/initializers/devise.rb5
-rw-r--r--config/initializers/gitlab_shell_secret_token.rb8
-rw-r--r--config/locales/doorkeeper.en.yml10
-rw-r--r--config/routes.rb11
-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/migrate/20150310194358_add_version_check_to_application_settings.rb5
-rw-r--r--db/migrate/20150327223628_add_devise_two_factor_to_users.rb8
-rw-r--r--db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb5
-rw-r--r--db/migrate/20150406133311_add_invite_data_to_member.rb13
-rw-r--r--db/migrate/20150417122318_remove_import_data_from_project.rb6
-rw-r--r--db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb6
-rw-r--r--db/migrate/20150425164647_remove_duplicate_tags.rb3
-rw-r--r--db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb6
-rw-r--r--db/migrate/20150429002313_remove_abandoned_group_members_records.rb5
-rw-r--r--db/migrate/20150509180749_convert_legacy_reference_notes.rb16
-rw-r--r--db/migrate/20150516060434_add_note_events_to_web_hooks.rb9
-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/schema.rb11
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/namespaces.md44
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/development/README.md4
-rw-r--r--doc/development/db_dump.md45
-rw-r--r--doc/development/migration_style_guide.md88
-rw-r--r--doc/install/installation.md27
-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/markdown/markdown.md20
-rw-r--r--doc/raketasks/backup_restore.md78
-rw-r--r--doc/raketasks/maintenance.md3
-rw-r--r--doc/release/monthly.md10
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/reset_root_password.md40
-rw-r--r--doc/system_hooks/system_hooks.md6
-rw-r--r--doc/update/6.x-or-7.x-to-7.11.md (renamed from doc/update/6.x-or-7.x-to-7.10.md)25
-rw-r--r--doc/update/7.10-to-7.11.md103
-rw-r--r--doc/update/mysql_to_postgresql.md55
-rw-r--r--doc/update/upgrader.md21
-rw-r--r--doc/web_hooks/web_hooks.md306
-rw-r--r--doc/workflow/2fa.pngbin0 -> 23415 bytes
-rw-r--r--doc/workflow/2fa_auth.pngbin0 -> 15569 bytes
-rw-r--r--doc/workflow/README.md3
-rw-r--r--doc/workflow/shortcuts.md5
-rw-r--r--doc/workflow/shortcuts.pngbin0 -> 78736 bytes
-rw-r--r--doc/workflow/timezone.md30
-rw-r--r--doc/workflow/two_factor_authentication.md65
-rw-r--r--doc_styleguide.md22
-rw-r--r--docker/README.md2
-rw-r--r--docker/app/Dockerfile11
-rw-r--r--docker/single/Dockerfile17
-rw-r--r--docker/single/assets/gitlab.rb37
-rw-r--r--features/admin/deploy_keys.feature5
-rw-r--r--features/dashboard/group.feature3
-rw-r--r--features/project/deploy_keys.feature7
-rw-r--r--features/project/forked_merge_requests.feature12
-rw-r--r--features/project/merge_requests.feature12
-rw-r--r--features/project/project.feature11
-rw-r--r--features/project/wiki.feature5
-rw-r--r--features/steps/admin/deploy_keys.rb11
-rw-r--r--features/steps/dashboard/dashboard.rb4
-rw-r--r--features/steps/dashboard/group.rb4
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/profile/notifications.rb2
-rw-r--r--features/steps/profile/profile.rb26
-rw-r--r--features/steps/project/commits/commits.rb2
-rw-r--r--features/steps/project/deploy_keys.rb14
-rw-r--r--features/steps/project/forked_merge_requests.rb15
-rw-r--r--features/steps/project/hooks.rb2
-rw-r--r--features/steps/project/merge_requests.rb27
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/project/wiki.rb10
-rw-r--r--features/steps/shared/project.rb11
-rw-r--r--features/support/env.rb1
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/internal.rb26
-rw-r--r--lib/api/merge_requests.rb8
-rw-r--r--lib/api/namespaces.rb11
-rw-r--r--lib/api/project_hooks.rb6
-rw-r--r--lib/api/projects.rb7
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/access.rb6
-rw-r--r--lib/gitlab/asciidoc.rb60
-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/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/git_access.rb62
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/ldap/access.rb2
-rw-r--r--lib/gitlab/markdown.rb34
-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/relative_link_filter.rb128
-rw-r--r--lib/gitlab/markdown/sanitization_filter.rb47
-rw-r--r--lib/gitlab/markdown/snippet_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/task_list_filter.rb23
-rw-r--r--lib/gitlab/markdown/user_reference_filter.rb16
-rw-r--r--lib/gitlab/markup_helper.rb (renamed from lib/gitlab/markdown_helper.rb)19
-rw-r--r--lib/gitlab/o_auth/user.rb4
-rw-r--r--lib/gitlab/reference_extractor.rb54
-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/search_results.rb16
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb2
-rw-r--r--lib/gitlab/upgrader.rb11
-rw-r--r--lib/omni_auth/request_forgery_protection.rb66
-rw-r--r--lib/redcarpet/render/gitlab_html.rb11
-rwxr-xr-xlib/support/init.d/gitlab3
-rwxr-xr-xlib/support/init.d/gitlab.default.example5
-rw-r--r--lib/tasks/brakeman.rake4
-rw-r--r--lib/tasks/gitlab/check.rake86
-rw-r--r--lib/tasks/gitlab/import.rake2
-rw-r--r--lib/tasks/gitlab/shell.rake3
-rw-r--r--lib/tasks/gitlab/task_helpers.rake4
-rw-r--r--lib/tasks/jasmine.rake2
-rw-r--r--lib/version_check.rb18
-rw-r--r--public/404.html7
-rw-r--r--public/422.html10
-rw-r--r--public/500.html5
-rw-r--r--public/502.html3
-rw-r--r--public/deploy.html12
-rw-r--r--public/static.css10
-rw-r--r--safe/public.pem9
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb17
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb17
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb129
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb18
-rw-r--r--spec/factories.rb23
-rw-r--r--spec/factories/notes.rb19
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb34
-rw-r--r--spec/features/groups_spec.rb36
-rw-r--r--spec/features/login_spec.rb101
-rw-r--r--spec/features/markdown_spec.rb63
-rw-r--r--spec/features/password_reset_spec.rb53
-rw-r--r--spec/features/projects_spec.rb55
-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.erb97
-rw-r--r--spec/helpers/application_helper_spec.rb16
-rw-r--r--spec/helpers/blob_helper_spec.rb33
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/helpers/emails_helper_spec.rb46
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb95
-rw-r--r--spec/helpers/labels_helper_spec.rb68
-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/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/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/merge_request_spec.js.coffee31
-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/gitlab/asciidoc_spec.rb59
-rw-r--r--spec/lib/gitlab/backend/grack_auth_spec.rb2
-rw-r--r--spec/lib/gitlab/backend/rack_attack_helpers_spec.rb35
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb70
-rw-r--r--spec/lib/gitlab/git_access_spec.rb14
-rw-r--r--spec/lib/gitlab/gitlab_markdown_helper_spec.rb28
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb27
-rw-r--r--spec/lib/gitlab/markdown/autolink_filter_spec.rb6
-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.rb76
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb15
-rw-r--r--spec/lib/gitlab/markdown/relative_link_filter_spec.rb115
-rw-r--r--spec/lib/gitlab/markdown/sanitization_filter_spec.rb53
-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.rb12
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb47
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb40
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb28
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb15
-rw-r--r--spec/lib/repository_cache_spec.rb1
-rw-r--r--spec/mailers/notify_spec.rb62
-rw-r--r--spec/models/commit_range_spec.rb23
-rw-r--r--spec/models/commit_spec.rb24
-rw-r--r--spec/models/external_issue_spec.rb24
-rw-r--r--spec/models/group_spec.rb26
-rw-r--r--spec/models/hooks/project_hook_spec.rb1
-rw-r--r--spec/models/hooks/service_hook_spec.rb34
-rw-r--r--spec/models/hooks/system_hook_spec.rb43
-rw-r--r--spec/models/hooks/web_hook_spec.rb15
-rw-r--r--spec/models/issue_spec.rb28
-rw-r--r--spec/models/label_spec.rb67
-rw-r--r--spec/models/merge_request_spec.rb41
-rw-r--r--spec/models/milestone_spec.rb2
-rw-r--r--spec/models/note_spec.rb535
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb15
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb31
-rw-r--r--spec/models/project_spec.rb21
-rw-r--r--spec/models/snippet_spec.rb36
-rw-r--r--spec/models/user_spec.rb48
-rw-r--r--spec/requests/api/files_spec.rb51
-rw-r--r--spec/requests/api/internal_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/api/namespaces_spec.rb29
-rw-r--r--spec/requests/api/projects_spec.rb22
-rw-r--r--spec/routing/project_routing_spec.rb34
-rw-r--r--spec/services/destroy_group_service_spec.rb44
-rw-r--r--spec/services/issues/close_service_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb27
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb18
-rw-r--r--spec/services/merge_requests/update_service_spec.rb36
-rw-r--r--spec/services/notes/create_service_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb15
-rw-r--r--spec/services/projects/destroy_service_spec.rb34
-rw-r--r--spec/services/system_note_service_spec.rb381
-rw-r--r--spec/support/filter_spec_helper.rb77
-rw-r--r--spec/support/mentionable_shared_examples.rb43
-rw-r--r--spec/support/reference_filter_spec_helper.rb50
-rw-r--r--spec/teaspoon_env.rb178
-rwxr-xr-xvendor/assets/javascripts/jasmine-fixture.js433
501 files changed, 8036 insertions, 4287 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000000..021acdeca39
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,49 @@
+before_script:
+ - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
+ - ruby -v
+ - which ruby
+ - gem install bundler
+ - which bundle
+ - echo $PATH
+ - cp config/database.yml.mysql config/database.yml
+ - cp config/gitlab.yml.example config/gitlab.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
+ - touch log/application.log
+ - touch log/test.log
+ - bundle install --without postgres production --jobs $(nproc)
+ - bundle exec rake db:create RAILS_ENV=test
+jobs:
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
+ name: Rspec
+ branches: true
+ tags: false
+ runner: ruby,mysql
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
+ name: Spinach
+ branches: true
+ tags: false
+ runner: ruby,mysql
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci
+ name: Jasmine
+ branches: true
+ tags: false
+ runner: ruby,mysql
+- script:
+ - bundle exec rubocop
+ name: Rubocop
+ branches: true
+ tags: false
+ runner: ruby,mysql
+- script:
+ - bundle exec rake brakeman
+ name: Brakeman
+ branches: true
+ tags: false
+ runner: ruby,mysql
+deploy_jobs: []
+skip_refs: ''
diff --git a/CHANGELOG b/CHANGELOG
index ef0f164264e..3df8e7ea516 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,83 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 7.11.0 (unreleased)
+v 7.12.0 (unreleased)
+ - 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)
+ - 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 Accepted or Rejected.
+ - Add Accepted and Rejected tabs 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.yaml in each push call
+ - When remove project - move repository and schedule it removal
+ - Improve group removing logic
+ - Trigger create-hooks on backup restore task
+
+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
+
+v 7.11.1
+ - no changes
+
+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)
- Make the first branch pushed to an empty repository the default HEAD (Stan Hu)
+ - Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu)
- Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
- Add application setting to restrict user signups to e-mail domains (Stan Hu)
- Don't allow a merge request to be merged when its title starts with "WIP".
- Add a page title to every page.
- Allow primary email to be set to an email that you've already added.
- - Fix Error 500 when searching Wiki pages (Stan Hu)
- - Get Gitorious importer to work again.
- Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
- Ignore invalid lines in .gitmodules
- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
@@ -17,26 +86,29 @@ v 7.11.0 (unreleased)
- Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu)
- Improve project page UI
- Fix broken file browsing with relative submodule in personal projects (Stan Hu)
- - Fix DB error when trying to tag a repository (Stan Hu)
- Add "Reply quoting selected text" shortcut key (`r`)
- Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention.
- Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention.
- When use change branches link at MR form - save source branch selection instead of target one
- Improve handling of large diffs
- -
+ - Added GitLab Event header for project hooks
+ - Add Two-factor authentication (2FA) for GitLab logins
- Show Atom feed buttons everywhere where applicable.
- Add project activity atom feed.
- Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits.
+ - Explain how to get a new password reset token in welcome emails
- Include commit comments in MR from a forked project.
- - Fix adding new group members from admin area
- Group milestones by title in the dashboard and all other issue views.
- Query issues, merge requests and milestones with their IID through API (Julien Bianchi)
- Add default project and snippet visibility settings to the admin web UI.
- Show incompatible projects in Google Code import status (Stan Hu)
- Fix bug where commit data would not appear in some subdirectories (Stan Hu)
- - Unescape branch names in compare commit (Stan Hu)
- Task lists are now usable in comments, and will show up in Markdown previews.
+ - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu)
- Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu)
+ - Protect OmniAuth request phase against CSRF.
+ - Don't send notifications to mentioned users that don't have access to the project in question.
+ - Add search issues/MR by number
- Move snippets UI to fluid layout
- Improve UI for sidebar. Increase separation between navigation and content
- Improve new project command options (Ben Bodenmiller)
@@ -48,6 +120,25 @@ v 7.11.0 (unreleased)
- Add footnotes support to Markdown (Guillaume Delbergue)
- Add current_sign_in_at to UserFull REST api.
- Make Sidekiq MemoryKiller shutdown signal configurable
+ - Add "Create Merge Request" buttons to commits and branches pages and push event.
+ - Show user roles by comments.
+ - Fix automatic blocking of auto-created users from Active Directory.
+ - Call merge request web hook for each new commits (Arthur Gautier)
+ - Use SIGKILL by default in Sidekiq::MemoryKiller
+ - Fix mentioning of private groups.
+ - Add style for <kbd> element in markdown
+ - Spin spinner icon next to "Checking for CI status..." on MR page.
+ - Fix reference links in dashboard activity and ATOM feeds.
+ - Ensure that the first added admin performs repository imports
+
+v 7.10.4
+ - Fix migrations broken in 7.10.2
+ - Make tags for GitLab installations running on MySQL case sensitive
+ - Get Gitorious importer to work again.
+ - Fix adding new group members from admin area
+ - 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
@@ -130,12 +221,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
@@ -144,7 +235,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
@@ -449,6 +540,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 3165b7379d3..38fa66816a7 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.
@@ -63,7 +61,7 @@ Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org
If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
-To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file.
+To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file.
### Merge request guidelines
@@ -86,7 +84,9 @@ If you can, please submit a merge request with the fix or improvements including
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk.
-The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
+The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast.
+Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as regressions requiring patch releases.
+After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
@@ -160,6 +160,8 @@ If you add a dependency in GitLab (such as an operating system package) please c
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript)
1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
+1. [Database Migrations](doc/development/migration_style_guide.md)
+1. [Documentation styleguide](doc_styleguide.md)
1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing).
This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
@@ -176,4 +178,4 @@ Project maintainers have the right and responsibility to remove, edit, or reject
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/)
+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
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 097a15a2af3..ec1cf33c3f6 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.2
+2.6.3
diff --git a/Gemfile b/Gemfile
index f950c5be154..0009a8affba 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,18 +1,7 @@
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'
-
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -23,34 +12,48 @@ gem "pg", group: :postgres
# Auth
gem "devise", '3.2.4'
gem "devise-async", '0.9.0'
-gem 'omniauth', "~> 1.1.3"
+gem 'omniauth', "~> 1.2.2"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
-gem 'omniauth-kerberos'
+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"
+# Two-factor authentication
+gem 'devise-two-factor'
+gem 'rqrcode-rails3'
+gem 'attr_encrypted', '1.3.4'
+
# Browser detection
gem "browser"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.1.10'
+gem "gitlab_git", '~> 7.2.2'
# 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
@@ -89,7 +92,7 @@ gem "seed-fu"
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
-gem 'task_list', '~> 1.0.0', require: 'task_list/railtie'
+gem 'task_list', '1.0.2', require: 'task_list/railtie'
gem 'github-markup'
gem 'redcarpet', '~> 3.2.3'
gem 'RedCloth'
@@ -97,7 +100,7 @@ gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.12'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
-gem 'asciidoctor', '= 0.1.4'
+gem 'asciidoctor', '~> 1.5.2'
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -167,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'
@@ -181,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.2'
+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
@@ -234,25 +237,18 @@ group :development, :test do
gem 'minitest', '~> 5.3.0'
# Generate Fake data
- gem "ffaker"
-
- # 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')
+ gem 'ffaker', '~> 2.0.0'
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.5.1'
- gem 'jasmine-rails'
+ gem 'teaspoon', '~> 1.0.0'
+ gem 'teaspoon-jasmine'
- gem "spring", '~> 1.3.1'
- gem "spring-commands-rspec", '1.0.4'
- gem "spring-commands-spinach", '1.0.0'
+ 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'
gem "byebug"
end
@@ -272,4 +268,4 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
-gem "rugments"
+gem "rugments", "~> 1.0.0.beta7"
diff --git a/Gemfile.lock b/Gemfile.lock
index 6f58c4f4fda..a341a5df409 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -42,10 +42,12 @@ GEM
arel (5.0.1.20140414130214)
asana (0.0.6)
activeresource (>= 3.2.3)
- asciidoctor (0.1.4)
+ asciidoctor (1.5.2)
ast (2.0.0)
astrolabe (1.3.0)
parser (>= 2.2.0.pre.3, < 3.0)
+ attr_encrypted (1.3.4)
+ encryptor (>= 1.3.0)
attr_required (1.0.0)
autoprefixer-rails (5.1.11)
execjs
@@ -99,13 +101,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)
@@ -136,6 +138,13 @@ GEM
warden (~> 1.2.3)
devise-async (0.9.0)
devise (~> 3.2)
+ devise-two-factor (1.0.1)
+ activemodel
+ activesupport
+ attr_encrypted (~> 1.3.2)
+ devise (~> 3.2.4)
+ rails
+ rotp (~> 1.6.1)
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.5)
@@ -147,6 +156,7 @@ GEM
email_spec (1.5.0)
launchy (~> 2.1)
mail (~> 2.2)
+ encryptor (1.3.0)
enumerize (0.7.0)
activesupport (>= 3.2)
equalizer (0.0.8)
@@ -166,7 +176,7 @@ 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
@@ -215,11 +225,11 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.1.10)
+ gitlab_git (7.2.2)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
- rugged (~> 0.21.2)
+ rugged (~> 0.22.2)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
@@ -251,19 +261,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)
@@ -290,14 +287,8 @@ 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
jquery-atwho-rails (1.0.1)
- jquery-rails (3.1.0)
+ jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -322,13 +313,14 @@ 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)
mime-types (1.25.1)
mimemagic (0.3.0)
- mini_portile (0.6.1)
+ mini_portile (0.6.2)
minitest (5.3.5)
mousetrap-rails (1.4.6)
multi_json (1.10.1)
@@ -340,7 +332,7 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
newrelic_rpm (3.9.4.245)
- nokogiri (1.6.5)
+ nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
nprogress-rails (0.1.2.3)
oauth (0.4.7)
@@ -352,9 +344,9 @@ GEM
rack (~> 1.2)
octokit (3.7.0)
sawyer (~> 0.6.0, >= 0.5.3)
- omniauth (1.1.4)
- hashie (>= 1.2, < 3)
- rack
+ omniauth (1.2.2)
+ hashie (>= 1.2, < 4)
+ rack (~> 1.0)
omniauth-bitbucket (0.0.2)
multi_json (~> 1.7)
omniauth (~> 1.1)
@@ -379,6 +371,9 @@ 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)
@@ -390,7 +385,6 @@ GEM
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
pg (0.15.1)
- phantomjs (1.9.8.0)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -408,10 +402,10 @@ GEM
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
racc (1.4.10)
- rack (1.5.2)
+ rack (1.5.3)
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)
@@ -440,8 +434,6 @@ GEM
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)
@@ -482,11 +474,11 @@ GEM
rest-client (1.6.7)
mime-types (>= 1.16)
rinku (1.7.3)
+ rotp (1.6.1)
rouge (1.7.7)
- rspec (2.99.0)
- rspec-core (~> 2.99.0)
- rspec-expectations (~> 2.99.0)
- rspec-mocks (~> 2.99.0)
+ rqrcode (0.4.2)
+ rqrcode-rails3 (0.1.7)
+ rqrcode (>= 0.4.2)
rspec-collection_matchers (1.1.2)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
@@ -509,6 +501,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)
@@ -516,8 +511,8 @@ GEM
sexp_processor (~> 4.1)
rubyntlm (0.5.0)
rubypants (0.2.0)
- rugged (0.21.4)
- rugments (1.0.0.beta6)
+ rugged (0.22.2)
+ rugments (1.0.0.beta7)
safe_yaml (0.9.7)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -533,9 +528,9 @@ 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)
@@ -575,11 +570,13 @@ 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)
@@ -592,8 +589,13 @@ GEM
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)
@@ -619,7 +621,7 @@ GEM
multi_json (~> 1.7)
twitter-stream (~> 0.1)
tins (0.13.1)
- turbolinks (2.0.0)
+ turbolinks (2.5.3)
coffee-rails
twitter-stream (0.1.16)
eventmachine (>= 0.12.8)
@@ -640,6 +642,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)
@@ -669,7 +673,8 @@ DEPENDENCIES
addressable
annotate (~> 2.6.0.beta2)
asana (~> 0.0.6)
- asciidoctor (= 0.1.4)
+ asciidoctor (~> 1.5.2)
+ attr_encrypted (= 1.3.4)
awesome_print
better_errors
binding_of_caller
@@ -691,13 +696,14 @@ DEPENDENCIES
default_value_for (~> 3.0.0)
devise (= 3.2.4)
devise-async (= 0.9.0)
+ devise-two-factor
diffy (~> 3.0.3)
doorkeeper (= 2.1.3)
dropzonejs-rails
email_spec
enumerize
factory_girl_rails
- ffaker
+ ffaker (~> 2.0.0)
fog (~> 1.14)
font-awesome-rails (~> 4.2)
foreman
@@ -707,23 +713,19 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.1.10)
+ gitlab_git (~> 7.2.2)
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.2)
jquery-scrollto-rails
jquery-turbolinks
jquery-ui-rails
@@ -735,12 +737,13 @@ DEPENDENCIES
newrelic_rpm
nprogress-rails
octokit (= 3.7.0)
- omniauth (~> 1.1.3)
+ omniauth (~> 1.2.2)
omniauth-bitbucket
omniauth-github
omniauth-gitlab
omniauth-google-oauth2
omniauth-kerberos
+ omniauth-saml
omniauth-shibboleth
omniauth-twitter
org-ruby (= 0.9.12)
@@ -748,23 +751,21 @@ DEPENDENCIES
poltergeist (~> 1.5.1)
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)
raphael-rails (~> 2.1.2)
- rb-fsevent
- rb-inotify
rdoc (~> 3.6)
redcarpet (~> 3.2.3)
redis-rails
request_store
rerun (~> 0.10.0)
+ rqrcode-rails3
rspec-rails (= 2.99)
rubocop (= 0.28.0)
- rugments
+ rugments (~> 1.0.0.beta7)
sanitize (~> 2.0)
sass-rails (~> 4.0.2)
sdoc
@@ -781,15 +782,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.0)
+ 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
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 130351b15b8..85ea5c876af 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.
## Open source software to collaborate on code
-![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif)
+To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
@@ -21,7 +21,7 @@ There are two editions of GitLab.
*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
-To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/).
+To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
## Code status
@@ -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.
+[These people](https://twitter.com/gitlab/favorites) seem to like it. \ No newline at end of file
diff --git a/VERSION b/VERSION
index e85691e6ff7..5f0902c7c6a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.11.0.pre
+7.12.0.pre \ No newline at end of file
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index bb9da147018..6a3f7386d5b 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()
@@ -140,8 +133,8 @@ $ ->
# Place the logo tooltip on the right when collapsed, bottom when expanded
$el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom'
else
- # Otherwise use the data-placement attribute like normal
- $el.data('placement')
+ # Otherwise use the data-placement attribute, or 'bottom' if undefined
+ $el.data('placement') or 'bottom'
})
# Form submitter
@@ -174,6 +167,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 +186,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/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 06787ddf874..da56e3cdbc8 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -27,6 +27,7 @@ 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'
@@ -112,6 +113,13 @@ 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'
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/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 86ad3d03bac..74d6b80be5e 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,4 +1,3 @@
-#= require jquery
#= require jquery.waitforimages
#= require task_list
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 7c1e2b822d7..b8f916b5223 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,17 +1,31 @@
-#= require jquery
-#= require bootstrap
+#= require jquery.waitforimages
#= require task_list
class @MergeRequest
+ # Initialize MergeRequest behavior
+ #
+ # Options:
+ # action - String, current controller action
+ # diffs_loaded - Boolean, have diffs been pre-rendered server-side?
+ # (default: true if `action` is 'diffs', otherwise false)
+ # commits_loaded - Boolean, have commits been pre-rendered server-side?
+ # (default: false)
+ #
+ # 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) ->
@initContextWidget()
this.$el = $('.merge-request')
- @diffs_loaded = if @opts.action == 'diffs' then true else false
- @commits_loaded = false
- this.activateTab(@opts.action)
+ @diffs_loaded = @opts.diffs_loaded or @opts.action == 'diffs'
+ @commits_loaded = @opts.commits_loaded or false
this.bindEvents()
+ this.activateTabFromPath()
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
@@ -65,8 +79,18 @@ class @MergeRequest
, 'json'
bindEvents: ->
- this.$('.merge-request-tabs').on 'click', 'li', (event) =>
- this.activateTab($(event.currentTarget).data('action'))
+ this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) =>
+ $target = $(e.target)
+ tab_action = $target.data('action')
+
+ # Lazy-load diffs
+ if tab_action == 'diffs'
+ this.loadDiff() unless @diffs_loaded
+ $('.diff-header').trigger('sticky_kit:recalc')
+
+ # Skip tab-persisting behavior on MergeRequests#new
+ unless @opts.action == 'new'
+ @setCurrentAction(tab_action)
this.$('.accept_merge_request').on 'click', ->
$('.automerge_widget.can_be_merged').hide()
@@ -84,21 +108,54 @@ class @MergeRequest
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()
+ # Activate a tab based on the current URL path
+ #
+ # If the current action is 'show' or 'new' (i.e., initial page load),
+ # activates the first tab, otherwise activates the tab corresponding to the
+ # current action (diffs, commits).
+ activateTabFromPath: ->
+ if @opts.action == 'show' || @opts.action == 'new'
+ this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
+ else
+ this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show')
+
+ # Replaces the current Merge Request-specific action in the URL with a new one
+ #
+ # If the action is "notes", the URL is reset to the standard
+ # `MergeRequests#show` route.
+ #
+ # Examples:
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ # setCurrentAction('diffs')
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('notes')
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('commits')
+ # location.pathname # => "/namespace/project/merge_requests/1/commits"
+ setCurrentAction: (action) ->
+ # Normalize action, just to be safe
+ action = 'notes' if action == 'show'
+
+ # Remove a trailing '/commits' or '/diffs'
+ new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '')
+
+ # Append the new action if we're on a tab other than 'notes'
+ unless action == 'notes'
+ new_state += "/#{action}"
+
+ # Ensure parameters and hash come along for the ride
+ new_state += location.search + location.hash
+
+ # Replace the current history state with the new one without breaking
+ # Turbolinks' history.
+ #
+ # See https://github.com/rails/turbolinks/issues/363
+ history.replaceState {turbolinks: true, url: new_state}, '', new_state
showState: (state) ->
$('.automerge_widget').hide()
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index c25b1ddb066..b9bd5c730bf 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
@@ -312,6 +310,14 @@ class @Notes
form.show()
textarea = form.find("textarea")
textarea.focus()
+
+ # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
+ # The textarea has the correct value, Chrome just won't show it unless we
+ # modify it, so let's clear it and re-set it!
+ value = textarea.val()
+ textarea.val ""
+ textarea.val value
+
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
###
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index de356fbec77..40459a9a155 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -12,11 +12,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", ->
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_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/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index a0794e7825a..08cbe911672 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -73,6 +73,22 @@
padding: 0;
}
+ kbd {
+ display: inline-block;
+ padding: 3px 5px;
+ font-size: 11px;
+ line-height: 10px;
+ color: #555;
+ vertical-align: middle;
+ background-color: #FCFCFC;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #CCC #CCC #BBB;
+ border-image: none;
+ border-radius: 3px;
+ box-shadow: 0px -1px 0px #BBB inset;
+ }
+
h1 {
margin-top: 45px;
font-size: 2.5em;
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index 596376c3970..3d7868fb7d2 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -1,10 +1,11 @@
$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;
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 1e569978cc8..1419a9cded9 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 {
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index 266041403e0..7e070b4f386 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -89,7 +89,6 @@ label {
@include box-shadow(none);
}
-.issuable-description,
.wiki-content {
margin-top: 35px;
}
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index fcd62373bfd..71afccba001 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -2,7 +2,14 @@
* Application Header
*
*/
+$header-height: 46px;
+
header {
+ &.navbar-empty {
+ background: #FFF;
+ border-bottom: 1px solid #EEE;
+ }
+
&.navbar-gitlab {
z-index: 100;
margin-bottom: 0;
@@ -11,161 +18,104 @@ header {
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;
+ line-height: 19px;
+ padding: 0;
+ background-color: #f5f5f5;
+ margin: 9px 0;
+ margin-left: 10px;
+ border-radius: 40px;
+ height: 26px;
+ width: 26px;
+ line-height: 26px;
+ 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;
a {
float: left;
- height: 46px;
+ height: $header-height;
width: 100%;
+ padding: 5px 8px;
+
+ h3 {
+ width: 158px;
+ float: left;
+ margin: 0;
+ margin-left: 20px;
+ font-size: 18px;
+ line-height: 34px;
+ font-weight: normal;
+ }
img {
width: 36px;
height: 36px;
+ float: left;
}
}
+
&: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 {
+ position: relative;
+ float: left;
+ margin: 0;
+ margin-left: 35px;
+ font-size: 18px;
+ line-height: 44px;
+ font-weight: bold;
+ color: #444;
+
+ @include str-truncated(37%);
+
+ a {
+ color: #444;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
}
}
- /**
- *
- * Search box
- *
- */
.search {
margin-right: 10px;
margin-left: 10px;
@@ -177,6 +127,7 @@ header {
}
.search-input {
+ width: 220px;
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
@@ -184,56 +135,74 @@ 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: #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;
- }
-}
+@mixin collapsed-header {
+ .header-logo {
+ width: $sidebar_collapsed_width;
-@media (max-width: 1200px) {
- .search .search-input {
- width: 200px;
- &:focus {
- width: 230px;
+ h3 {
+ display: none;
}
}
-}
-@media (max-width: $screen-xs-max) {
- #nprogress .spinner {
- right: 35px !important;
+ .header-content {
+ .title {
+ margin-left: 30px;
+ }
}
}
@media (max-width: $screen-md-max) {
- .header-collapsed, .header-expanded {
- width: 52px;
+ header .container .title {
+ max-width: 43%;
+ }
- h3 {
- display: none;
- }
+ .header-collapsed, .header-expanded {
+ @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 {
+ max-width: 70%;
+ }
+
+ .navbar-nav {
+ margin: 0px;
+ float: none !important;
+
+ .visible-xs, .visable-sm {
+ display: table-cell !important;
+ }
+ }
+
+ li {
+ display: table-cell;
+ width: 1%;
+ }
+ }
+}
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/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index b7f6fac5223..74108c1f086 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -57,7 +57,7 @@
}
.container .title {
- margin-left: 6px !important;
+ margin-left: 15px !important;
max-width: 70% !important;
}
}
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 754c5b53020..69bddc6f59e 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -1,6 +1,4 @@
.page-with-sidebar {
- background: $background-color;
-
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -102,13 +100,13 @@
padding-left: 50px;
.sidebar-wrapper {
- width: 52px;
+ width: $sidebar_collapsed_width;
.nav-sidebar {
margin-top: 29px;
position: fixed;
top: 45px;
- width: 52px;
+ width: $sidebar_collapsed_width;
li a {
padding-left: 18px;
@@ -125,7 +123,21 @@
.collapse-nav a {
left: 0px;
- width: 52px;
+ width: $sidebar_collapsed_width;
+ }
+
+ .sidebar-user {
+ .username {
+ display: none;
+ }
+
+ .avatar {
+ margin-bottom: 10px;
+ }
+
+ .logout-holder {
+ text-align: center;
+ }
}
}
}
@@ -170,3 +182,15 @@
@include expanded-sidebar;
}
}
+
+.sidebar-user {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ padding: 10px;
+ color: #fff;
+
+ .avatar {
+ margin-top: 5px;
+ }
+}
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..bcb8bbe3134 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -1,7 +1,7 @@
.zennable {
position: relative;
- input {
+ .zen-toggle-comment {
display: none;
}
@@ -26,10 +26,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,6 +64,9 @@
}
}
+ // Make the placeholder text in the standard textarea the same color as the
+ // background, effectively hiding it
+
.zen-backdrop textarea::-webkit-input-placeholder {
color: white;
}
@@ -78,6 +83,9 @@
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;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 84361e15481..359f4073e87 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -29,10 +29,6 @@
.commits-feed-holder {
float: right;
-
- .btn {
- padding: 4px 12px;
- }
}
li.commit {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3165396a94d..f5ac7bd8805 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -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/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 589a43c4264..42b8ecabb38 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -33,7 +33,16 @@ ul.notes {
&:before {
content: "\00b7";
}
+
font-size: 13px;
+
+ a {
+ @extend .cgray;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
}
.author {
color: #333;
@@ -44,6 +53,14 @@ ul.notes {
}
.author-username {
}
+
+ .note-role {
+ float: right;
+ margin-top: 1px;
+ border: 1px solid #bbb;
+ background-color: transparent;
+ color: #999;
+ }
}
.discussion {
@@ -62,11 +79,11 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
- // Reduce left padding of first ul element
+ // Reduce left padding of first task list ul element
ul.task-list:first-child {
padding-left: 10px;
- // sub-lists should be padded normally
+ // sub-tasks should be padded normally
ul {
padding-left: 20px;
}
@@ -134,28 +151,23 @@ ul.notes {
.discussion,
.note {
- &.note:hover {
- .note-actions { display: block; }
- }
- .discussion-header:hover {
- .discussion-actions { display: block; }
- }
-
.discussion-actions,
.note-actions {
- display: none;
float: right;
-
- i.fa {
- font-size: 16px;
- line-height: 16px;
- vertical-align: middle;
- }
+ margin-left: 10px;
a {
- @extend .cgray;
+ margin-left: 5px;
+
+ color: #999;
+
+ i.fa {
+ font-size: 16px;
+ line-height: 16px;
+ }
&:hover {
+ @extend .cgray;
&.danger { @extend .cred; }
}
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 280e8b57174..5a5fbc468a3 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -84,8 +84,9 @@
}
.btn {
- line-height: 36px;
- height: 56px;
+ line-height: 40px;
+ height: 42px;
+ padding: 0px 12px;
img {
width: 32px;
@@ -93,3 +94,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/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 5a8d4665294..b93ea0f020e 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -48,14 +48,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;
+ }
}
}
@@ -129,7 +131,7 @@
}
.option-descr {
- margin-left: 24px;
+ margin-left: 36px;
color: $gray;
}
}
@@ -209,23 +211,34 @@ 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 {
+ margin-top: 10px;
}
.nav-pills a {
padding: 10px;
+ font-weight: bold;
+ color: $gl-link-color;
}
.nav {
- margin: 10px 0;
+ margin-bottom: 15px;
}
}
+
+ .ci-status-image {
+ max-height: 22px;
+ }
}
.transfer-project .select2-container {
@@ -249,7 +262,8 @@ ul.nav.nav-projects-tabs {
}
.breadcrumb.repo-breadcrumb {
- padding: 2px 0;
+ padding: 0;
+ line-height: 34px;
background: white;
border: none;
font-size: 16px;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 57f63b52aa1..34ee4d7b31e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -106,17 +106,9 @@
}
}
-.tree-download-holder .btn {
- padding: 4px 12px;
-}
-
.tree-ref-holder {
float: left;
margin-right: 15px;
-
- .select2-container .select2-choice, .select2-container.select2-drop-above .select2-choice {
- padding: 4px 12px;
- }
}
.readme-holder {
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 139b3cc1ac4..1b06b4aa925 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -1,8 +1,9 @@
@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 +20,6 @@
}
.page-with-sidebar {
- background: $color-darker;
-
.collapse-nav a {
color: #FFF;
background: $color;
@@ -29,6 +28,12 @@
.sidebar-wrapper {
background: $color-darker;
border-right: 1px solid $color-darker;
+
+ .sidebar-user {
+ a {
+ color: $color-light;
+ }
+ }
}
.nav-sidebar li {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 3975e30835e..a01e2a907d7 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -38,10 +38,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:twitter_sharing_enabled,
:sign_in_text,
:home_page_url,
+ :after_sign_out_path,
:max_attachment_size,
: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/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 0a463239d74..690096bdbcf 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -33,7 +33,7 @@ class Admin::HooksController < Admin::ApplicationController
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
- @hook.execute(data)
+ @hook.execute(data, 'system_hooks')
redirect_to :back
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index d36e359934c..06d6d61e907 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -86,11 +86,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.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 eee10d6c22a..62d46a5482e 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
@@ -252,7 +252,7 @@ class ApplicationController < ActionController::Base
end
def configure_permitted_parameters
- devise_parameter_sanitizer.sanitize(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) }
+ devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me, :otp_attempt) }
end
def hexdigest(string)
@@ -289,14 +289,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/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
new file mode 100644
index 00000000000..d5918a7af3b
--- /dev/null
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -0,0 +1,30 @@
+# == AuthenticatesWithTwoFactor
+#
+# Controller concern to handle two-factor authentication
+#
+# Upon inclusion, skips `require_no_authentication` on `:create`.
+module AuthenticatesWithTwoFactor
+ extend ActiveSupport::Concern
+
+ included do
+ # This action comes from DeviseController, but because we call `sign_in`
+ # manually, not skipping this action would cause a "You are already signed
+ # in." error message to be shown upon successful login.
+ skip_before_action :require_no_authentication, only: [:create]
+ end
+
+ # Store the user's ID in the session for later retrieval and render the
+ # two factor code prompt
+ #
+ # The user must have been authenticated with a valid login and password
+ # before calling this method!
+ #
+ # user - User record
+ #
+ # Returns nil
+ def prompt_for_two_factor(user)
+ session[:otp_user_id] = user.id
+
+ render 'devise/sessions/two_factor' and return
+ end
+end
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 bb9d65c9ed6..a767815b311 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
@@ -65,8 +68,15 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
- rescue Gitlab::OAuth::ForbiddenAction => e
- flash[:notice] = e.message
+ rescue Gitlab::OAuth::SignupDisabledError => e
+ message = "Signing in using your #{oauth['provider']} account without a pre-existing GitLab account is not allowed."
+
+ if current_application_settings.signup_enabled?
+ message << " Create a GitLab account first, and then connect it to your #{oauth['provider']} account."
+ end
+
+ flash[:notice] = message
+
redirect_to new_user_session_path
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index dcbbe5baa4b..145f27b67dd 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -15,4 +15,45 @@ class PasswordsController < Devise::PasswordsController
respond_with(resource)
end
end
+
+ # After a user resets their password, prompt for 2FA code if enabled instead
+ # of signing in automatically
+ #
+ # See http://git.io/vURrI
+ def update
+ 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?)
+ resource.unlock_access! if unlockable?(resource)
+
+ # Since we are not signing this user in, we use the :updated_not_active
+ # message which only contains "Your password was changed successfully."
+ set_flash_message(:notice, :updated_not_active) if is_flashing_format?
+
+ # Redirect to sign in so they can enter 2FA code
+ respond_with(resource, location: new_session_path(resource)) and return
+ end
+ end
+ end
+
+ def edit
+ super
+ reset_password_token = Devise.token_generator.digest(
+ User,
+ :reset_password_token,
+ resource.reset_password_token
+ )
+
+ unless reset_password_token.nil?
+ user = User.where(
+ reset_password_token: reset_password_token
+ ).first_or_initialize
+
+ unless user.reset_password_period_valid?
+ flash[:alert] = 'Your password reset token has expired.'
+ redirect_to(new_user_password_url(user_email: user['email']))
+ end
+ end
+ end
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
new file mode 100644
index 00000000000..42579b3eb44
--- /dev/null
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -0,0 +1,50 @@
+class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
+ def new
+ unless current_user.otp_secret
+ current_user.otp_secret = User.generate_otp_secret(32)
+ current_user.save!
+ end
+
+ @qr_code = build_qr_code
+ end
+
+ def create
+ if current_user.valid_otp?(params[:pin_code])
+ current_user.otp_required_for_login = true
+ @codes = current_user.generate_otp_backup_codes!
+ current_user.save!
+
+ render 'create'
+ else
+ @error = 'Invalid pin code'
+ @qr_code = build_qr_code
+
+ render 'new'
+ end
+ end
+
+ def codes
+ @codes = current_user.generate_otp_backup_codes!
+ current_user.save!
+ end
+
+ def destroy
+ current_user.update_attributes({
+ otp_required_for_login: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_backup_codes: nil
+ })
+
+ redirect_to profile_account_path
+ end
+
+ private
+
+ def build_qr_code
+ issuer = "GitLab | #{current_user.email}"
+ uri = current_user.otp_provisioning_uri(current_user.email, issuer: issuer)
+ RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
+ end
+end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 7c20b81c0b1..c5f085c236f 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -6,11 +6,12 @@ class Projects::CompareController < Projects::ApplicationController
before_action :authorize_download_code!
def index
+ @ref = Addressable::URI.unescape(params[:to])
end
def show
base_ref = Addressable::URI.unescape(params[:from])
- head_ref = Addressable::URI.unescape(params[:to])
+ @ref = head_ref = Addressable::URI.unescape(params[:to])
compare_result = CompareService.new.execute(
current_user,
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/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 57fc48ac7da..76062446c92 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -53,6 +53,6 @@ class Projects::HooksController < Projects::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events)
+ params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c524e1a0ea3..7d168aa827b 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -19,7 +19,15 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
@issues = get_issues_collection
- @issues = @issues.full_search(terms) if terms.present?
+
+ if terms.present?
+ if terms =~ /\A#(\d+)\z/
+ @issues = @issues.where(iid: $1)
+ else
+ @issues = @issues.full_search(terms)
+ end
+ end
+
@issues = @issues.page(params[:page]).per(PER_PAGE)
respond_to do |format|
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 5b93e95866a..71d3051ab88 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,10 +2,13 @@ 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!
@@ -19,7 +22,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def index
terms = params['issue_search']
@merge_requests = get_merge_requests_collection
- @merge_requests = @merge_requests.full_search(terms) if terms.present?
+
+ if terms.present?
+ if terms =~ /\A[#!](\d+)\z/
+ @merge_requests = @merge_requests.where(iid: $1)
+ else
+ @merge_requests = @merge_requests.full_search(terms)
+ end
+ end
+
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
respond_to do |format|
@@ -59,6 +70,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def commits
+ render 'show'
+ end
+
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d7fbc979067..b110de11013 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -73,10 +73,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_controller.rb b/app/controllers/projects_controller.rb
index dc430351551..4ca5fc65459 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
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 830751a989f..6ccc7934f2f 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.execute(current_user)
respond_to do |format|
format.html { redirect_to new_user_session_path, notice: "Account successfully removed." }
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 3f11d7afe6f..4d976fe6630 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,4 +1,9 @@
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 =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
@@ -14,7 +19,7 @@ class SessionsController < Devise::SessionsController
# Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully.
- unless redirect_path == '/users/sign_in'
+ unless redirect_path == new_user_session_path
store_location_for(:redirect, redirect_path)
end
@@ -27,11 +32,67 @@ class SessionsController < Devise::SessionsController
def create
super do |resource|
- # User has successfully signed in, so clear any unused reset tokens
+ # User has successfully signed in, so clear any unused reset token
if resource.reset_password_token.present?
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
end
end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
+ end
+
+ def find_user
+ if user_params[:login]
+ User.by_login(user_params[:login])
+ elsif user_params[:otp_attempt] && session[:otp_user_id]
+ User.find(session[:otp_user_id])
+ end
+ end
+
+ def authenticate_with_two_factor
+ user = self.resource = find_user
+
+ return unless user && user.otp_required_for_login
+
+ if user_params[:otp_attempt].present? && session[:otp_user_id]
+ if valid_otp_attempt?(user)
+ # Remove any lingering user data from login
+ session.delete(:otp_user_id)
+
+ sign_in(user) and return
+ else
+ flash.now[:alert] = 'Invalid two-factor code.'
+ render :two_factor and return
+ end
+ else
+ if user && user.valid_password?(user_params[:password])
+ prompt_for_two_factor(user)
+ end
+ 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])
+ end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 17edff68be2..28536e359e5 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -52,13 +52,13 @@ class UploadsController < ApplicationController
def upload_model
upload_models = {
- user: User,
- project: Project,
- note: Note,
- group: Group
+ "user" => User,
+ "project" => Project,
+ "note" => Note,
+ "group" => Group
}
- upload_models[params[:model].to_sym]
+ upload_models[params[:model]]
end
def upload_mount
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..0bed2115dc7 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,10 @@ class IssuableFinder
case params[:state]
when 'closed'
items.closed
+ when 'rejected'
+ items.respond_to?(:rejected) ? items.rejected : items.closed
+ when 'merged'
+ items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
items
when 'opened'
@@ -85,25 +162,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 +184,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 +221,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/application_helper.rb b/app/helpers/application_helper.rb
index 6e86400a4f6..a539ec49f7a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -214,7 +214,7 @@ module ApplicationHelper
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do
haml_tag :time, date.to_s,
- class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
+ class: html_class, datetime: date.getutc.iso8601, title: date.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement }
haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
@@ -222,18 +222,28 @@ module ApplicationHelper
end
def render_markup(file_name, file_content)
- GitHub::Markup.render(file_name, file_content).
- force_encoding(file_content.encoding).html_safe
+ if gitlab_markdown?(file_name)
+ Haml::Helpers.preserve(markdown(file_content))
+ elsif asciidoc?(file_name)
+ asciidoc(file_content)
+ else
+ GitHub::Markup.render(file_name, file_content).
+ force_encoding(file_content.encoding).html_safe
+ end
rescue RuntimeError
simple_format(file_content)
end
def markup?(filename)
- Gitlab::MarkdownHelper.markup?(filename)
+ Gitlab::MarkupHelper.markup?(filename)
end
def gitlab_markdown?(filename)
- Gitlab::MarkdownHelper.gitlab_markdown?(filename)
+ Gitlab::MarkupHelper.gitlab_markdown?(filename)
+ end
+
+ def asciidoc?(filename)
+ Gitlab::MarkupHelper.asciidoc?(filename)
end
# Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to
@@ -269,10 +279,6 @@ module ApplicationHelper
html_options
end
- def escaped_autolink(text)
- auto_link ERB::Util.html_escape(text), link: :urls
- end
-
def promo_host
'about.gitlab.com'
end
@@ -320,16 +326,29 @@ module ApplicationHelper
end
def state_filters_text_for(entity, project)
- entity_title = entity.to_s.humanize
+ titles = {
+ opened: "Open",
+ merged: "Accepted"
+ }
+
+ entity_title = titles[entity] || entity.to_s.humanize
count =
if project.nil?
- ""
+ nil
elsif current_controller?(:issues)
- " (#{project.issues.send(entity).count})"
+ project.issues.send(entity).count
elsif current_controller?(:merge_requests)
- " (#{project.merge_requests.send(entity).count})"
+ project.merge_requests.send(entity).count
end
- "#{entity_title}#{count}"
+
+ html = content_tag :span, entity_title
+
+ if count.present?
+ html += " "
+ html += content_tag :span, number_with_delimiter(count), class: 'badge'
+ end
+
+ html.html_safe
end
end
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 4ea838ca447..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,12 +8,14 @@ module BlobHelper
)
begin
- lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content)
- rescue Rugments::Lexer::AmbiguousGuess
+ @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
end
- formatter.format(lexer.lex(blob_content)).html_safe
+ result
end
def no_highlight_files
@@ -55,7 +57,7 @@ module BlobHelper
end
def editing_preview_title(filename)
- if Gitlab::MarkdownHelper.previewable?(filename)
+ if Gitlab::MarkupHelper.previewable?(filename)
'Preview'
else
'Preview changes'
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 01847c6b807..f1dc906cab4 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -1,21 +1,20 @@
module CompareHelper
- def compare_to_mr_button?
- @project.merge_requests_enabled &&
- params[:from].present? &&
- params[:to].present? &&
- @repository.branch_names.include?(params[:from]) &&
- @repository.branch_names.include?(params[:to]) &&
- params[:from] != params[:to] &&
- !@refs_are_same
+ def create_mr_button?(from = params[:from], to = params[:to], project = @project)
+ from.present? &&
+ to.present? &&
+ from != to &&
+ project.merge_requests_enabled &&
+ project.repository.branch_names.include?(from) &&
+ project.repository.branch_names.include?(to)
end
- def compare_mr_path
+ def create_mr_path(from = params[:from], to = params[:to], project = @project)
new_namespace_project_merge_request_path(
- @project.namespace,
- @project,
+ project.namespace,
+ project,
merge_request: {
- source_branch: params[:to],
- target_branch: params[:from]
+ source_branch: to,
+ target_branch: from
}
)
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 1b10795bb7b..1bd3ec5e0e0 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -101,6 +101,10 @@ module DiffHelper
(bottom) ? 'js-unfold-bottom' : ''
end
+ def unfold_class(unfold)
+ (unfold) ? 'unfold js-unfold' : ''
+ end
+
def diff_line_content(line)
if line.blank?
" &nbsp;"
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 0df3ecc90b7..128de18bc47 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -35,4 +35,23 @@ module EmailsHelper
lexer = Rugments::Lexers::Diff.new
raw formatter.format(lexer.lex(diffcontent))
end
+
+ def password_reset_token_valid_time
+ valid_hours = Devise.reset_password_within / 60 / 60
+ if valid_hours >= 24
+ unit = 'day'
+ valid_length = (valid_hours / 24).floor
+ else
+ unit = 'hour'
+ valid_length = valid_hours.floor
+ end
+
+ pluralize(valid_length, unit)
+ end
+
+ def reset_token_expire_message
+ link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
+ msg = "This link is valid for #{password_reset_token_valid_time}. "
+ msg << "After it expires, you can #{link_tag}."
+ end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 18c75a8726b..d440da050e1 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -168,8 +168,8 @@ module EventsHelper
end
end
- def event_note(text)
- text = first_line_in_markdown(text, 150)
+ def event_note(text, options = {})
+ text = first_line_in_markdown(text, 150, options)
sanitize(text, tags: %w(a img b pre code p span))
end
@@ -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:%SZ")
+ xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%S%Z")
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 24263a0f619..2777944fc9d 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -1,3 +1,5 @@
+require 'nokogiri'
+
module GitlabMarkdownHelper
include Gitlab::Markdown
@@ -19,199 +21,82 @@ module GitlabMarkdownHelper
escape_once(body)
end
- gfm_body = gfm(escaped_body, @project, html_options)
+ 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
# 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, {
- # Handled further down the line by Gitlab::Markdown::SanitizationFilter
- escape_html: false
- }.merge(options))
+ 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
end
+ def asciidoc(text)
+ Gitlab::Asciidoc.render(text, {
+ commit: @commit,
+ project: @project,
+ project_wiki: @project_wiki,
+ requested_path: @path,
+ ref: @ref
+ })
+ end
+
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
- def first_line_in_markdown(text, max_chars = nil)
- md = markdown(text).strip
+ def first_line_in_markdown(text, max_chars = nil, options = {})
+ md = markdown(text, options).strip
truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
- if wiki_page.format == :markdown
+ case wiki_page.format
+ when :markdown
markdown(wiki_page.content)
+ when :asciidoc
+ asciidoc(wiki_page.content)
else
wiki_page.formatted_content.html_safe
end
end
- # TODO (rspeicher): This should be its own filter
- def create_relative_links(text)
- paths = extract_paths(text)
-
- paths.uniq.each do |file_path|
- # If project does not have repository
- # its nothing to rebuild
- #
- # TODO: pass project variable to markdown helper instead of using
- # instance variable. Right now it generates invalid path for pages out
- # of project scope. Example: search results where can be rendered markdown
- # from different projects
- if @repository && @repository.exists? && !@repository.empty?
- new_path = rebuild_path(file_path)
- # Finds quoted path so we don't replace other mentions of the string
- # eg. "doc/api" will be replaced and "/home/doc/api/text" won't
- text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
- end
- end
-
- text
- end
-
- def extract_paths(text)
- links = substitute_links(text)
- image_links = substitute_image_links(text)
- links + image_links
- end
-
- def substitute_links(text)
- links = text.scan(/<a href=\"([^"]*)\">/)
- relative_links = links.flatten.reject{ |link| link_to_ignore? link }
- relative_links
- end
-
- def substitute_image_links(text)
- links = text.scan(/<img src=\"([^"]*)\"/)
- relative_links = links.flatten.reject{ |link| link_to_ignore? link }
- relative_links
- end
-
- def link_to_ignore?(link)
- if link =~ /\A\#\w+/
- # ignore anchors like <a href="#my-header">
- true
- else
- ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
- end
- end
-
- def ignored_protocols
- ["http://","https://", "ftp://", "mailto:", "smb://"]
- end
-
- def rebuild_path(file_path)
- file_path = file_path.dup
- file_path.gsub!(/(#.*)/, "")
- id = $1 || ""
- file_path = relative_file_path(file_path)
- file_path = sanitize_slashes(file_path)
-
- [
- Gitlab.config.gitlab.relative_url_root,
- @project.path_with_namespace,
- path_with_ref(file_path),
- file_path
- ].compact.join("/").gsub(/\A\/*|\/*\z/, '') + id
- end
-
- def sanitize_slashes(path)
- path[0] = "" if path.start_with?("/")
- path.chop if path.end_with?("/")
- path
- end
-
- def relative_file_path(path)
- requested_path = @path
- nested_path = build_nested_path(path, requested_path)
- return nested_path if file_exists?(nested_path)
- path
- end
-
- # Covering a special case, when the link is referencing file in the same directory eg:
- # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md)
- # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md
- # If we are at doc/api and the README.md shown in below the tree view
- # this takes the request path(doc/api) and adds users.md so the path looks like doc/api/users.md
- def build_nested_path(path, request_path)
- return request_path if path == ""
- return path unless request_path
- if local_path(request_path) == "tree"
- base = request_path.split("/").push(path)
- base.join("/")
- else
- base = request_path.split("/")
- base.pop
- base.push(path).join("/")
- end
- end
-
- # Checks if the path exists in the repo
- # eg. checks if doc/README.md exists, if not then link to blob
- def path_with_ref(path)
- if file_exists?(path)
- "#{local_path(path)}/#{correct_ref}"
- else
- "blob/#{correct_ref}"
- end
- end
-
- def file_exists?(path)
- return false if path.nil?
- @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
- end
-
- # Check if the path is pointing to a directory(tree) or a file(blob)
- # eg. doc/api is directory and doc/README.md is file
- def local_path(path)
- return "tree" if @repository.tree(current_sha, path).entries.any?
- return "raw" if @repository.blob_at(current_sha, path).image?
- "blob"
- end
-
- def current_sha
- if @commit
- @commit.id
- elsif @repository && !@repository.empty?
- if @ref
- @repository.commit(@ref).try(:sha)
- else
- @repository.head_commit.sha
- end
- end
- end
-
- # We will assume that if no ref exists we can point to master
- def correct_ref
- @ref ? @ref : "master"
- end
-
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
@@ -260,15 +145,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/icons_helper.rb b/app/helpers/icons_helper.rb
index a9030729b48..a730684f8f3 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -26,15 +26,15 @@ module IconsHelper
end
def public_icon
- icon('globe')
+ icon('globe fw')
end
def internal_icon
- icon('shield')
+ icon('shield fw')
end
def private_icon
- icon('lock')
+ icon('lock fw')
end
def file_type_icon_class(type, mode, name)
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/projects_helper.rb b/app/helpers/projects_helper.rb
index 96d2606f1a1..94ce6646634 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
@@ -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/selects_helper.rb b/app/helpers/selects_helper.rb
index bec8f2f1aa7..2b99a398049 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -10,6 +10,7 @@ module SelectsHelper
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
+ project = opts[:project] || @project
html = {
class: css_class,
@@ -21,8 +22,8 @@ module SelectsHelper
}
unless opts[:scope] == :all
- if @project
- html['data-project-id'] = @project.id
+ if project
+ html['data-project-id'] = project.id
elsif @group
html['data-group-id'] = @group.id
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 6dd9b6f017c..03a49e119b8 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -25,13 +25,7 @@ module TreeHelper
end
def render_readme(readme)
- if gitlab_markdown?(readme.name)
- preserve(markdown(readme.data))
- elsif markup?(readme.name)
- render_markup(readme.name, readme.data)
- else
- simple_format(readme.data)
- end
+ render_markup(readme.name, readme.data)
end
# Return an image icon depending on the file type and mode
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
new file mode 100644
index 00000000000..f64d730b448
--- /dev/null
+++ b/app/helpers/version_check_helper.rb
@@ -0,0 +1,7 @@
+module VersionCheckHelper
+ def version_status_badge
+ if Rails.env.production?
+ image_tag VersionCheck.new.url
+ end
+ 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 85a15596f8d..4e6c60dc8ca 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -101,6 +101,27 @@ class Ability
rules -= project_archived_rules
end
+ unless project.issues_enabled
+ rules -= named_abilities('issue')
+ end
+
+ unless project.merge_requests_enabled
+ 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('project_snippet')
+ end
+
+ unless project.wiki_enabled
+ rules -= named_abilities('wiki')
+ end
+
rules
end
end
@@ -272,5 +293,16 @@ class Ability
abilities
end
end
+
+ private
+
+ def named_abilities(name)
+ [
+ :"read_#{name}",
+ :"write_#{name}",
+ :"modify_#{name}",
+ :"admin_#{name}"
+ ]
+ end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index d5123249c53..80463ee8841 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,6 +18,8 @@
# 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
@@ -30,6 +32,10 @@ class ApplicationSetting < ActiveRecord::Base
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|
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be5a118bfec..f02fe240540 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,11 +156,6 @@ 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)
end
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 3ef3e8b67d8..6f9f54d08cc 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.
@@ -39,13 +44,13 @@ module Mentionable
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def has_mentioned?(target)
- Note.cross_reference_exists?(target, local_reference)
+ SystemNoteService.cross_reference_exists?(target, local_reference)
end
- def mentioned_users(current_user = nil, p = project)
+ def mentioned_users(current_user = nil)
return [] if mentionable_text.blank?
- ext = Gitlab::ReferenceExtractor.new(p, current_user)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
ext.analyze(mentionable_text)
ext.users.uniq
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 7a5e4876ff2..9f667f47e0d 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -35,31 +35,39 @@ module Participable
end
end
- def participants(current_user = self.author)
- self.class.participant_attrs.flat_map do |attr|
+ def participants(current_user = self.author, project = self.project)
+ participants = self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
value =
- if meth.arity == 1
+ if meth.arity == 1 || meth.arity == -1
meth.call(current_user)
else
meth.call
end
- participants_for(value, current_user)
+ participants_for(value, current_user, project)
end.compact.uniq
+
+ if project
+ participants.select! do |user|
+ user.can?(:read_project, project)
+ end
+ end
+
+ participants
end
private
- def participants_for(value, current_user = nil)
+ def participants_for(value, current_user = nil, project = nil)
case value
when User
[value]
when Enumerable, ActiveRecord::Relation
- value.flat_map { |v| participants_for(v, current_user) }
+ value.flat_map { |v| participants_for(v, current_user, project) }
when Participable
- value.participants(current_user)
+ value.participants(current_user, project)
end
end
end
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/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 1386a9eccc9..051c672cb33 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -17,10 +17,12 @@ 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
- validate :avatar_type, if: ->(user) { user.avatar_changed? }
+ validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AvatarUploader
@@ -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/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 21867a9316c..ca7066b959a 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
class ProjectHook < WebHook
@@ -21,5 +22,6 @@ class ProjectHook < WebHook
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) }
+ scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 2e11239c40b..b55e217975f 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -13,8 +13,13 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
class ServiceHook < WebHook
belongs_to :service
+
+ def execute(data)
+ super(data, 'service_hook')
+ end
end
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index ee32b49bc66..6fb2d421026 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
class SystemHook < WebHook
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 315d96af1b9..46fb85336e5 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
class WebHook < ActiveRecord::Base
@@ -21,6 +22,7 @@ class WebHook < ActiveRecord::Base
default_value_for :push_events, true
default_value_for :issues_events, false
+ default_value_for :note_events, false
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
@@ -30,12 +32,15 @@ class WebHook < ActiveRecord::Base
validates :url, presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
- def execute(data)
+ def execute(data, hook_name)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
WebHook.post(url,
body: data.to_json,
- headers: { "Content-Type" => "application/json" },
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
@@ -45,7 +50,10 @@ class WebHook < ActiveRecord::Base
}
WebHook.post(post_url,
body: data.to_json,
- headers: { "Content-Type" => "application/json" },
+ headers: {
+ "Content-Type" => "application/json",
+ "X-Gitlab-Event" => hook_name.singularize.titleize
+ },
verify: false,
basic_auth: auth)
end
@@ -54,7 +62,7 @@ class WebHook < ActiveRecord::Base
false
end
- def async_execute(data)
- Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data)
+ def async_execute(data, hook_name)
+ Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
end
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..f1f9f23b12c 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"
@@ -133,7 +134,31 @@ class MergeRequest < ActiveRecord::Base
# 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 :rejected, -> { with_states(:closed) }
+
+ 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 +197,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 +313,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
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9bbb2bafb98..9c543b37023 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -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 cbce6786683..d5f716b3de0 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -63,143 +63,9 @@ class Note < ActiveRecord::Base
after_update :set_references
class << self
- def create_status_change_note(noteable, project, author, status, source)
- body = "Status changed to #{status}#{' by ' + source.gfm_reference if source}"
-
- create(
- noteable: noteable,
- project: project,
- author: author,
- note: body,
- system: true
- )
- end
-
- # +noteable+ was referenced from +mentioner+, by including GFM in either
- # +mentioner+'s description or an associated Note.
- # Create a system Note associated with +noteable+ with a GFM back-reference
- # to +mentioner+.
- def create_cross_reference_note(noteable, mentioner, author)
- gfm_reference = mentioner_gfm_ref(noteable, mentioner)
-
- note_options = {
- project: noteable.project,
- author: author,
- note: cross_reference_note_content(gfm_reference),
- system: true
- }
-
- if noteable.kind_of?(Commit)
- note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
- else
- note_options.merge!(noteable: noteable)
- end
-
- create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
- end
-
- def create_milestone_change_note(noteable, project, author, milestone)
- body = if milestone.nil?
- 'Milestone removed'
- else
- "Milestone changed to #{milestone.title}"
- end
-
- create(
- noteable: noteable,
- project: project,
- author: author,
- note: body,
- system: true
- )
- end
-
- def create_assignee_change_note(noteable, project, author, assignee)
- body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}"
-
- create({
- noteable: noteable,
- project: project,
- author: author,
- note: body,
- system: true
- })
- end
-
- def create_labels_change_note(noteable, project, author, added_labels, removed_labels)
- labels_count = added_labels.count + removed_labels.count
- added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ')
- removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ')
- message = ''
-
- if added_labels.present?
- message << "added #{added_labels}"
- end
-
- if added_labels.present? && removed_labels.present?
- message << ' and '
- end
-
- if removed_labels.present?
- message << "removed #{removed_labels}"
- end
-
- message << ' ' << 'label'.pluralize(labels_count)
- body = "#{message.capitalize}"
-
- create(
- noteable: noteable,
- project: project,
- author: author,
- note: body,
- system: true
- )
- end
-
- def create_new_commits_note(merge_request, project, author, new_commits, existing_commits = [], oldrev = nil)
- total_count = new_commits.length + existing_commits.length
- commits_text = ActionController::Base.helpers.pluralize(total_count, 'commit')
- body = "Added #{commits_text}:\n\n"
-
- if existing_commits.length > 0
- commit_ids =
- if existing_commits.length == 1
- existing_commits.first.short_id
- else
- if oldrev
- "#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}"
- else
- "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
- end
- end
-
- commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit')
-
- branch =
- if merge_request.for_fork?
- "#{merge_request.target_project_namespace}:#{merge_request.target_branch}"
- else
- merge_request.target_branch
- end
-
- message = "* #{commit_ids} - #{commits_text} from branch `#{branch}`"
- body << message
- body << "\n"
- end
-
- new_commits.each do |commit|
- message = "* #{commit.short_id} - #{commit.title}"
- body << message
- body << "\n"
- end
-
- create(
- noteable: merge_request,
- project: project,
- author: author,
- note: body,
- system: true
- )
+ # TODO (rspeicher): Update usages
+ def create_cross_reference_note(*args)
+ SystemNoteService.cross_reference(*args)
end
def discussions_from_notes(notes)
@@ -227,88 +93,19 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end
- # Determine if cross reference note should be created.
- # eg. mentioning a commit in MR comments which exists inside a MR
- # should not create "mentioned in" note.
- def cross_reference_disallowed?(noteable, mentioner)
- if mentioner.kind_of?(MergeRequest)
- mentioner.commits.map(&:id).include? noteable.id
- end
- end
-
- # Determine whether or not a cross-reference note already exists.
- def cross_reference_exists?(noteable, mentioner)
- gfm_reference = mentioner_gfm_ref(noteable, mentioner, true)
- notes = if noteable.is_a?(Commit)
- where(commit_id: noteable.id, noteable_type: 'Commit')
- else
- where(noteable_id: noteable.id, noteable_type: noteable.class)
- end
-
- notes.where('note like ?', cross_reference_note_pattern(gfm_reference)).
- system.any?
- end
-
def search(query)
where("note like :query", query: "%#{query}%")
end
+ end
- def cross_reference_note_prefix
- 'mentioned in '
- end
-
- private
-
- def cross_reference_note_content(gfm_reference)
- cross_reference_note_prefix + "#{gfm_reference}"
- end
-
- def cross_reference_note_pattern(gfm_reference)
- # Older cross reference notes contained underscores for emphasis
- "%" + cross_reference_note_content(gfm_reference) + "%"
- end
-
- # Prepend the mentioner's namespaced project path to the GFM reference for
- # cross-project references. For same-project references, return the
- # unmodified GFM reference.
- def mentioner_gfm_ref(noteable, mentioner, cross_reference = false)
- 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 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 cross_reference?
+ system && SystemNoteService.cross_reference?(note)
end
def max_attachment_size
current_application_settings.max_attachment_size.megabytes.to_i
end
- def cross_reference?
- note.start_with?(self.class.cross_reference_note_prefix)
- end
-
def find_diff
return nil unless noteable && noteable.diffs.present?
@@ -449,16 +246,6 @@ class Note < ActiveRecord::Base
@discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end
- # Returns true if this is a downvote note,
- # otherwise false is returned
- def downvote?
- votable? && (note.start_with?('-1') ||
- note.start_with?(':-1:') ||
- note.start_with?(':thumbsdown:') ||
- note.start_with?(':thumbs_down_sign:')
- )
- end
-
def for_commit?
noteable_type == "Commit"
end
@@ -500,14 +287,18 @@ class Note < ActiveRecord::Base
nil
end
- # Returns true if this is an upvote note,
- # otherwise false is returned
+ DOWNVOTES = %w(-1 :-1: :thumbsdown: :thumbs_down_sign:)
+
+ # Check if the note is a downvote
+ def downvote?
+ votable? && note.start_with?(*DOWNVOTES)
+ end
+
+ UPVOTES = %w(+1 :+1: :thumbsup: :thumbs_up_sign:)
+
+ # Check if the note is an upvote
def upvote?
- votable? && (note.start_with?('+1') ||
- note.start_with?(':+1:') ||
- note.start_with?(':thumbsup:') ||
- note.start_with?(':thumbs_up_sign:')
- )
+ votable? && note.start_with?(*UPVOTES)
end
def superceded?(notes)
@@ -535,8 +326,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 e866681aab9..3c9f0dad28b 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
@@ -144,7 +145,7 @@ class Project < ActiveRecord::Base
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :avatar_type,
- if: ->(project) { project.avatar && project.avatar_changed? }
+ if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AvatarUploader
@@ -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
@@ -483,7 +493,7 @@ class Project < ActiveRecord::Base
def execute_hooks(data, hooks_scope = :push_hooks)
hooks.send(hooks_scope).each do |hook|
- hook.async_execute(data)
+ hook.async_execute(data, hooks_scope.to_s)
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 949a4d7111b..a9354754686 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -40,6 +40,12 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
+ ci_yaml_file = ci_yaml_file(data)
+
+ if ci_yaml_file
+ data.merge!(ci_yaml_file: ci_yaml_file)
+ end
+
service_hook.execute(data)
end
@@ -123,6 +129,14 @@ class GitlabCiService < CiService
private
+ def ci_yaml_file(data)
+ ref = data[:checkout_sha]
+ repo = project.repository
+ commit = repo.commit(ref)
+ blob = Gitlab::Git::Blob.find(repo, commit.id, ".gitlab-ci.yml")
+ blob && blob.data
+ end
+
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
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_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..1ca97017637 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -370,8 +370,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..3ab9e834c63 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
@@ -50,6 +52,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/tree.rb b/app/models/tree.rb
index f279e896cda..93b3246a668 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -1,11 +1,11 @@
class Tree
- include Gitlab::MarkdownHelper
+ include Gitlab::MarkupHelper
attr_accessor :repository, :sha, :path, :entries
def initialize(repository, sha, path = '/')
path = '/' if path.blank?
-
+
@repository = repository
@sha = sha
@path = path
@@ -20,7 +20,7 @@ class Tree
available_readmes = blobs.select(&:readme?)
if available_readmes.count == 0
- return @readme = nil
+ return @readme = nil
end
# Take the first previewable readme, or the first available readme, if we
diff --git a/app/models/user.rb b/app/models/user.rb
index 1cf7cfea974..596dc7ea33a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -50,6 +50,11 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean
+# otp_backup_codes :text
# public_email :string(255) default(""), not null
#
@@ -57,11 +62,13 @@ 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
@@ -70,8 +77,14 @@ class User < ActiveRecord::Base
default_value_for :hide_no_password, false
default_value_for :theme_id, gitlab_config.default_theme
- devise :database_authenticatable, :lockable, :async,
- :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable
+ devise :two_factor_authenticatable,
+ otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
+
+ devise :two_factor_backupable, otp_number_of_backup_codes: 10
+ serialize :otp_backup_codes, JSON
+
+ devise :lockable, :async, :recoverable, :rememberable, :trackable,
+ :validatable, :omniauthable, :confirmable, :registerable
attr_accessor :force_random_password
@@ -137,7 +150,7 @@ class User < ActiveRecord::Base
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
- validate :avatar_type, if: ->(user) { user.avatar_changed? }
+ validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
@@ -236,6 +249,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
#
@@ -246,6 +271,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
@@ -298,7 +327,7 @@ class User < ActiveRecord::Base
if primary_email_record
primary_email_record.destroy
self.emails.create(email: self.email_was)
-
+
self.update_secondary_emails!
end
end
@@ -438,7 +467,7 @@ class User < ActiveRecord::Base
end
def project_deploy_keys
- DeployKey.in_projects(self.authorized_projects.pluck(:id))
+ DeployKey.unscoped.in_projects(self.authorized_projects.pluck(:id)).distinct(:id)
end
def accessible_deploy_keys
@@ -454,7 +483,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
@@ -626,6 +655,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
@@ -660,4 +695,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..9017a63af3b
--- /dev/null
+++ b/app/services/delete_user_service.rb
@@ -0,0 +1,16 @@
+class DeleteUserService
+ 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..4d02752454e 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -13,5 +13,12 @@ module Files
def repository
project.repository
end
+
+ def after_commit(sha)
+ commit = repository.commit(sha)
+ full_ref = 'refs/heads/' + (params[:new_branch] || ref)
+ old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
+ GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
+ end
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 23833aa78ec..0a80455bc6b 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,7 +1,7 @@
require_relative "base_service"
module Files
- class CreateService < BaseService
+ class CreateService < Files::BaseService
def execute
allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
@@ -33,16 +33,24 @@ module Files
end
end
+ content =
+ if params[:encoding] == 'base64'
+ Base64.decode64(params[:content])
+ else
+ params[:content]
+ end
- new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
- created_successfully = new_file_action.commit!(
- params[:content],
+ sha = repository.commit_file(
+ current_user,
+ file_path,
+ content,
params[:commit_message],
- params[:encoding],
- params[:new_branch]
+ params[:new_branch] || ref
)
- if created_successfully
+
+ if sha
+ after_commit(sha)
success
else
error("Your changes could not be committed, because the file has been changed")
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 1497a0f883b..2281777604c 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,7 +1,7 @@
require_relative "base_service"
module Files
- class DeleteService < BaseService
+ class DeleteService < Files::BaseService
def execute
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
@@ -19,14 +19,15 @@ module Files
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]
+ sha = repository.remove_file(
+ current_user,
+ path,
+ params[:commit_message],
+ ref
)
- if deleted_successfully
+ if sha
+ after_commit(sha)
success
else
error("Your changes could not be committed, because the file has been changed")
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 0724d3ae634..013cc1ee322 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,7 +1,7 @@
require_relative "base_service"
module Files
- class UpdateService < BaseService
+ class UpdateService < Files::BaseService
def execute
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
@@ -19,14 +19,22 @@ module Files
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],
+ content =
+ if params[:encoding] == 'base64'
+ Base64.decode64(params[:content])
+ else
+ params[:content]
+ end
+
+ sha = repository.commit_file(
+ current_user,
+ path,
+ content,
params[:commit_message],
- params[:encoding],
- params[:new_branch]
+ params[:new_branch] || ref
)
+ after_commit(sha)
success
rescue Gitlab::Satellite::CheckoutFailed => ex
error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index bdf36af02fd..cde65349d5c 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -127,7 +127,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 5e1906ad2ae..1d99223cfe6 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -2,17 +2,28 @@ class IssuableBaseService < BaseService
private
def create_assignee_note(issuable)
- Note.create_assignee_change_note(
+ SystemNoteService.change_assignee(
issuable, issuable.project, current_user, issuable.assignee)
end
def create_milestone_note(issuable)
- Note.create_milestone_change_note(
+ SystemNoteService.change_milestone(
issuable, issuable.project, current_user, issuable.milestone)
end
def create_labels_note(issuable, added_labels, removed_labels)
- Note.create_labels_change_note(
+ 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
end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index f670019cc63..138465859ce 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -14,7 +14,7 @@ module Issues
private
def create_note(issue, current_commit)
- Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
+ SystemNoteService.change_status(issue, issue.project, current_user, issue.state, current_commit)
end
end
end
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 1e5c398516d..e48ca359f4f 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -14,7 +14,7 @@ module Issues
private
def create_note(issue)
- Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
+ SystemNoteService.change_status(issue, issue.project, current_user, issue.state, nil)
end
end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 8f04a69287a..6af942a5ca4 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -37,6 +37,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/base_service.rb b/app/services/merge_requests/base_service.rb
index f6e1ae6f283..e455fe95791 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -2,7 +2,7 @@ module MergeRequests
class BaseService < ::IssuableBaseService
def create_note(merge_request)
- Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
+ SystemNoteService.change_status(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def hook_data(merge_request, action)
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e9b526d1fb7..d0648da049b 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -10,6 +10,7 @@ module MergeRequests
close_merge_requests
reload_merge_requests
+ execute_mr_web_hooks
comment_mr_with_commits
true
@@ -82,8 +83,23 @@ module MergeRequests
mr_commit_ids.include?(commit.id)
end
- Note.create_new_commits_note(merge_request, merge_request.project,
- @current_user, new_commits, existing_commits, @oldrev)
+ SystemNoteService.add_commits(merge_request, merge_request.project,
+ @current_user, new_commits,
+ existing_commits, @oldrev)
+ end
+ end
+
+ # Call merge request webhook with update branches
+ def execute_mr_web_hooks
+ merge_requests = @project.origin_merge_requests.opened
+ .where(source_branch: @branch_name)
+ .to_a
+ merge_requests += @fork_merge_requests.where(source_branch: @branch_name)
+ .to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+ execute_hooks(merge_request, 'update')
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 23af2656c37..4f6c6cba9a9 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]
@@ -41,6 +42,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 +57,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 d19a6c2eca3..0ff37c41743 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -31,7 +31,7 @@ module Notes
def execute_hooks(note)
note_data = hook_data(note)
- # TODO: Support Webhooks
+ note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 0d7ffbeebd9..312b56eb87b 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -91,10 +91,14 @@ class NotificationService
# * project team members with notification level higher then Participating
#
def merge_mr(merge_request, current_user)
- recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
+ recipients = [merge_request.author, merge_request.assignee]
+
+ recipients = add_project_watchers(recipients, merge_request.target_project)
+ recipients = reject_muted_users(recipients, merge_request.target_project)
+
recipients = add_subscribed_users(recipients, merge_request)
recipients = reject_unsubscribed_users(recipients, merge_request)
- recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
+
recipients.delete(current_user)
recipients.each do |recipient|
@@ -137,20 +141,17 @@ class NotificationService
recipients = recipients.concat(participants)
# Merge project watchers
- recipients = recipients.concat(project_watchers(note.project)).compact.uniq
+ recipients = add_project_watchers(recipients, note.project)
# Reject users with Mention notification level, except those mentioned in _this_ note.
recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
recipients = recipients + note.mentioned_users
- # Reject mutes users
recipients = reject_muted_users(recipients, note.project)
recipients = add_subscribed_users(recipients, note.noteable)
-
recipients = reject_unsubscribed_users(recipients, note.noteable)
- # Reject author
recipients.delete(note.author)
# build notify method like 'note_commit_email'
@@ -287,6 +288,10 @@ class NotificationService
users
end
+ def add_project_watchers(recipients, project)
+ recipients.concat(project_watchers(project)).compact.uniq
+ end
+
# Remove users with disabled notifications from array
# Also remove duplications and nil recipients
def reject_muted_users(users, project = nil)
@@ -403,11 +408,13 @@ class NotificationService
[target.author, target.assignee]
end
- recipients = reject_muted_users(recipients, project)
+ recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project)
+ recipients = reject_muted_users(recipients, project)
+
recipients = add_subscribed_users(recipients, target)
- recipients = recipients.concat(project_watchers(project)).uniq
recipients = reject_unsubscribed_users(recipients, target)
+
recipients
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/system_hooks_service.rb b/app/services/system_hooks_service.rb
index c5d0b08845b..60235b6be2a 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -7,12 +7,12 @@ class SystemHooksService
def execute_hooks(data)
SystemHook.all.each do |sh|
- async_execute_hook sh, data
+ async_execute_hook(sh, data, 'system_hooks')
end
end
- def async_execute_hook(hook, data)
- Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data)
+ def async_execute_hook(hook, data, hook_name)
+ Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
end
def build_event_data(model, event)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
new file mode 100644
index 00000000000..b6801a92330
--- /dev/null
+++ b/app/services/system_note_service.rb
@@ -0,0 +1,317 @@
+# SystemNoteService
+#
+# Used for creating system notes (e.g., when a user references a merge request
+# from an issue, an issue's assignee changes, an issue is closed, etc.)
+class SystemNoteService
+ # Called when commits are added to a Merge Request
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # 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 - Optional String SHA of a previous Commit
+ #
+ # See new_commit_summary and existing_commit_summary.
+ #
+ # Returns the created Note object
+ def self.add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil)
+ total_count = new_commits.length + existing_commits.length
+ commits_text = "#{total_count} commit".pluralize(total_count)
+
+ body = "Added #{commits_text}:\n\n"
+ body << existing_commit_summary(noteable, existing_commits, oldrev)
+ body << new_commit_summary(new_commits).join("\n")
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when the assignee of a Noteable is changed or removed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # assignee - User being assigned, or nil
+ #
+ # Example Note text:
+ #
+ # "Assignee removed"
+ #
+ # "Reassigned to @rspeicher"
+ #
+ # Returns the created Note object
+ def self.change_assignee(noteable, project, author, assignee)
+ body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when one or more labels on a Noteable are added and/or removed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # added_labels - Array of Labels added
+ # removed_labels - Array of Labels removed
+ #
+ # Example Note text:
+ #
+ # "Added ~1 and removed ~2 ~3 labels"
+ #
+ # "Added ~4 label"
+ #
+ # "Removed ~5 label"
+ #
+ # Returns the created Note object
+ def self.change_label(noteable, project, author, added_labels, removed_labels)
+ labels_count = added_labels.count + removed_labels.count
+
+ references = ->(label) { "~#{label.id}" }
+ added_labels = added_labels.map(&references).join(' ')
+ removed_labels = removed_labels.map(&references).join(' ')
+
+ body = ''
+
+ if added_labels.present?
+ body << "added #{added_labels}"
+ body << ' and ' if removed_labels.present?
+ end
+
+ if removed_labels.present?
+ body << "removed #{removed_labels}"
+ end
+
+ body << ' ' << 'label'.pluralize(labels_count)
+ body = "#{body.capitalize}"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when the milestone of a Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # milestone - Milestone being assigned, or nil
+ #
+ # Example Note text:
+ #
+ # "Milestone removed"
+ #
+ # "Miletone changed to 7.11"
+ #
+ # Returns the created Note object
+ def self.change_milestone(noteable, project, author, milestone)
+ body = 'Milestone '
+ body += milestone.nil? ? 'removed' : "changed to #{milestone.title}"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when the status of a Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # status - String status
+ # source - Mentionable performing the change, or nil
+ #
+ # Example Note text:
+ #
+ # "Status changed to merged"
+ #
+ # "Status changed to closed by bc17db76"
+ #
+ # Returns the created Note object
+ def self.change_status(noteable, project, author, status, source)
+ body = "Status changed to #{status}"
+ body += " by #{source.gfm_reference}" if source
+
+ 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
+ # mentioner - Mentionable object
+ # author - User performing the reference
+ #
+ # Example Note text:
+ #
+ # "mentioned in #1"
+ #
+ # "mentioned in !2"
+ #
+ # "mentioned in 54f7727c"
+ #
+ # See cross_reference_note_content.
+ #
+ # Returns the created Note object
+ def self.cross_reference(noteable, mentioner, author)
+ return if cross_reference_disallowed?(noteable, mentioner)
+
+ gfm_reference = mentioner.gfm_reference(noteable.project)
+
+ note_options = {
+ project: noteable.project,
+ author: author,
+ note: cross_reference_note_content(gfm_reference)
+ }
+
+ if noteable.kind_of?(Commit)
+ note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
+ else
+ note_options.merge!(noteable: noteable)
+ end
+
+ create_note(note_options)
+ end
+
+ def self.cross_reference?(note_text)
+ note_text.start_with?(cross_reference_note_prefix)
+ end
+
+ # Check if a cross-reference is disallowed
+ #
+ # This method prevents adding a "mentioned in !1" note on every single commit
+ # in a merge request.
+ #
+ # noteable - Noteable object being referenced
+ # mentioner - Mentionable object
+ #
+ # Returns Boolean
+ def self.cross_reference_disallowed?(noteable, mentioner)
+ 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)
+
+ if noteable.is_a?(Commit)
+ # Commits have non-integer IDs, so they're stored in `commit_id`
+ notes = notes.where(commit_id: noteable.id)
+ else
+ notes = notes.where(noteable_id: noteable.id)
+ end
+
+ gfm_reference = mentioner.gfm_reference(noteable.project)
+ notes = notes.where(note: cross_reference_note_content(gfm_reference))
+
+ notes.count > 0
+ end
+
+ private
+
+ def self.create_note(args = {})
+ Note.create(args.merge(system: true))
+ end
+
+ def self.cross_reference_note_prefix
+ 'mentioned in '
+ end
+
+ def self.cross_reference_note_content(gfm_reference)
+ "#{cross_reference_note_prefix}#{gfm_reference}"
+ end
+
+ # Build an Array of lines detailing each commit added in a merge request
+ #
+ # new_commits - Array of new Commit objects
+ #
+ # Returns an Array of Strings
+ def self.new_commit_summary(new_commits)
+ new_commits.collect do |commit|
+ "* #{commit.short_id} - #{commit.title}"
+ end
+ end
+
+ # Build a single line summarizing existing commits being added in a merge
+ # request
+ #
+ # noteable - MergeRequest object
+ # existing_commits - Array of existing Commit objects
+ # oldrev - Optional String SHA of a previous Commit
+ #
+ # Examples:
+ #
+ # "* ea0f8418...2f4426b7 - 24 commits from branch `master`"
+ #
+ # "* ea0f8418..4188f0ea - 15 commits from branch `fork:master`"
+ #
+ # "* ea0f8418 - 1 commit from branch `feature`"
+ #
+ # Returns a newline-terminated String
+ def self.existing_commit_summary(noteable, existing_commits, oldrev = nil)
+ return '' if existing_commits.empty?
+
+ count = existing_commits.size
+
+ commit_ids = if count == 1
+ existing_commits.first.short_id
+ else
+ if oldrev
+ "#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}"
+ else
+ "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
+ end
+ end
+
+ commits_text = "#{count} commit".pluralize(count)
+
+ branch = noteable.target_branch
+ branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
+
+ "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
+ end
+end
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
index 21ec2c01cb8..e85e58751e7 100644
--- a/app/services/test_hook_service.rb
+++ b/app/services/test_hook_service.rb
@@ -1,6 +1,6 @@
class TestHookService
def execute(hook, current_user)
data = Gitlab::PushDataBuilder.build_sample(hook.project, current_user)
- hook.execute(data)
+ hook.execute(data, 'push_hooks')
end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index b9d7e8b4586..188a08940ab 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -30,8 +30,14 @@
.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
@@ -64,6 +70,11 @@
= 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
@@ -77,6 +88,13 @@
.col-sm-10
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
.help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+ .form_group
+ = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
+ .col-sm-10
+ .checkbox
+ = f.label :user_oauth_applications do
+ = f.check_box :user_oauth_applications
+ Allow users to register any application to use GitLab as an OAuth provider
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index d1c586328a2..3732ff847b9 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -56,7 +56,12 @@
%span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled
.col-md-4
- %h4 Components
+ %h4
+ Components
+ - if current_application_settings.version_check_enabled
+ .pull-right
+ = version_status_badge
+
%hr
%p
GitLab
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/users/index.html.haml b/app/views/admin/users/index.html.haml
index fe648470233..45dee86b017 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -79,11 +79,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/show.html.haml b/app/views/admin/users/show.html.haml
index 7fc85206109..f7195ac3326 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -140,18 +140,22 @@
.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.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?
- %li
- Next groups with all content will be removed:
+ %p
+ This user is currently an owner in these groups:
%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"
+ %p
+ You must transfer ownership or delete these groups before you can delete this user.
#profile.tab-pane
.row
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 5ecd53cff84..cfb386e131f 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -23,10 +23,9 @@
%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"
= link_to group, class: 'group-name' do
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 6e88fc9be40..07bda1c77f8 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
- xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index dfdf0d68c8f..0dd2edbb1bc 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..61d2fbe538c 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/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index 71edb73cd8a..e9a612231d5 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Activity"
- xml.link href: dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id dashboard_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index e8820daf58f..29ffe8a8be3 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -6,7 +6,7 @@
.devise-errors
= devise_error_messages!
.clearfix.append-bottom-20
- = f.email_field :email, placeholder: "Email", class: "form-control", required: true
+ = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email]
.clearfix
= f.submit "Reset password", class: "btn-primary btn"
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
new file mode 100644
index 00000000000..22b2c1a186b
--- /dev/null
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -0,0 +1,10 @@
+%div
+ .login-box
+ .login-heading
+ %h3 Two-factor Authentication
+ .login-body
+ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor authentication code', required: true, autofocus: true
+ %p.help-block.hint If you've lost your phone, you may enter one of your recovery codes.
+ .prepend-top-20
+ = f.submit "Verify code", class: "btn btn-save"
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 8dce0b16936..f8ba9d80ae8 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -5,6 +5,6 @@
- providers.each do |provider|
%span.light
- if default_providers.include?(provider)
- = link_to oauth_image_tag(provider), omniauth_authorize_path(resource_name, provider), class: 'oauth-image-link'
+ = link_to oauth_image_tag(provider), omniauth_authorize_path(resource_name, provider), method: :post, class: 'oauth-image-link'
- else
- = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn", "data-no-turbolink" => "true"
+ = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), method: :post, class: "btn", "data-no-turbolink" => "true"
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index c86ce9ae651..742b74a67c7 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
&nbsp;
- = gfm event_commit_title(commit[:message]), project
+ = gfm event_commit_title(commit[:message]), project: project
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml
index 0edb61ea246..4259f64c191 100644
--- a/app/views/events/_event_issue.atom.haml
+++ b/app/views/events/_event_issue.atom.haml
@@ -1,3 +1,3 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present?
- = markdown(issue.description, xhtml: true)
+ = markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml
index 1a8b62abeab..e8ed13df783 100644
--- a/app/views/events/_event_merge_request.atom.haml
+++ b/app/views/events/_event_merge_request.atom.haml
@@ -1,3 +1,3 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present?
- = markdown(merge_request.description, xhtml: true)
+ = markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml
index b49c331ccf2..cfbfba50202 100644
--- a/app/views/events/_event_note.atom.haml
+++ b/app/views/events/_event_note.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(note.note, xhtml: true)
+ = markdown(note.note, xhtml: true, reference_only_path: false, project: note.project)
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 5d14def8f75..3625cb49d8b 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]), xhtml: true)
+ %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project)
- if event.commits_count > 15
%p
%i
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 4ef18c09060..07bec1697f5 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -14,7 +14,7 @@
.event-note
.md
%i.fa.fa-comment-o.event-note-icon
- = event_note(event.target.note)
+ = event_note(event.target.note, project: event.project)
- note = event.target
- if note.attachment.url
- if note.attachment.image?
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 60d7978b13f..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
@@ -17,15 +17,27 @@
- few_commits.each do |commit|
= render "events/commit", commit: commit, project: project
+ - create_mr = current_user == event.author && event.new_ref? && create_mr_button?(event.project.default_branch, event.ref_name, event.project)
- if event.commits_count > 1
%li.commits-stat
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
+
- if event.md_ref?
- from = event.commit_from
- from_label = truncate_sha(from)
- else
- from = event.project.default_branch
- from_label = from
+
= link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do
- %strong Compare &rarr; #{from_label}...#{truncate_sha(event.commit_to)}
+ Compare #{from_label}...#{truncate_sha(event.commit_to)}
+
+ - if create_mr
+ or
+ = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do
+ create a merge request
+ - elsif create_mr
+ %li.commits-stat
+ = link_to create_mr_path(event.project.default_branch, event.ref_name, event.project) do
+ Create Merge Request
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 56b1948a474..ec39a755f0f 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -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/issues.html.haml b/app/views/groups/issues.html.haml
index 6a3da6adacf..e0756e909be 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..3d9e857cc52 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/show.atom.builder b/app/views/groups/show.atom.builder
index b52e78faaa3..a91d1a6e94b 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@group.name} activity"
- xml.link href: group_url(@group, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 1678311141e..0687840af39 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'
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index af39dfeac5b..bf4b7234b21 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -3,6 +3,8 @@
GitLab
%span= Gitlab::VERSION
%small= Gitlab::REVISION
+ - if current_application_settings.version_check_enabled
+ = version_status_badge
%p.slead
GitLab is open source software to collaborate on code.
%br
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..c1283734d25 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -7,6 +7,15 @@
= render 'layouts/nav/dashboard'
.collapse-nav
= render partial: 'layouts/collapse_button'
+ - if current_user
+ .sidebar-user
+ = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'top'} do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32'
+ .username
+ = current_user.username
+ .logout-holder
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'top'} do
+ = icon('sign-out')
.content-wrapper
.container-fluid
.content
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..155825cc4c2 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -3,8 +3,8 @@
= render "layouts/head"
%body{class: "#{app_theme}", :'data-page' => body_data_page}
- 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
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 5a59c9fd59a..d406f5764a7 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -2,7 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body.ui_mars.login-page.application
- = render "layouts/empty_head_panel"
+ = 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..2e3a2b16eb7 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -2,7 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{app_theme} application"}
- = render "layouts/head_panel", title: "" if current_user
+ = 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..2970af377f5
--- /dev/null
+++ b/app/views/layouts/header/_default.html.haml
@@ -0,0 +1,43 @@
+%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
+ %h3 GitLab
+ .header-content
+ %h1.title
+ = title
+
+ %button.navbar-toggle
+ %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
+ = 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
+ = 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')
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/header/_empty.html.haml
index 358caa3868b..16fbf6d4020 100644
--- a/app/views/layouts/_empty_head_panel.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-empty
.container
%h4.center
= image_tag 'logo-white.png', width: 32, height: 32
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
new file mode 100644
index 00000000000..6a031722aaa
--- /dev/null
+++ b/app/views/layouts/header/_public.html.haml
@@ -0,0 +1,14 @@
+%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
+ %h3 GitLab
+ .header-content
+ %h1.title= title
+
+ - 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'
+
+= render 'shared/outdated_browser'
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 31d8ed3ed86..ac37fd4c1c1 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -4,7 +4,7 @@
= icon('user fw')
%span
Profile
- = nav_link(controller: :accounts) do
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= icon('gear fw')
%span
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 21260302a09..7dd14449def 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -12,7 +12,7 @@
= link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do
= icon('pencil-square-o')
%span
- Project
+ Project 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')
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/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/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index ebbe98dd472..4feacdaacff 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -11,4 +11,6 @@
- if @user.created_by_id
%p
- = link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token)
+ = link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token)
+ %p
+ = reset_token_expire_message
diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb
index 96b26879a77..dd9b71e3b84 100644
--- a/app/views/notify/new_user_email.text.erb
+++ b/app/views/notify/new_user_email.text.erb
@@ -5,4 +5,6 @@ The Administrator created an account for you. Now you are a member of the compan
login.................. <%= @user.email %>
<% if @user.created_by_id %>
<%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %>
+
+ <%= reset_token_expire_message %>
<% end %>
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 1c3a3d68aca..a26d4e0c757 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,58 +26,91 @@
- 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?
+ .panel.panel-default
+ .panel-heading
+ Two-factor Authentication
+ .panel-body
+ - if current_user.otp_required_for_login
+ .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
+ = succeed ',' do
+ = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
+ invalidating all previous codes.
+
+ - else
+ %p
+ Increase your account's security by enabling two-factor authentication (2FA).
+ %p
+ Each time you log in you’ll be required to provide your username and
+ password as usual, plus a randomly-generated code from your phone.
+ %div
+ = 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),
- class: "btn btn-lg #{'active' if oauth_active?(provider)}"
- - if oauth_active?(provider)
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
- %i.fa.fa-close
+ .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
- %i.fa.fa-spinner.fa-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 yur 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
index af284f60409..f450ec1c018 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -1,54 +1,56 @@
- page_title "Design"
%h3.page-title
- Design Settings
+ = page_title
%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
+ .panel.panel-default.application-theme
+ .panel-heading
Application theme
- .themes_opts
- = label_tag do
- .prev.default
- = f.radio_button :theme_id, 1
- Graphite
+ .panel-body
+ .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.classic
+ = f.radio_button :theme_id, 2
+ Charcoal
- = label_tag do
- .prev.modern
- = f.radio_button :theme_id, 3
- Green
+ = 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.gray
+ = f.radio_button :theme_id, 4
+ Gray
- = label_tag do
- .prev.violet
- = f.radio_button :theme_id, 5
- Violet
+ = 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
+ = label_tag do
+ .prev.blue
+ = f.radio_button :theme_id, 6
+ Blue
+ %br
+ .clearfix
- %fieldset.code-preview-theme
- %legend
+ .panel.panel-default.code-preview-theme
+ .panel-heading
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
+ .panel-body
+ .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/show.html.haml b/app/views/profiles/show.html.haml
index 29c30905117..6534afb0e89 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.
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
new file mode 100644
index 00000000000..1b1395eaa17
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -0,0 +1,11 @@
+%p.slead
+ Should you ever lose your phone, each of these recovery codes can be used one
+ time each to regain access to your account. Please save them in a safe place.
+
+.codes.well
+ %ul
+ - @codes.each do |code|
+ %li
+ %span.monospace= code
+
+= link_to 'Proceed', profile_account_path, class: 'btn btn-success'
diff --git a/app/views/profiles/two_factor_auths/codes.html.haml b/app/views/profiles/two_factor_auths/codes.html.haml
new file mode 100644
index 00000000000..addf356697a
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/codes.html.haml
@@ -0,0 +1,5 @@
+- page_title 'Recovery Codes', 'Two-factor Authentication'
+
+%h3.page-title Two-factor Authentication Recovery codes
+%hr
+= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml
new file mode 100644
index 00000000000..e330aadac13
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/create.html.haml
@@ -0,0 +1,6 @@
+- page_title 'Two-factor Authentication', 'Account'
+
+.alert.alert-success
+ Congratulations! You have enabled Two-factor Authentication!
+
+= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
new file mode 100644
index 00000000000..b9f3e2380fe
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -0,0 +1,39 @@
+- page_title 'Two-factor Authentication', 'Account'
+
+%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.
+
+%hr
+
+= 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-2
+ = raw @qr_code
+ .col-sm-8.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
+ = 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/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
index 1865b5be8c6..c9c17110d2b 100644
--- a/app/views/projects/_aside.html.haml
+++ b/app/views/projects/_aside.html.haml
@@ -1,83 +1,108 @@
.clearfix
- .append-bottom-20
- = render "shared/clone_panel"
-
- unless @project.empty_repo?
- .well
- %h4 Repository
- %ul.nav.nav-pills
- %li= link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref)
- %li= link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), namespace_project_branches_path(@project.namespace, @project)
- %li= link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), namespace_project_tags_path(@project.namespace, @project)
+ .panel.panel-default
+ .panel-heading
+ = visibility_level_icon(@project.visibility_level)
+ = "#{visibility_level_label(@project.visibility_level).capitalize} project"
+
+ .panel-body
+ - if @repository.changelog || @repository.license || @repository.contribution_guide
+ %ul.nav.nav-pills
+ - if @repository.changelog
+ %li.hidden-xs
+ = link_to changelog_url(@project) do
+ Changelog
+ - if @repository.license
+ %li
+ = link_to license_url(@project) do
+ License
+ - if @repository.contribution_guide
+ %li
+ = link_to contribution_guide_url(@project) do
+ Contribution guide
+
+ .actions
+ - if can? current_user, :write_issue, @project
+ = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do
+ = icon("exclamation-circle fw")
+ New Issue
- .actions
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm' do
- %i.fa.fa-exchange
- Compare code
+ - if can? current_user, :write_merge_request, @project
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
+ = icon("plus fw")
+ New Merge Request
+
+ - if forked_from_project = @project.forked_from_project
+ .panel-footer
+ = icon("code-fork fw")
+ Forked from
+ .pull-right
+ = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project)
+
+
+ - @project.ci_services.each do |ci_service|
+ - if ci_service.active? && ci_service.respond_to?(:builds_path)
+ .panel-footer
+ = icon("check fw")
+ = ci_service.title
+ .pull-right
+ - if ci_service.respond_to?(:status_img_path)
+ = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
+ = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image'
+ - else
+ = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
- - if can?(current_user, :download_code, @project)
- &nbsp;
- = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm'
- unless @project.empty_repo?
- .well
- %h4 Contribute
- %ul.nav.nav-pills
- - if @repository.changelog
- %li.hidden-xs
- = link_to changelog_url(@project) do
- Changelog
- - if @repository.contribution_guide
- %li.hidden-xs
- = link_to contribution_guide_url(@project) do
- Contribution guide
- - if @repository.license
+ .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 license_url(@project) do
- License
- .actions
- = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm' do
- %i.fa.fa-fw.fa-exclamation-circle
- New issue
- - if can? current_user, :write_merge_request, @project
- &nbsp;
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
+ %li
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
+
+ .actions
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do
+ %i.fa.fa-exchange
+ Compare code
+ - if can?(current_user, :download_code, @project)
+ = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm'
+ - if version = @repository.version
+ .panel-footer
+ = icon("clock-o fw")
+ Version
+ .pull-right
+ = link_to version_url(@project) do
+ = @repository.blob_by_oid(version.id).data
+ = render "shared/clone_panel"
- if @project.archived?
+ %br
.alert.alert-warning
%h4
- %i.fa.fa-exclamation-triangle
+ = icon("exclamation-triangle fw")
Archived project!
%p Repository is read-only
- - if @project.forked_from_project
- .well
- %h4
- Forked from
- .pull-right
- = link_to @project.forked_from_project.namespace.try(:name), project_path(@project.forked_from_project)
-
-
-- if version = @repository.version
- .well
- %h4
- 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
- = 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"
- - else
- = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
+ - 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/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
deleted file mode 100644
index d623a3716ed..00000000000
--- a/app/views/projects/_dropdown.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-- if current_user
- .dropdown.pull-right
- %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-bars
- %ul.dropdown-menu
- - if @project.issues_enabled && can?(current_user, :write_issue, @project)
- %li
- = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do
- %i.fa.fa-fw.fa-exclamation-circle
- New issue
- - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
- %li
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
- %i.fa.fa-fw.fa-tasks
- New merge request
- - if @project.snippets_enabled && can?(current_user, :write_snippet, @project)
- %li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
- %i.fa.fa-fw.fa-file-text-o
- New snippet
- - if can?(current_user, :admin_project_member, @project)
- %li
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
- %i.fa.fa-fw.fa-users
- New project member
- - if can? current_user, :push_code, @project
- %li.divider
- %li
- = link_to new_namespace_project_branch_path(@project.namespace, @project) do
- %i.fa.fa-fw.fa-code-fork
- New branch
- %li
- = link_to new_namespace_project_tag_path(@project.namespace, @project) do
- %i.fa.fa-fw.fa-tag
- New tag
-
-
diff --git a/app/views/projects/_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
index e321a84974e..2c5b24b8130 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -11,6 +11,15 @@
.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
@@ -37,7 +46,7 @@
.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)
+ selected: issuable.assignee_id, project: @target_project || @project)
&nbsp;
= link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
.form-group
@@ -70,6 +79,24 @@
- if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+- if issuable.is_a?(MergeRequest)
+ %hr
+ - unless @merge_request.persisted?
+ .form-group
+ = f.label :source_branch, class: 'control-label' do
+ %i.fa.fa-code-fork
+ Source Branch
+ .col-sm-10
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ %p.help-block
+ = link_to 'Change source branch', mr_change_branches_path(@merge_request)
+ .form-group
+ = f.label :target_branch, class: 'control-label' do
+ %i.fa.fa-code-fork
+ Target Branch
+ .col-sm-10
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' })
+
.form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
%p
diff --git a/app/views/projects/_section.html.haml b/app/views/projects/_section.html.haml
index 0b7f4cb780a..f4f876f3809 100644
--- a/app/views/projects/_section.html.haml
+++ b/app/views/projects/_section.html.haml
@@ -1,10 +1,12 @@
%ul.nav.nav-tabs
%li.active
= link_to '#tab-activity', 'data-toggle' => 'tab' do
+ = icon("tachometer")
Activity
- if @repository.readme
%li
= link_to '#tab-readme', 'data-toggle' => 'tab' do
+ = icon("file-text-o")
Readme
.tab-content
.tab-pane.active#tab-activity
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/_text.html.haml b/app/views/projects/blob/_text.html.haml
index f6bd62f239b..4429c395aee 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,8 +1,4 @@
-- if gitlab_markdown?(blob.name)
- .file-content.wiki
- = preserve do
- = markdown(blob.data)
-- elsif markup?(blob.name)
+- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
- else
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 5c79d0ef11f..84742608986 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -2,7 +2,7 @@
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
= render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.since, line_new: @form.since, bottom: false}
+ line_old: @form.since, line_new: @form.since, bottom: false, new_file: false}
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
@@ -16,4 +16,4 @@
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
= render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.to, line_new: @form.to, bottom: true}
+ line_old: @form.to, line_new: @form.to, bottom: true, new_file: false}
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 4e7415be4aa..43412624da6 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -10,16 +10,19 @@
%i.fa.fa-lock
protected
.pull-right
- - if can?(current_user, :download_code, @project)
- = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-xs'
+ - if create_mr_button?(@repository.root_ref, branch.name)
+ = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
+ = icon('plus')
+ Merge Request
+
- if branch.name != @repository.root_ref
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-xs', method: :post, title: "Compare" do
- %i.fa.fa-files-o
+ = icon("exchange")
Compare
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
- %i.fa.fa-trash-o
+ = icon("trash-o")
- if commit
%ul.list-unstyled
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index a714f5f79e0..e3d8cd0fdd5 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,17 +1,22 @@
%ul.nav.nav-tabs
= nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, @repository.root_ref) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
+ = icon("history")
Commits
- %span.badge= number_with_precision(@repository.commit_count, precision: 0, delimiter: ',')
+ %span.badge= number_with_delimiter(@repository.commit_count)
= nav_link(controller: :compare) do
- = link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref)
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) do
+ = icon("exchange")
+ Compare
= nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do
+ = icon("code-fork")
Branches
%span.badge.js-totalbranch-count= @repository.branches.size
= nav_link(controller: :tags) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
+ = icon("tags")
Tags
%span.badge.js-totaltags-count= @repository.tags.length
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 01edd9447ce..3854ad5d611 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name}:#{@ref} commits"
- xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html"
xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any?
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c8531b090a6..9682100a54c 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -8,11 +8,17 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
-- if current_user && current_user.private_token
- .commits-feed-holder.hidden-xs.hidden-sm
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
- %i.fa.fa-rss
- Commits feed
+.commits-feed-holder.hidden-xs.hidden-sm
+ - if create_mr_button?(@repository.root_ref, @ref)
+ = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
+ = icon('plus')
+ Create Merge Request
+
+ - if current_user && current_user.private_token
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'prepend-left-10 btn' do
+ = icon("rss")
+ Commits Feed
+
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index dfb1dded9ea..a0e904cfd8b 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -13,9 +13,10 @@
= text_field_tag :to, params[:to], class: "form-control"
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- - if compare_to_mr_button?
- = link_to compare_mr_path, class: 'prepend-left-10 btn' do
- %strong Make a merge request
+ - if create_mr_button?
+ = link_to create_mr_path, class: 'prepend-left-10 btn' do
+ = icon("plus")
+ Create Merge Request
:javascript
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/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml
index 4ebe3379733..d1f897b99f7 100644
--- a/app/views/projects/diffs/_match_line.html.haml
+++ b/app/views/projects/diffs/_match_line.html.haml
@@ -1,7 +1,7 @@
-%td.old_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_old},
- class: unfold_bottom_class(bottom)}
+%td.old_line.diff-line-num{data: {linenumber: line_old},
+ class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
\...
-%td.new_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_new},
- class: unfold_bottom_class(bottom)}
+%td.new_line.diff-line-num{data: {linenumber: line_new},
+ class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
\...
%td.line_content.matched= line
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index e6dfbfd6511..a6373181b45 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -12,7 +12,7 @@
%tr.line_holder{ id: line_code, class: "#{type}" }
- if type == "match"
= render "projects/diffs/match_line", {line: line.text,
- line_old: line_old, line_new: line.new_pos, bottom: false}
+ line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
- else
%td.old_line
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
@@ -29,7 +29,7 @@
- if last_line > 0
= render "projects/diffs/match_line", {line: "",
- line_old: last_line, line_new: last_line, bottom: true}
+ line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file}
- if diff_file.diff.blank? && diff_file.mode_changed?
.file-mode-changed
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 808c03148f4..eadbf61fdd4 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -35,6 +35,13 @@
%p.light
This url will be triggered when a new tag is pushed to the repository
%div
+ = f.check_box :note_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :note_events, class: 'list-label' do
+ %strong Comments
+ %p.light
+ This url will be triggered when someone adds a comment
+ %div
= f.check_box :issues_events, class: 'pull-left'
.prepend-left-20
= f.label :issues_events, class: 'list-label' do
@@ -64,6 +71,6 @@
.clearfix
%span.monospace= hook.url
%p
- - %w(push_events tag_push_events issues_events merge_requests_events).each do |trigger|
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger|
- if hook.send(trigger)
%span.label.label-gray= trigger.titleize
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 2016f5c709c..48858fa32da 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -30,5 +30,4 @@
%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/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index ef36d1f9547..a4e25e5ce88 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -8,8 +8,7 @@
= 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)
+ = link_to_label(label, project: issue.project)
.pull-right.light
- if issue.closed?
%span
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 5fa8fbdf893..dc8e477185b 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} issues"
- xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 709ea1f7897..1d5597602d1 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -14,11 +14,11 @@
= 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
+ = 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/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..eb3dba6858d 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -27,5 +27,4 @@
%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/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 5d5a23b5409..65f5c3d6a19 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,17 +4,16 @@
= 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
%i.fa.fa-check
- MERGED
+ ACCEPTED
- elsif merge_request.closed?
%span
- %i.fa.fa-close
- CLOSED
+ %i.fa.fa-ban
+ REJECTED
- else
%span.hidden-xs.hidden-sm
%span.label-branch<
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 24a9563dd4d..9a2edbf0a8c 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -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){
@@ -55,6 +56,8 @@
: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..74f8b9950cf 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -22,44 +22,44 @@
%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"
- 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
+ = 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
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ab845a7e719..841d1e1cfe9 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -7,6 +7,6 @@
= 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'
+ = 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
index a5a821c1847..1d0e2e350b0 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -9,7 +9,7 @@
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, null_user: true)
+ = 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
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index cb536214c69..906cc11dc67 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -1,14 +1,17 @@
- unless @allowed_to_merge
- if @project.archived?
%p
- %strong Archived projects cannot be committed to!
+ %strong Archived projects do not provide commit access.
- 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.
+ %strong This merge request contains merge conflicts that must be resolved.
+ Only those with write access to this repository can merge merge requests.
.automerge_widget.work_in_progress.hide
- %strong This request can't be merged automatically because it is marked a Work In Progress. Even if it could be merged, you don't have permission to do so.
+ %strong This merge request is marked as Work In Progress.
+ Only those with write access to this repository can merge merge requests.
.automerge_widget.can_be_merged.hide
- %strong This request can be merged automatically, but you don't have permission to do so.
+ %strong This request can be merged automatically.
+ Only those with write access to this repository can merge merge requests.
- if @show_merge_controls
@@ -34,7 +37,7 @@
%br
.light
- If you still want to merge this request manually - use
+ 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"
@@ -42,47 +45,45 @@
.automerge_widget.no_satellite.hide
%p
%span
- %strong This repository does not have satellite. Ask an administrator to fix this issue
+ %strong This repository does not have a satellite. Please 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
+ This pull request contains merge conflicts that must be resolved.
+ You can try it manually on the
%strong
- = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do
- command line
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
%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.
+ This happens when Git is not able to automatically resolve conflicts between branches.
.automerge_widget.work_in_progress.hide
%h4
- This request can't be merged because it is marked a <strong>Work In Progress</strong>.
+ This request cannot be merged because it is marked as <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 merged.
+ When the merge request is ready, remove the "WIP" prefix from the title to allow merging.
.automerge_widget.unchecked
%p
%strong
%i.fa.fa-spinner.fa-spin
- Checking for ability to automatically merge…
+ Checking automatic merge…
.automerge_widget.already_cannot_be_merged.hide
%p
- %strong This merge request can not be merged. Try to reload the page.
+ %strong This merge request cannot 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;
+ Merge is in progress. Please wait… Page will be reloaded automatically. &nbsp;
diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index ffa3f7b0e36..3b1cd53df37 100644
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -1,34 +1,34 @@
- if @commits.any?
.ci_widget.ci-success{style: "display:none"}
- %i.fa.fa-check
+ = 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"}
- %i.fa.fa-times
+ = 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"}
- %i.fa.fa-clock-o
+ = 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
- %i.fa.fa-spinner
+ = icon("spinner spin")
Checking for CI status for #{@merge_request.last_commit_short_sha}
.ci_widget.ci-canceled{style: "display:none"}
- %i.fa.fa-times
+ = 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"}
- %i.fa.fa-times
+ = 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..0690fdb769f 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,9 +1,9 @@
%h4.page-title
.issue-box{ class: issue_box_class(@merge_request) }
- if @merge_request.merged?
- Merged
+ Accepted
- elsif @merge_request.closed?
- Closed
+ Rejected
- else
Open
= "Merge Request ##{@merge_request.iid}"
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index 44bd9347f51..6396232db22 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -11,17 +11,17 @@
- if @merge_request.closed?
%h4
- Closed
+ Rejected
- if @merge_request.closed_event
- by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ 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
- if @merge_request.merged?
%h4
- Merged
+ Accepted
- if @merge_request.merge_event
- by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
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 bba2b8764ac..417eaa1b09d 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 @project.issues_enabled
- .pull-right
+ .pull-right
+ - if can?(current_user, :write_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,10 +86,10 @@
.col-md-3
= render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
.col-md-3
- = render('merge_requests', title: 'Declined (closed)', merge_requests: @merge_requests.declined, id: 'closed')
+ = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.rejected, id: 'closed')
.col-md-3
.panel.panel-primary
- .panel-heading Merged
+ .panel-heading Accepted
%ul.well-list
- @merge_requests.merged.each do |merge_request|
= render 'merge_request', merge_request: merge_request
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 4d26b52df01..0a77f200f56 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -8,27 +8,32 @@
= image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
.timeline-content
.note-header
- .note-actions
- = link_to "##{dom_id(note)}", name: dom_id(note) do
- = icon('link')
- Link here
- &nbsp;
- - if note_editable?(note)
+ - if note_editable?(note)
+ .note-actions
= link_to '#', title: 'Edit comment', class: 'js-note-edit' do
= icon('pencil-square-o')
- Edit
- &nbsp;
- = link_to namespace_project_note_path(@project.namespace, @project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'danger js-note-delete' do
- = icon('trash-o', class: 'cred')
- Remove
+
+ = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
+ = icon('trash-o')
+
+ - unless note.system
+ - member = note.project.team.find_member(note.author.id)
+ - 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: ''
- = link_to_member(@project, note.author, avatar: false)
+
+ = link_to_member(note.project, note.author, avatar: false)
+
%span.author-username
= '@' + note.author.username
+
%span.note-last-update
- = note_timestamp(note)
+ = link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
+ = note_timestamp(note)
- if note.superceded?(@notes)
- if note.upvote?
@@ -65,7 +70,7 @@
= link_to note.attachment.url, target: '_blank' do
= icon('paperclip')
= note.attachment_identifier
- = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note),
+ = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
.clear
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index 7c6f7243173..e7a3854701c 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -14,7 +14,9 @@
- last_note = discussion_notes.last
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')}
+
.discussion-body.js-toggle-content
= 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/show.atom.builder b/app/views/projects/show.atom.builder
index bb713dcafa5..242684e5c7c 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -1,7 +1,7 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} activity"
- xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 72916cad182..04590f65b27 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -8,7 +8,7 @@
- if can? current_user, :download_code, @project
.tree-download-holder
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-sm pull-right hidden-xs hidden-sm', split_button: true
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
= render "tree", tree: @tree
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 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/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 6834969de8b..b2c085f34b1 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -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/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 2efa616d664..adfdd1c7506 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -11,6 +11,6 @@
#{merge_request.project.name_with_namespace}
.pull-right
- if merge_request.merged?
- %span.label.label-primary Merged
+ %span.label.label-primary Accepted
- elsif merge_request.closed?
- %span.label.label-danger Closed
+ %span.label.label-danger Rejected
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 8af393777f0..95099853918 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -13,16 +13,7 @@
.file-title
%i.fa.fa-file
%strong= snippet_blob[:snippet_object].file_name
- - if gitlab_markdown?(snippet_blob[:snippet_object].file_name)
- .file-content.wiki
- - snippet_blob[:snippet_chunks].each do |snippet|
- - unless snippet[:data].empty?
- = preserve do
- = markdown(snippet[:data])
- - else
- .file-content.code
- .nothing-here-block Empty file
- - elsif markup?(snippet_blob[:snippet_object].file_name)
+ - if markup?(snippet_blob[:snippet_object].file_name)
.file-content.wiki
- snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty?
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 60bb76e898a..3f489a04e71 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -18,7 +18,7 @@
:"data-container" => "body"}
= gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true
- - if project.kind_of?(Project)
+ - if project.kind_of?(Project) && project.empty_repo?
.input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level)
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
index fa8b4eae314..a5187fa4ea7 100644
--- a/app/views/shared/_issuable_filter.html.haml
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -3,15 +3,28 @@
%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] == 'rejected')}
+ = link_to page_filter_path(state: 'rejected') do
+ = icon('ban')
+ #{state_filters_text_for(:rejected, @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
@@ -51,8 +64,6 @@
= 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/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index b07c4d20f12..02416125a72 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,7 +1,7 @@
- Gitlab::VisibilityLevel.values.each do |level|
.radio
- restricted = restricted_visibility_levels.include?(level)
- = label model_method, level do
+ = form.label "#{model_method}_#{level}" do
= form.radio_button model_method, level, checked: (selected_level == level), disabled: restricted
= visibility_level_icon(level)
.option-title
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 30458793fd1..d26a99bb14c 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -1,9 +1,5 @@
- unless @snippet.content.empty?
- - if gitlab_markdown?(@snippet.file_name)
- .file-content.wiki
- = preserve do
- = markdown(@snippet.data)
- - elsif markup?(@snippet.file_name)
+ - if markup?(@snippet.file_name)
.file-content.wiki
= render_markup(@snippet.file_name, @snippet.data)
- else
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 6783587bda9..2feeeecc48b 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -11,7 +11,7 @@
.col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
-
+
.form-group
.file-editor
= f.label :file_name, "File", class: 'control-label'
@@ -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/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb
index 73085c046bd..fb878965288 100644
--- a/app/workers/project_web_hook_worker.rb
+++ b/app/workers/project_web_hook_worker.rb
@@ -3,8 +3,8 @@ class ProjectWebHookWorker
sidekiq_options queue: :project_web_hook
- def perform(hook_id, data)
+ def perform(hook_id, data, hook_name)
data = data.with_indifferent_access
- WebHook.find(hook_id).execute(data)
+ WebHook.find(hook_id).execute(data, hook_name)
end
end
diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb
index 3ebc62b7e7a..a122c274763 100644
--- a/app/workers/system_hook_worker.rb
+++ b/app/workers/system_hook_worker.rb
@@ -3,7 +3,7 @@ class SystemHookWorker
sidekiq_options queue: :system_hook
- def perform(hook_id, data)
- SystemHook.find(hook_id).execute data
+ def perform(hook_id, data, hook_name)
+ SystemHook.find(hook_id).execute(data, hook_name)
end
end
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/application.rb b/config/application.rb
index fa399533e52..7e899cc3b5b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -31,7 +31,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters.push(:password, :password_confirmation, :private_token)
+ config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index bd2081688d1..c7f22b9388b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -182,6 +182,10 @@ 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.
@@ -210,6 +214,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'
+ # } }
+
@@ -245,6 +258,10 @@ production: &base
repos_path: /home/git/repositories/
hooks_path: /home/git/gitlab-shell/hooks/
+ # File that contains the secret key for verifying access for gitlab-shell.
+ # Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app).
+ # secret_file: /home/git/gitlab/.gitlab_shell_secret
+
# Git over HTTP
upload_pack: true
receive_pack: true
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index e5ac66a2323..c234bd69e9a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -87,6 +87,8 @@ 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['providers'] ||= []
Settings['issues_tracker'] ||= {}
@@ -148,6 +150,7 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/'
Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/'
+Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
Settings.gitlab_shell['repos_path'] ||= Settings.gitlab['user_home'] + '/repositories/'
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index 38a5fa98dc2..5312fd8e89a 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.skip_paths << '/teaspoon'
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index 8f6c5673103..6f1f267bf97 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -10,3 +10,10 @@ if Gitlab::LDAP::Config.enabled?
alias_method server['provider_name'], :ldap
end
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
diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb
new file mode 100644
index 00000000000..c668864089b
--- /dev/null
+++ b/config/initializers/attr_encrypted_no_db_connection.rb
@@ -0,0 +1,20 @@
+module AttrEncrypted
+ module Adapters
+ module ActiveRecord
+ def attribute_instance_methods_as_symbols_with_no_db_connection
+ # Use with_connection so the connection doesn't stay pinned to the thread.
+ connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false
+
+ if connected
+ # Call version from AttrEncrypted::Adapters::ActiveRecord
+ attribute_instance_methods_as_symbols_without_no_db_connection
+ else
+ # Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord
+ AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call
+ end
+ end
+
+ alias_method_chain :attribute_instance_methods_as_symbols, :no_db_connection
+ end
+ end
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 8f8c4169740..091548348b1 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -1,6 +1,11 @@
# Use this hook to configure devise mailer, warden hooks and so forth. The first
# four configuration values can also be set straight in your models.
Devise.setup do |config|
+ config.warden do |manager|
+ manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
+ manager.default_strategies(scope: :user).unshift :two_factor_backupable
+ end
+
# ==> Mailer Configuration
# Configure the class responsible to send e-mails.
config.mailer = "DeviseMailer"
diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb
index e7c9f0ba7c2..751fccead07 100644
--- a/config/initializers/gitlab_shell_secret_token.rb
+++ b/config/initializers/gitlab_shell_secret_token.rb
@@ -5,8 +5,7 @@ require 'securerandom'
# Your secret key for verifying the gitlab_shell.
-secret_file = Rails.root.join('.gitlab_shell_secret')
-gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+secret_file = Gitlab.config.gitlab_shell.secret_file
unless File.exist? secret_file
# Generate a new token of 16 random hexadecimal characters and store it in secret_file.
@@ -14,6 +13,7 @@ unless File.exist? secret_file
File.write(secret_file, token)
end
-if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
- FileUtils.symlink(secret_file, gitlab_shell_symlink)
+link_path = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(link_path)
+ FileUtils.symlink(secret_file, link_path)
end
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index c5b6b75e7f6..a4032a21420 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -31,7 +31,7 @@ en:
messages:
# Common error messages
invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
- invalid_redirect_uri: 'The redirect uri included is not valid.'
+ invalid_redirect_uri: 'The redirect URI included is not valid.'
unauthorized_client: 'The client is not authorized to perform this request using this method.'
access_denied: 'The resource owner or authorization server denied the request.'
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
@@ -63,11 +63,11 @@ en:
flash:
applications:
create:
- notice: 'Application created.'
+ notice: 'The application was created successfully.'
destroy:
- notice: 'Application deleted.'
+ notice: 'The application was deleted successfully.'
update:
- notice: 'Application updated.'
+ notice: 'The application was updated successfully.'
authorized_applications:
destroy:
- notice: 'Application revoked.'
+ notice: 'The application was revoked access.'
diff --git a/config/routes.rb b/config/routes.rb
index 4b38dede7b4..f4a104664f3 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',
@@ -166,7 +165,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
@@ -226,6 +225,11 @@ Gitlab::Application.routes.draw do
resources :keys
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
+ resource :two_factor_auth, only: [:new, :create, :destroy] do
+ member do
+ post :codes
+ end
+ end
end
end
@@ -417,7 +421,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
@@ -445,6 +449,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/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/migrate/20150310194358_add_version_check_to_application_settings.rb b/db/migrate/20150310194358_add_version_check_to_application_settings.rb
new file mode 100644
index 00000000000..e9d42c1e749
--- /dev/null
+++ b/db/migrate/20150310194358_add_version_check_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddVersionCheckToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :version_check_enabled, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20150327223628_add_devise_two_factor_to_users.rb b/db/migrate/20150327223628_add_devise_two_factor_to_users.rb
new file mode 100644
index 00000000000..11b026ee8f3
--- /dev/null
+++ b/db/migrate/20150327223628_add_devise_two_factor_to_users.rb
@@ -0,0 +1,8 @@
+class AddDeviseTwoFactorToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :encrypted_otp_secret, :string
+ add_column :users, :encrypted_otp_secret_iv, :string
+ add_column :users, :encrypted_otp_secret_salt, :string
+ add_column :users, :otp_required_for_login, :boolean
+ end
+end
diff --git a/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb b/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb
new file mode 100644
index 00000000000..913958db7c5
--- /dev/null
+++ b/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb
@@ -0,0 +1,5 @@
+class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :otp_backup_codes, :text
+ end
+end
diff --git a/db/migrate/20150406133311_add_invite_data_to_member.rb b/db/migrate/20150406133311_add_invite_data_to_member.rb
index 3452fd45c4f..5d3e856ddce 100644
--- a/db/migrate/20150406133311_add_invite_data_to_member.rb
+++ b/db/migrate/20150406133311_add_invite_data_to_member.rb
@@ -1,5 +1,5 @@
class AddInviteDataToMember < ActiveRecord::Migration
- def change
+ def up
add_column :members, :created_by_id, :integer
add_column :members, :invite_email, :string
add_column :members, :invite_token, :string
@@ -9,4 +9,15 @@ class AddInviteDataToMember < ActiveRecord::Migration
add_index :members, :invite_token, unique: true
end
+
+ def down
+ remove_index :members, :invite_token
+
+ change_column :members, :user_id, :integer, null: false
+
+ remove_column :members, :invite_accepted_at
+ remove_column :members, :invite_token
+ remove_column :members, :invite_email
+ remove_column :members, :created_by_id
+ end
end
diff --git a/db/migrate/20150417122318_remove_import_data_from_project.rb b/db/migrate/20150417122318_remove_import_data_from_project.rb
index c275b49d228..46cf63593c9 100644
--- a/db/migrate/20150417122318_remove_import_data_from_project.rb
+++ b/db/migrate/20150417122318_remove_import_data_from_project.rb
@@ -1,5 +1,9 @@
class RemoveImportDataFromProject < ActiveRecord::Migration
- def change
+ def up
remove_column :projects, :import_data
end
+
+ def down
+ add_column :projects, :import_data, :text
+ end
end
diff --git a/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
index 9b0f13f3fa7..50a9b2439e0 100644
--- a/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
+++ b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
@@ -1,7 +1,11 @@
class AddDefaultProjectVisibililtyToApplicationSettings < ActiveRecord::Migration
- def change
+ def up
add_column :application_settings, :default_project_visibility, :integer
visibility = Settings.gitlab.default_projects_features['visibility_level']
execute("update application_settings set default_project_visibility = #{visibility}")
end
+
+ def down
+ remove_column :application_settings, :default_project_visibility
+ end
end
diff --git a/db/migrate/20150425164647_remove_duplicate_tags.rb b/db/migrate/20150425164647_remove_duplicate_tags.rb
index 1a9152cb965..13e5038db9c 100644
--- a/db/migrate/20150425164647_remove_duplicate_tags.rb
+++ b/db/migrate/20150425164647_remove_duplicate_tags.rb
@@ -1,7 +1,8 @@
class RemoveDuplicateTags < ActiveRecord::Migration
def up
select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(id) > 1").each do |tag|
- duplicate_ids = select_all("SELECT id FROM tags WHERE name = '#{tag["name"]}'").map{|tag| tag["id"]}
+ tag_name = quote_string(tag["name"])
+ duplicate_ids = select_all("SELECT id FROM tags WHERE name = '#{tag_name}'").map{|tag| tag["id"]}
origin_tag_id = duplicate_ids.first
duplicate_ids.delete origin_tag_id
diff --git a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
index 51237354d9f..8f1b0cc8935 100644
--- a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
+++ b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
@@ -1,7 +1,11 @@
class AddDefaultSnippetVisibilityToAppSettings < ActiveRecord::Migration
- def change
+ def up
add_column :application_settings, :default_snippet_visibility, :integer
visibility = Settings.gitlab.default_projects_features['visibility_level']
execute("update application_settings set default_snippet_visibility = #{visibility}")
end
+
+ def down
+ remove_column :application_settings, :default_snippet_visibility
+ end
end
diff --git a/db/migrate/20150429002313_remove_abandoned_group_members_records.rb b/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
index 6013605bb35..244637e1c4a 100644
--- a/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
+++ b/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
@@ -1,6 +1,9 @@
class RemoveAbandonedGroupMembersRecords < ActiveRecord::Migration
- def change
+ def up
execute("DELETE FROM members WHERE type = 'GroupMember' AND source_id NOT IN(\
SELECT id FROM namespaces WHERE type='Group')")
end
+
+ def down
+ end
end
diff --git a/db/migrate/20150509180749_convert_legacy_reference_notes.rb b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
new file mode 100644
index 00000000000..b02605489be
--- /dev/null
+++ b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
@@ -0,0 +1,16 @@
+# Convert legacy Markdown-emphasized notes to the current, non-emphasized format
+#
+# _mentioned in 54f7727c850972f0401c1312a7c4a6a380de5666_
+#
+# becomes
+#
+# mentioned in 54f7727c850972f0401c1312a7c4a6a380de5666
+class ConvertLegacyReferenceNotes < ActiveRecord::Migration
+ def up
+ execute %q{UPDATE notes SET note = trim(both '_' from note) WHERE system = true AND note LIKE '\_%\_'}
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/migrate/20150516060434_add_note_events_to_web_hooks.rb b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb
new file mode 100644
index 00000000000..0097587b4f6
--- /dev/null
+++ b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb
@@ -0,0 +1,9 @@
+class AddNoteEventsToWebHooks < ActiveRecord::Migration
+ def up
+ add_column :web_hooks, :note_events, :boolean, default: false, null: false
+ end
+
+ def down
+ remove_column :web_hooks, :note_events, :boolean
+ end
+end
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/schema.rb b/db/schema.rb
index 04abf9bb9a6..aea0742cf3b 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: 20150502064022) do
+ActiveRecord::Schema.define(version: 20150529150354) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -28,10 +28,13 @@ ActiveRecord::Schema.define(version: 20150502064022) do
t.integer "default_branch_protection", default: 2
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 "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"
end
create_table "broadcast_messages", force: true do |t|
@@ -493,6 +496,11 @@ ActiveRecord::Schema.define(version: 20150502064022) do
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.text "otp_backup_codes"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -527,6 +535,7 @@ ActiveRecord::Schema.define(version: 20150502064022) do
t.boolean "issues_events", default: false, null: false
t.boolean "merge_requests_events", default: false, null: false
t.boolean "tag_push_events", default: false
+ t.boolean "note_events", default: false, null: false
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
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/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/projects.md b/doc/api/projects.md
index 971fe96fb8e..17c014019ea 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -32,6 +32,7 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
+- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
```json
[
@@ -134,6 +135,7 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
+- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
### List ALL projects
@@ -149,6 +151,7 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
+- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
### Get single project
diff --git a/doc/development/README.md b/doc/development/README.md
index d5d264be19d..6bc8e1888db 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -1,4 +1,4 @@
-# Development
+# Development
- [Architecture](architecture.md) of GitLab
- [Shell commands](shell_commands.md) in the GitLab codebase
@@ -6,3 +6,5 @@
- [CI setup](ci_setup.md) for testing GitLab
- [Sidekiq debugging](sidekiq_debugging.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
+- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [How to dump production data to staging](dump_db.md)
diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md
new file mode 100644
index 00000000000..4ad3bd534e0
--- /dev/null
+++ b/doc/development/db_dump.md
@@ -0,0 +1,45 @@
+# Importing a database dump into a staging enviroment
+
+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.
+
+On the staging VM, add the following line to `/etc/gitlab/gitlab.rb` to speed up
+large database imports.
+
+```
+# On STAGING
+echo "postgresql['checkpoint_segments'] = 64" | sudo tee -a /etc/gitlab/gitlab.rb
+sudo touch /etc/gitlab/skip-auto-migrations
+sudo gitlab-ctl reconfigure
+```
+
+Next, we let the production environment stream a compressed SQL dump to our
+local machine via SSH, and redirect this stream to a psql client on the staging
+VM.
+
+```
+# On LOCAL MACHINE
+ssh -C gitlab.example.com sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -Cc gitlabhq_production |\
+ ssh -C staging-vm sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -d template1
+```
+
+## Recreating directory structure
+
+If you need to re-create some directory structure on the staging server you can
+use this procedure.
+
+First, on the production server, create a list of directories you want to
+re-create.
+
+```
+# On PRODUCTION
+(umask 077; sudo find /var/opt/gitlab/git-data/repositories -maxdepth 1 -type d -print0 > directories.txt)
+```
+
+Copy `directories.txt` to the staging server and create the directories there.
+
+```
+# On STAGING
+sudo -u git xargs -0 mkdir -p < directories.txt
+```
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
new file mode 100644
index 00000000000..4fa1961fde9
--- /dev/null
+++ b/doc/development/migration_style_guide.md
@@ -0,0 +1,88 @@
+# Migration Style Guide
+
+When writing migrations for GitLab, you have to take into account that
+these will be ran by hundreds of thousands of organizations of all sizes, some with
+many years of data in their database.
+
+In addition, having to take a server offline for a an upgrade small or big is
+a big burden for most organizations. For this reason it is important that your
+migrations are written carefully, can be applied online and adhere to the style guide below.
+
+When writing your migrations, also consider that databases might have stale data
+or inconsistencies and guard for that. Try to make as little assumptions as possible
+about the state of the database.
+
+Please don't depend on GitLab specific code since it can change in future versions.
+If needed copy-paste GitLab code into the migration to make make it forward compatible.
+
+## Comments in the migration
+
+Each migration you write needs to have the two following pieces of information
+as comments.
+
+### Online, Offline, errors?
+
+First, you need to provide information on whether the migration can be applied:
+
+1. online without errors (works on previous version and new one)
+2. online with errors on old instances after migrating
+3. online with errors on new instances while migrating
+4. offline (needs to happen without app servers to prevent db corruption)
+
+It is always preferable to have a migration run online. If you expect the migration
+to take particularly long (for instance, if it loops through all notes),
+this is valuable information to add.
+
+### Reversibility
+
+Your migration should be reversible. This is very important, as it should
+be possible to downgrade in case of a vulnerability or bugs.
+
+In your migration, add a comment describing how the reversibility of the
+migration was tested.
+
+
+## Removing indices
+
+If you need to remove index, please add a condition like in following example:
+
+```
+remove_index :namespaces, column: :name if index_exists?(:namespaces, :name)
+```
+
+## Adding indices
+
+If you need to add an unique index please keep in mind there is possibility of existing duplicates. If it is possible write a separate migration for handling this situation. It can be just removing or removing with overwriting all references to these duplicates depend on situation.
+
+## Testing
+
+Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
+
+Make sure your migration can be reversed.
+
+## Data migration
+
+Please prefer Arel and plain SQL over usual ActiveRecord syntax. In case of using plain SQL you need to quote all input manually with `quote_string` helper.
+
+Example with Arel:
+
+```
+users = Arel::Table.new(:users)
+users.group(users[:user_id]).having(users[:id].count.gt(5))
+
+#updtae other tables with this results
+```
+
+Example with plain SQL and `quote_string` helper:
+
+```
+select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(id) > 1").each do |tag|
+ tag_name = quote_string(tag["name"])
+ duplicate_ids = select_all("SELECT id FROM tags WHERE name = '#{tag_name}'").map{|tag| tag["id"]}
+ origin_tag_id = duplicate_ids.first
+ duplicate_ids.delete origin_tag_id
+
+ execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
+ execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
+end
+```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index ca25eaea799..badea4de214 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -62,7 +62,13 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev nodejs
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake nodejs
+
+If you want to use Kerberos for user authentication, then install libkrb5-dev:
+
+ sudo apt-get install libkrb5-dev
+
+**Note:** If you don't know what Kerberos is, you can assume you don't need it.
Make sure you have the right version of Git installed
@@ -189,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-10-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-11-stable gitlab
-**Note:** You can change `7-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -235,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
@@ -276,17 +279,19 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
# For PostgreSQL (note, the option says "without ... mysql")
- sudo -u git -H bundle install --deployment --without development test mysql aws
+ sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
# Or if you use MySQL (note, the option says "without ... postgres")
- sudo -u git -H bundle install --deployment --without development test postgres aws
+ sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+
+**Note:** If you want to use Kerberos for user authentication, then omit `kerberos` in the `--without` option above.
### Install GitLab Shell
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.2] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.3] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
@@ -294,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
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/markdown/markdown.md b/doc/markdown/markdown.md
index e95ddbb7578..9c7f723c06d 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -66,16 +66,26 @@ It is not reasonable to italicize just _part_ of a word, especially when you're
perform_complicated_task
do_this_and_do_that_and_another_thing
-perform_complicated_task
+perform_complicated_task
do_this_and_do_that_and_another_thing
## URL auto-linking
-GFM will autolink standard URLs you copy and paste into your text. So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
+GFM will autolink almost any URL you copy and paste into your text.
- http://www.google.com
+ * http://www.google.com
+ * https://google.com/
+ * ftp://ftp.us.debian.org/debian/
+ * smb://foo/bar/baz
+ * irc://irc.freenode.net/gitlab
+ * http://localhost:3000
-http://www.google.com
+* http://www.google.com
+* https://google.com/
+* ftp://ftp.us.debian.org/debian/
+* smb://foo/bar/baz
+* irc://irc.freenode.net/gitlab
+* http://localhost:3000
## Code and Syntax Highlighting
@@ -423,7 +433,7 @@ Quote break.
You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
-See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements, as well as the `class`, and `id` attributes on all elements.
+See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements.
```no-highlight
<dl>
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index bfef975024f..ae2d465e0c1 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -9,6 +9,8 @@ This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
+
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
@@ -150,11 +152,9 @@ If you have an installation from source, please consider backing up your `gitlab
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
-```
-# Omnibus package installation
-sudo gitlab-rake gitlab:backup:restore
+### Installation from source
-# installation from source
+```
bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
@@ -196,11 +196,45 @@ Restoring repositories:
Deleting tmp directories...[DONE]
```
-## Configure cron to make daily backups
+### Omnibus installations
+
+We will assume that you have installed GitLab from an omnibus package and run
+`sudo gitlab-ctl reconfigure` at least once.
+
+First make sure your backup tar file is in `/var/opt/gitlab/backups`.
+
+```shell
+sudo cp 1393513186_gitlab_backup.tar /var/opt/gitlab/backups/
+```
+
+Next, restore the backup by running the restore command. You need to specify the
+timestamp of the backup you are restoring.
+
+```shell
+# Stop processes that are connected to the database
+sudo gitlab-ctl stop unicorn
+sudo gitlab-ctl stop sidekiq
+
+# This command will overwrite the contents of your GitLab database!
+sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
+
+# Start GitLab
+sudo gitlab-ctl start
+
+# Create satellites
+sudo gitlab-rake gitlab:satellites:create
+
+# Check GitLab
+sudo gitlab-rake gitlab:check SANITIZE=true
+```
+
+If there is a GitLab version mismatch between your backup tar file and the installed
+version of GitLab, the restore command will abort with an error. Install a package for
+the [required version](https://www.gitlab.com/downloads/archives/) and try again.
-For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
+## Configure cron to make daily backups
-For installation from source:
+### For installation from source:
```
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
@@ -217,6 +251,32 @@ Add the following lines at the bottom:
The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
This is recommended to reduce cron spam.
+### For omnibus installations
+
+To schedule a cron job that backs up your repositories and GitLab metadata, use the root user:
+
+```
+sudo su -
+crontab -e
+```
+
+There, add the following line to schedule the backup for everyday at 2 AM:
+
+```
+0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1
+```
+
+You may also want to set a limited lifetime for backups to prevent regular
+backups using all your disk space. To do this add the following lines to
+`/etc/gitlab/gitlab.rb` and reconfigure:
+
+```
+# limit backup lifetime to 7 days - 604800 seconds
+gitlab_rails['backup_keep_time'] = 604800
+```
+
+NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
## Alternative backup strategies
If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
@@ -239,3 +299,7 @@ Example: LVM snapshots + rsync
If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
+
+### Note
+This documentation is for GitLab CE.
+We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com. \ No newline at end of file
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 41a994f3f68..2aca91d5371 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:
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index cfe01896d8f..eb97f3cd7f6 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -9,7 +9,8 @@ The new release manager should create overall issue to track the progress.
## Release Manager
-A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
+A release manager is selected that coordinates all releases the coming month,
+including the patch releases for previous releases.
The release manager has to make sure all the steps below are done and delegated where necessary.
This person should also make sure this document is kept up to date and issues are created and updated.
@@ -29,7 +30,6 @@ All steps from issue template are explained below
```
Xth: (7 working days before the 22nd)
-- [ ] Code freeze
- [ ] Update the CE changelog (#LINK)
- [ ] Update the EE changelog (#LINK)
- [ ] Update the CI changelog (#LINK)
@@ -79,10 +79,6 @@ Xth: (1 working day before the 22nd)
- - -
-## Code Freeze
-
-Stop merging code in master, except for important bug fixes
-
## Update changelog
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is
@@ -98,6 +94,8 @@ There are three changelogs that need to be updated: CE, EE and CI.
Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre.
+On creating the stable branches, notify the core team and developers.
+
## QA
Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress.
diff --git a/doc/security/README.md b/doc/security/README.md
index 49dfa6eec76..473f3632dcd 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -4,3 +4,4 @@
- [Rack attack](rack_attack.md)
- [Web Hooks and insecure internal web services](webhooks.md)
- [Information exclusivity](information_exclusivity.md)
+- [Reset your root password](reset_root_password.md) \ No newline at end of file
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
new file mode 100644
index 00000000000..3c13f262677
--- /dev/null
+++ b/doc/security/reset_root_password.md
@@ -0,0 +1,40 @@
+# How to reset your root password
+
+Log into your server with root privileges. Then start a Ruby on Rails console.
+
+Start the console with this command:
+
+```bash
+gitlab-rails console production
+```
+
+Wait until the console has loaded.
+
+There are multiple ways to find your user. You can search for email or username.
+
+```bash
+user = User.where(id: 1).first
+```
+
+or
+
+```bash
+user = User.find_by(email: 'admin@local.host')
+```
+
+Now you can change your password:
+
+```bash
+user.password = 'secret_pass'
+user.password_confirmation = 'secret_pass'
+```
+
+It's important that you change both password and password_confirmation to make it work.
+
+Don't forget to save the changes.
+
+```bash
+user.save!
+```
+
+Exit the console and try to login with your new password. \ No newline at end of file
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index f9b6d37d840..b0e4613cdef 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -6,6 +6,12 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser
## Hooks request example
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
**Project created:**
```json
diff --git a/doc/update/6.x-or-7.x-to-7.10.md b/doc/update/6.x-or-7.x-to-7.11.md
index 39e12f32d0e..b1daa648f1f 100644
--- a/doc/update/6.x-or-7.x-to-7.10.md
+++ b/doc/update/6.x-or-7.x-to-7.11.md
@@ -1,7 +1,7 @@
-# From 6.x or 7.x to 7.10
-*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.10.md) for the most up to date instructions.*
+# 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.*
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.10.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.11.
## 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-10-stable
+sudo -u git -H git checkout 7-11-stable
```
OR
@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-10-stable-ee
+sudo -u git -H git checkout 7-11-stable-ee
```
## 4. Install additional packages
@@ -91,7 +91,8 @@ sudo apt-get install logrotate
# Install pkg-config and cmake, which is needed for the latest versions of rugged
sudo apt-get install pkg-config cmake
-# Install Kerberos header files, which are needed for GitLab EE Kerberos support
+# If you want to use Kerberos with GitLab EE for user authentication, install Kerberos header files
+# If you don't know what Kerberos is, you can assume you don't need it.
sudo apt-get install libkrb5-dev
# Install nodejs, javascript runtime required for assets
@@ -126,7 +127,7 @@ sudo apt-get install nodejs
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.6.2
+sudo -u git -H git checkout v2.6.3
```
## 7. Install libs, migrations, etc.
@@ -161,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-10-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-11-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/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-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
@@ -181,8 +182,8 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* 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.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
## 9. Start application
diff --git a/doc/update/7.10-to-7.11.md b/doc/update/7.10-to-7.11.md
new file mode 100644
index 00000000000..79bc6de1e46
--- /dev/null
+++ b/doc/update/7.10-to-7.11.md
@@ -0,0 +1,103 @@
+# From 7.10 to 7.11
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-11-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-11-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.3
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-10-stable:config/gitlab.yml.example origin/7-11-stable:config/gitlab.yml.example
+``````
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (7.10)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.9 to 7.10](7.9-to-7.10.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 50941db25f6..2c43cf59c1f 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -1,7 +1,7 @@
# Migrating GitLab from MySQL to Postgres
*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.*
-If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, import into Postgres and rebuild the indexes as described below. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this.
+If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, convert the resulting SQL file, and import it into Postgres. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this.
## Export from MySQL and import into Postgres
@@ -14,13 +14,11 @@ sudo service gitlab stop
git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
-mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p
-python db_converter.py databasename.mysql databasename.psql
+mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
+python db_converter.py gitlabhq_production.mysql gitlabhq_production.psql
# Import the database dump as the application database user
-sudo -u git psql -f databasename.psql -d gitlabhq_production
-
-# Rebuild indexes (see below)
+sudo -u git psql -f gitlabhq_production.psql -d gitlabhq_production
# Install gems for PostgreSQL (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
@@ -28,51 +26,6 @@ sudo -u git -H bundle install --without development test mysql --deployment
sudo service gitlab start
```
-## Rebuild indexes
-
-The lanyrd database converter script does not preserve all indexes, so we have to recreate them ourselves after migrating from MySQL. It is not necessary to shut down GitLab for this process.
-
-### For non-omnibus installations
-
-On non-omnibus installations (distributed using Git) we retrieve the index declarations from version control using `git stash`.
-
-```
-# Clone the database converter on your Postgres-backed GitLab server
-cd /tmp
-git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
-
-cd /home/git/gitlab
-
-# Stash changes to db/schema.rb to make sure we can find the right index statements
-sudo -u git -H git stash
-
-# Generate add_index.rb
-ruby /tmp/mysql-postgresql-converter/add_index_statements.rb db/schema.rb > /tmp/mysql-postgresql-converter/add_index.rb
-
-# Create the indexes
-sudo -u git -H bundle exec rails runner -e production 'eval $stdin.read' < /tmp/mysql-postgresql-converter/add_index.rb
-```
-
-### For omnibus-gitlab installations
-
-On omnibus-gitlab we need to get the index declarations from a file called `schema.rb.bundled`. For versions older than 6.9, we need to download the file.
-
-```
-# Clone the database converter on your Postgres-backed GitLab server
-cd /tmp
-/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
-cd /tmp/mysql-postgresql-converter
-
-# Download schema.rb.bundled if necessary
-test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo /opt/gitlab/embedded/bin/curl -o /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled https://gitlab.com/gitlab-org/gitlab-ce/raw/v6.9.1/db/schema.rb
-
-# Generate add_index.rb
-/opt/gitlab/embedded/bin/ruby add_index_statements.rb /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled > add_index.rb
-
-# Create the indexes
-/opt/gitlab/bin/gitlab-rails runner 'eval $stdin.read' < add_index.rb
-```
-
## Converting a GitLab backup file from MySQL to Postgres
**Note:** Please make sure to have Python 2.7.x (or higher) installed.
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index d23534d58b6..6854250dab7 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -1,5 +1,10 @@
# GitLab Upgrader
-*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
+
+*DEPRECATED* We recommend to [switch to the Omnibus package and repository server](https://about.gitlab.com/update/) instead of using this script.
+
+Although deprecated, if someone wants to make this script into a gem or otherwise improve it merge requests are welcome.
+
+*Make sure you view this [upgrade guide from the 'master' branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
@@ -24,13 +29,15 @@ If you have local changes to your GitLab repository the script will stash them a
## 2. Run GitLab upgrade tool
-Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce).
+
+GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
cd /home/git/gitlab
- sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.1"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute'
+ sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute'
# to perform a non-interactive install (no user input required) you can add -y
- # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.1"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y
+ # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y
## 3. Start application
@@ -59,13 +66,15 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
You've read through the entire guide and probably already did all the steps one by one.
-Here is a one line command with step 1 to 5 for the next time you upgrade:
+Below is a one line command with step 1 to 5 for the next time you upgrade.
+
+Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce).
```bash
cd /home/git/gitlab; \
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \
- sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.1"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \
+ sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \
cd /home/git/gitlab-shell; \
sudo -u git -H git fetch; \
sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 851f50f5e9a..73717ffc7d6 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -12,6 +12,12 @@ If you send a web hook to an SSL endpoint [the certificate will not be verified]
Triggered when you push to the repository except when pushing tags.
+**Request header**:
+
+```
+X-Gitlab-Event: Push Hook
+```
+
**Request body:**
```json
@@ -63,6 +69,13 @@ Triggered when you push to the repository except when pushing tags.
Triggered when you create (or delete) tags to the repository.
+**Request header**:
+
+```
+X-Gitlab-Event: Tag Push Hook
+```
+
+
**Request body:**
```json
@@ -92,6 +105,12 @@ Triggered when you create (or delete) tags to the repository.
Triggered when a new issue is created or an existing issue was updated/closed/reopened.
+**Request header**:
+
+```
+X-Gitlab-Event: Issue Hook
+```
+
**Request body:**
```json
@@ -121,10 +140,295 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
}
}
```
+## Comment events
+
+Triggered when a new comment is made on commits, merge requests, issues, and code snippets.
+The note data will be stored in `object_attributes` (e.g. `note`, `noteable_type`). The
+payload will also include information about the target of the comment. For example,
+a comment on a issue will include the specific issue information under the `issue` key.
+Valid target types:
+
+1. `commit`
+2. `merge_request`
+3. `issue`
+4. `snippet`
+
+### Comment on commit
+
+**Request header**:
+
+```
+X-Gitlab-Event: Note Hook
+```
+
+**Request body:**
+
+```json
+{
+ "object_kind": "note",
+ "user": {
+ "name": "Adminstrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "http://localhost/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1243,
+ "note": "This is a commit comment. How does this work?",
+ "noteable_type": "Commit",
+ "author_id": 1,
+ "created_at": "2015-05-17 18:08:09 UTC",
+ "updated_at": "2015-05-17 18:08:09 UTC",
+ "project_id": 5,
+ "attachment":null,
+ "line_code": "bec9703f7a456cd2b4ab5fb3220ae016e3e394e3_0_1",
+ "commit_id": "cfe32cf61b73a0d5e9f13e774abde7ff789b1660",
+ "noteable_id": null,
+ "system": false,
+ "st_diff": {
+ "diff": "--- /dev/null\n+++ b/six\n@@ -0,0 +1 @@\n+Subproject commit 409f37c4f05865e4fb208c771485f211a22c4c2d\n",
+ "new_path": "six",
+ "old_path": "six",
+ "a_mode": "0",
+ "b_mode": "160000",
+ "new_file": true,
+ "renamed_file": false,
+ "deleted_file": false
+ },
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/cfe32cf61b73a0d5e9f13e774abde7ff789b1660#note_1243"
+ },
+ "commit": {
+ "id": "cfe32cf61b73a0d5e9f13e774abde7ff789b1660",
+ "message": "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "timestamp": "2014-02-27T10:06:20+02:00",
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/cfe32cf61b73a0d5e9f13e774abde7ff789b1660",
+ "author": {
+ "name": "Dmitriy Zaporozhets",
+ "email": "dmitriy.zaporozhets@gmail.com"
+ }
+ }
+}
+```
+
+### Comment on merge request
+
+**Request header**:
+
+```
+X-Gitlab-Event: Note Hook
+```
+
+**Request body:**
+
+```json
+{
+ "object_kind": "note",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "http://example.com/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1244,
+ "note": "This MR needs work.",
+ "noteable_type": "MergeRequest",
+ "author_id": 1,
+ "created_at": "2015-05-17 18:21:36 UTC",
+ "updated_at": "2015-05-17 18:21:36 UTC",
+ "project_id": 5,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 7,
+ "system": false,
+ "st_diff": null,
+ "url": "http://example.com/gitlab-org/gitlab-test/merge_requests/1#note_1244"
+ },
+ "merge_request": {
+ "id": 7,
+ "target_branch": "markdown",
+ "source_branch": "master",
+ "source_project_id": 5,
+ "author_id": 8,
+ "assignee_id": 28,
+ "title": "Tempora et eos debitis quae laborum et.",
+ "created_at": "2015-03-01 20:12:53 UTC",
+ "updated_at": "2015-03-21 18:27:27 UTC",
+ "milestone_id": 11,
+ "state": "opened",
+ "merge_status": "cannot_be_merged",
+ "target_project_id": 5,
+ "iid": 1,
+ "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
+ "position": 0,
+ "locked_at": null,
+ "source": {
+ "name": "Gitlab Test",
+ "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
+ "http_url": "http://example.com/gitlab-org/gitlab-test.git",
+ "namespace": "Gitlab Org",
+ "visibility_level": 10
+ },
+ "target": {
+ "name": "Gitlab Test",
+ "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
+ "http_url": "http://example.com/gitlab-org/gitlab-test.git",
+ "namespace": "Gitlab Org",
+ "visibility_level": 10
+ },
+ "last_commit": {
+ "id": "562e173be03b8ff2efb05345d12df18815438a4b",
+ "message": "Merge branch 'another-branch' into 'master'\n\nCheck in this test\n",
+ "timestamp": "2015-04-08T21: 00:25-07:00",
+ "url": "http://example.com/gitlab-org/gitlab-test/commit/562e173be03b8ff2efb05345d12df18815438a4b",
+ "author": {
+ "name": "John Smith",
+ "email": "john@example.com"
+ }
+ }
+ }
+}
+```
+
+### Comment on issue
+
+**Request header**:
+
+```
+X-Gitlab-Event: Note Hook
+```
+
+**Request body:**
+
+```json
+{
+ "object_kind": "note",
+ "user": {
+ "name": "Adminstrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "http://example.com/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1241,
+ "note": "Hello world",
+ "noteable_type": "Issue",
+ "author_id": 1,
+ "created_at": "2015-05-17 17:06:40 UTC",
+ "updated_at": "2015-05-17 17:06:40 UTC",
+ "project_id": 5,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 92,
+ "system": false,
+ "st_diff": null,
+ "url": "http://example.com/gitlab-org/gitlab-test/issues/17#note_1241"
+ },
+ "issue": {
+ "id": 92,
+ "title": "test",
+ "assignee_id": null,
+ "author_id": 1,
+ "project_id": 5,
+ "created_at": "2015-04-12 14:53:17 UTC",
+ "updated_at": "2015-04-26 08:28:42 UTC",
+ "position": 0,
+ "branch_name": null,
+ "description": "test",
+ "milestone_id": null,
+ "state": "closed",
+ "iid": 17
+ }
+}
+```
+
+### Comment on code snippet
+
+
+**Request header**:
+
+```
+X-Gitlab-Event: Note Hook
+```
+
+**Request body:**
+
+```
+{
+ "object_kind": "note",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "http://example.com/gitlab-org/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlab-org/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 1245,
+ "note": "Is this snippet doing what it's supposed to be doing?",
+ "noteable_type": "Snippet",
+ "author_id": 1,
+ "created_at": "2015-05-17 18:35:50 UTC",
+ "updated_at": "2015-05-17 18:35:50 UTC",
+ "project_id": 5,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 53,
+ "system": false,
+ "st_diff": null,
+ "url": "http://example.com/gitlab-org/gitlab-test/snippets/53#note_1245"
+ },
+ "snippet": {
+ "id": 53,
+ "title": "test",
+ "content": "puts 'Hello world'",
+ "author_id": 1,
+ "project_id": 5,
+ "created_at": "2015-04-09 02:40:38 UTC",
+ "updated_at": "2015-04-09 02:40:38 UTC",
+ "file_name": "test.rb",
+ "expires_at": null,
+ "type": "ProjectSnippet",
+ "visibility_level": 0
+ }
+}
+```
## Merge request events
-Triggered when a new merge request is created or an existing merge request was updated/merged/closed.
+Triggered when a new merge request is created, an existing merge request was updated/merged/closed or a commit is added in the source branch.
+
+**Request header**:
+
+```
+X-Gitlab-Event: Merge Request Hook
+```
**Request body:**
diff --git a/doc/workflow/2fa.png b/doc/workflow/2fa.png
new file mode 100644
index 00000000000..bbf415210d5
--- /dev/null
+++ b/doc/workflow/2fa.png
Binary files differ
diff --git a/doc/workflow/2fa_auth.png b/doc/workflow/2fa_auth.png
new file mode 100644
index 00000000000..4a4fbe68984
--- /dev/null
+++ b/doc/workflow/2fa_auth.png
Binary files differ
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 7e996dc47d4..89005e51958 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -11,5 +11,8 @@
- [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)
+- [Two-factor Authentication (2FA)](two_factor_authentication.md)
- [Protected branches](protected_branches.md)
+- [Change your time zone](timezone.md)
+- [Keyboard shortcuts](shortcuts.md)
- [Web Editor](web_editor.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
new file mode 100644
index 00000000000..7e08c0e51ac
--- /dev/null
+++ b/doc/workflow/timezone.md
@@ -0,0 +1,30 @@
+# Changing your time zone
+
+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'
+```
+or
+```
+gitlab_rails['time_zone'] = 'Europe/Brussels'
+```
+
+After you added this field, reconfigure and restart:
+```
+gitlab-ctl reconfigure
+gitlab-ctl restart
+```
diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md
new file mode 100644
index 00000000000..8ac1ca4b351
--- /dev/null
+++ b/doc/workflow/two_factor_authentication.md
@@ -0,0 +1,65 @@
+# 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].
+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** > **Acount** 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 **Acount**.
+1. Click **Disable Two-factor Authentication**.
+
+[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
diff --git a/doc_styleguide.md b/doc_styleguide.md
new file mode 100644
index 00000000000..670af765f3a
--- /dev/null
+++ b/doc_styleguide.md
@@ -0,0 +1,22 @@
+# Documentation styleguide
+
+This styleguide recommends best practices to improve documentation and to keep it organized and easy to find.
+
+## 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.
+
+
+## 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
diff --git a/docker/README.md b/docker/README.md
index 2e533ae9dd5..a73ccd0dba0 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -107,7 +107,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:
diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
index df828a2a349..fe3f7f0bcd2 100644
--- a/docker/app/Dockerfile
+++ b/docker/app/Dockerfile
@@ -5,15 +5,14 @@ RUN apt-get update -q \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
ca-certificates \
openssh-server \
- wget
+ wget \
+ apt-transport-https
# Download & Install GitLab
-# If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
-RUN TMP_FILE=$(mktemp); \
- wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab-ce_7.10.1~omnibus.2-1_amd64.deb \
- && dpkg -i $TMP_FILE \
- && rm -f $TMP_FILE
+RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
+RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
+RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
diff --git a/docker/single/Dockerfile b/docker/single/Dockerfile
index 8cdc24cf045..a6cbf131237 100644
--- a/docker/single/Dockerfile
+++ b/docker/single/Dockerfile
@@ -2,20 +2,18 @@ FROM ubuntu:14.04
MAINTAINER Sytse Sijbrandij
# Install required packages
-RUN apt-get update
-ENV DEBIAN_FRONTEND noninteractive
-RUN apt-get install -yq --no-install-recommends \
+RUN apt-get update -q \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
ca-certificates \
openssh-server \
- wget
+ wget \
+ apt-transport-https
# Download & Install GitLab
-# If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
-RUN TMP_FILE=$(mktemp); \
- wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab-ce_7.10.1~omnibus.2-1_amd64.deb \
- && dpkg -i $TMP_FILE \
- && rm -f $TMP_FILE
+RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
+RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
+RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
@@ -30,6 +28,7 @@ EXPOSE 80 22
# Copy assets
COPY assets/wrapper /usr/local/bin/
+COPY assets/gitlab.rb /etc/gitlab/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/single/assets/gitlab.rb b/docker/single/assets/gitlab.rb
new file mode 100644
index 00000000000..ef84e7832d6
--- /dev/null
+++ b/docker/single/assets/gitlab.rb
@@ -0,0 +1,37 @@
+# External URL should be your Docker instance.
+# By default, GitLab will use the Docker container hostname.
+# Always use port 80 here to force the internal nginx to bind port 80,
+# even if you intend to use another port in Docker.
+# external_url "http://192.168.59.103/"
+
+# Prevent Postgres from trying to allocate 25% of total memory
+postgresql['shared_buffers'] = '1MB'
+
+# Configure GitLab to redirect PostgreSQL logs to the data volume
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Some configuration of GitLab
+# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
+gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
+gitlab_rails['gitlab_support_email'] = 'support@example.com'
+gitlab_rails['time_zone'] = 'Europe/Paris'
+
+# SMTP settings
+# You must use an external server, the Docker container does not install an SMTP server
+gitlab_rails['smtp_enable'] = true
+gitlab_rails['smtp_address'] = "smtp.example.com"
+gitlab_rails['smtp_port'] = 587
+gitlab_rails['smtp_user_name'] = "user"
+gitlab_rails['smtp_password'] = "password"
+gitlab_rails['smtp_domain'] = "example.com"
+gitlab_rails['smtp_authentication'] = "plain"
+gitlab_rails['smtp_enable_starttls_auto'] = true
+
+# Enable LDAP authentication
+# gitlab_rails['ldap_enabled'] = true
+# gitlab_rails['ldap_host'] = 'ldap.example.com'
+# gitlab_rails['ldap_port'] = 389
+# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
+# gitlab_rails['ldap_allow_username_or_email_login'] = false
+# gitlab_rails['ldap_uid'] = 'uid'
+# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/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/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/project/deploy_keys.feature b/features/project/deploy_keys.feature
index a71f6124d9c..47cf774094f 100644
--- a/features/project/deploy_keys.feature
+++ b/features/project/deploy_keys.feature
@@ -9,9 +9,10 @@ Feature: Project Deploy Keys
Then I should see project deploy key
Scenario: I should see project deploy keys
- Given other project has deploy key
+ Given other projects have deploy keys
When I visit project deploy keys page
- Then I should see other project deploy key
+ Then I should see other project deploy key
+ And I should only see the same deploy key once
Scenario: I should see public deploy keys
Given public deploy key exists
@@ -26,7 +27,7 @@ Feature: Project Deploy Keys
And I should see newly created deploy key
Scenario: I attach other project deploy key to project
- Given other project has deploy key
+ Given other projects have deploy keys
And I visit project deploy keys page
When I click attach deploy key
Then I should be on deploy keys page
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index d9fbb875c28..ad1160e3343 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -38,3 +38,15 @@ Feature: Project Forked Merge Requests
Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
Then the target repository should be the original repository
+
+ @javascript
+ Scenario: I see the users in the target project for a new merge request
+ Given I logout
+ And I sign in as an admin
+ And I have a project forked off of "Shop" called "Forked Shop"
+ Then I visit project "Forked Shop" merge requests page
+ And I click link "New Merge Request"
+ And I fill out a "Merge Request On Forked Project" merge request
+ When I click "Assign to" dropdown"
+ Then I should see the target project ID in the input selector
+ And I should see the users from the target project ID
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 60caf783fe4..eb091c291e9 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -10,8 +10,8 @@ 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
- Given I click link "Closed"
+ Scenario: I should see rejected merge requests
+ Given I click link "Rejected"
Then I should see "Feature NS-03" in merge requests
And I should not see "Bug NS-04" in merge requests
@@ -207,3 +207,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 ae28312a69a..56ae5c78d01 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -62,3 +62,14 @@ Feature: Project
And I add project tags
And I save project
Then I should see project tags
+
+ Scenario: I should not see "New Issue" or "New Merge Request" buttons
+ Given I disable issues and merge requests in 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/wiki.feature b/features/project/wiki.feature
index 977cd609a11..7a70f348754 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -69,6 +69,11 @@ Feature: Project Wiki
And I click on the "Pages" button
Then I should see non-escaped link in the pages list
+ @javascript @focus
+ 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
diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
index fb0b611762e..844837d177d 100644
--- a/features/steps/admin/deploy_keys.rb
+++ b/features/steps/admin/deploy_keys.rb
@@ -14,17 +14,6 @@ class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
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
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 8508b2a8096..bb1f2f444f9 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -23,8 +23,8 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
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"
+ find("input#merge_request_source_branch").value.should == "fix"
+ find("input#merge_request_target_branch").value.should == "master"
end
step 'user with name "John Doe" joined project "Shop"' do
diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb
index 8384df2fb59..aeea49320ff 100644
--- a/features/steps/dashboard/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -60,4 +60,8 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
page.should have_content "Samurai"
page.should have_content "Tokugawa Shogunate"
end
+
+ step 'I should see the "Can not leave message"' do
+ page.should have_content "You can not leave Owned group because you're the last owner"
+ end
end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 228b83e5fd0..84348d1709a 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -203,8 +203,8 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
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"))
+ page.should have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
+ page.should have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
end
step 'I click on one group milestone' do
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 13e93618eb7..b6e03b549af 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"
+ page.should have_content "Notifications"
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 791982d16c3..32888eb17d9 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -3,25 +3,27 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
include SharedPaths
step 'I should see my profile info' do
- page.should have_content "Profile Settings"
+ page.should 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'
+ find('#user_location').value.should == 'Ukraine'
end
step 'I change my avatar' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 30b1934b363..c888e82e207 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -12,7 +12,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click atom feed link' do
- click_link "Feed"
+ click_link "Commits Feed"
end
step 'I see commits atom feed' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 50e14513a7a..81d1182cd1b 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -45,10 +45,20 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
end
- step 'other project has deploy key' do
- @second_project = create :project, namespace: create(:group)
+ step 'other projects have deploy keys' do
+ @second_project = create(:project, namespace: create(:group))
@second_project.team << [current_user, :master]
create(:deploy_keys_project, project: @second_project)
+
+ @third_project = create(:project, namespace: create(:group))
+ @third_project.team << [current_user, :master]
+ create(:deploy_keys_project, project: @third_project, deploy_key: @second_project.deploy_keys.first)
+ end
+
+ step 'I should only see the same deploy key once' do
+ within '.available-keys' do
+ page.should have_selector('ul li', count: 1)
+ end
end
step 'public deploy key exists' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 94d21d28a0c..ebfa102cee5 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -128,6 +128,21 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
end
+ step 'I click "Assign to" dropdown"' do
+ first('.ajax-users-select').click
+ end
+
+ step 'I should see the target project ID in the input selector' do
+ expect(page).to have_selector("input[data-project-id=\"#{@project.id}\"]")
+ end
+
+ 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
+ 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
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 4b135202593..d06905285fe 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -23,7 +23,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
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
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index f67e6e3d8ca..4ca7cf5e5fe 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -19,8 +19,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_link "All"
end
- step 'I click link "Closed"' do
- click_link "Closed"
+ step 'I click link "Rejected"' do
+ click_link "Rejected"
end
step 'I should see merge request "Wiki Feature"' do
@@ -32,7 +32,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see closed merge request "Bug NS-04"' do
merge_request = MergeRequest.find_by!(title: "Bug NS-04")
merge_request.closed?.should be_true
- page.should have_content "Closed by"
+ page.should have_content "Rejected by"
end
step 'I should see merge request "Bug NS-04"' do
@@ -113,7 +113,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click on the Changes tab via Javascript' do
- find('.diffs-tab').click
+ within '.merge-request-tabs' do
+ click_link 'Changes'
+ end
+
sleep 2
end
@@ -202,7 +205,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see merged request' do
within '.issue-box' do
- page.should have_content "Merged"
+ page.should have_content "Accepted"
end
end
@@ -302,6 +305,20 @@ 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
+ page.should have_content 'From fix into feature'
+ page.should have_content 'Target branch changed from master to feature'
+ end
+
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 00706ab30e9..fcc15aacc21 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -102,4 +102,16 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see project tags' do
expect(find_field('Tags').value).to eq 'tag1, tag2'
end
+
+ step 'I should not see "New Issue" button' do
+ page.should_not have_link 'New Issue'
+ end
+
+ step 'I should not see "New Merge Request" button' do
+ page.should_not have_link 'New Merge Request'
+ end
+
+ step 'I should not see "Snippets" button' do
+ page.should_not have_link 'Snippets'
+ end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 717132da45d..58cb0ceb3f1 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -133,6 +133,16 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
current_path.should 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']")
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index b60ac5e3423..3059c4ee041 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -14,6 +14,17 @@ 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
+ @project.save
+ end
+
# Add another user to project "Shop"
step 'I add a user to project "Shop"' do
@project = Project.find_by(name: "Shop")
diff --git a/features/support/env.rb b/features/support/env.rb
index f34302721ed..d4a878ea4ce 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'
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/helpers.rb b/lib/api/helpers.rb
index 85e9081680d..1ebf9a1f022 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -243,7 +243,7 @@ module API
end
def secret_token
- File.read(Rails.root.join('.gitlab_shell_secret')).chomp
+ File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
def handle_member_errors(errors)
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index f98a17773e7..e38736fc28b 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -24,10 +24,6 @@ module API
User.find_by(id: params[:user_id])
end
- unless actor
- return Gitlab::GitAccessStatus.new(false, 'No such user or key')
- end
-
project_path = params[:project]
# Check for *.wiki repositories.
@@ -39,22 +35,14 @@ module API
project = Project.find_with_namespace(project_path)
- if project
- access =
- if wiki
- Gitlab::GitAccessWiki.new(actor, project)
- else
- Gitlab::GitAccess.new(actor, project)
- end
-
- status = access.check(params[:action], params[:changes])
- end
+ access =
+ if wiki
+ Gitlab::GitAccessWiki.new(actor, project)
+ else
+ Gitlab::GitAccess.new(actor, project)
+ end
- if project && access.can_read_project?
- status
- else
- Gitlab::GitAccessStatus.new(false, 'No such project')
- end
+ access.check(params[:action], params[:changes])
end
#
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 2216a12a87a..d835dce2ded 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -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,10 +147,15 @@ 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
+ # 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?
render_api_error!({ labels: errors }, 400)
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_hooks.rb b/lib/api/project_hooks.rb
index be9850367b9..ad4d2e65dfd 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -43,7 +43,8 @@ module API
:push_events,
:issues_events,
:merge_requests_events,
- :tag_push_events
+ :tag_push_events,
+ :note_events
]
@hook = user_project.hooks.new(attrs)
@@ -73,7 +74,8 @@ module API
:push_events,
:issues_events,
:merge_requests_events,
- :tag_push_events
+ :tag_push_events,
+ :note_events
]
if @hook.update_attributes attrs
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index e3fff79d68f..1f2251c9b9c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -22,7 +22,12 @@ module API
projects = projects.search(params[:search])
end
- projects.reorder(project_order_by => project_sort)
+ if params[:ci_enabled_first].present?
+ projects.includes(:gitlab_ci_service).
+ reorder("services.active DESC, projects.#{project_order_by} #{project_sort}")
+ else
+ projects.reorder(project_order_by => project_sort)
+ end
end
def project_order_by
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 518964db50d..22b8f90dc5c 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -47,7 +47,7 @@ module API
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
- @hook.execute(data)
+ @hook.execute(data, 'system_hooks')
data
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 032a5d76e43..7d4c68c7412 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.execute(user)
else
not_found!('User')
end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 424541b4a04..6d0e30e916f 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -51,9 +51,9 @@ module Gitlab
def protection_options
{
- "Not protected, developers and masters can (force) push and delete the branch" => PROTECTION_NONE,
- "Partially protected, developers can also push but prevent all force pushes and deletion" => PROTECTION_DEV_CAN_PUSH,
- "Fully protected, only masters can push and prevent all force pushes and deletion" => PROTECTION_FULL,
+ "Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE,
+ "Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH,
+ "Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL,
}
end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
new file mode 100644
index 00000000000..bf33e5b1b1e
--- /dev/null
+++ b/lib/gitlab/asciidoc.rb
@@ -0,0 +1,60 @@
+require 'asciidoctor'
+require 'html/pipeline'
+
+module Gitlab
+ # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
+ # the resulting HTML through HTML pipeline filters.
+ module Asciidoc
+
+ # Provide autoload paths for filters to prevent a circular dependency error
+ autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
+
+ DEFAULT_ADOC_ATTRS = [
+ 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
+ 'env-gitlab', 'source-highlighter=html-pipeline'
+ ].freeze
+
+ # Public: Converts the provided Asciidoc markup into HTML.
+ #
+ # input - the source text in Asciidoc format
+ # context - a Hash with the template context:
+ # :commit
+ # :project
+ # :project_wiki
+ # :requested_path
+ # :ref
+ # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter
+ # html_opts - a Hash of options for HTML output:
+ # :xhtml - output XHTML instead of HTML
+ #
+ def self.render(input, context, asciidoc_opts = {}, html_opts = {})
+ asciidoc_opts = asciidoc_opts.reverse_merge(
+ safe: :secure,
+ backend: html_opts[:xhtml] ? :xhtml5 : :html5,
+ attributes: []
+ )
+ asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS)
+
+ html = ::Asciidoctor.convert(input, asciidoc_opts)
+
+ if context[:project]
+ result = HTML::Pipeline.new(filters).call(html, context)
+
+ save_opts = html_opts[:xhtml] ?
+ Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0
+
+ html = result[:output].to_html(save_with: save_opts)
+ end
+
+ html.html_safe
+ end
+
+ private
+
+ def self.filters
+ [
+ Gitlab::Markdown::RelativeLinkFilter
+ ]
+ 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/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/git_access.rb b/lib/gitlab/git_access.rb
index bc72b7528d5..c90184d31cf 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -31,8 +31,7 @@ module Gitlab
def can_push_to_branch?(ref)
return false unless user
- if project.protected_branch?(ref) &&
- !(project.developers_can_push_to_protected_branch?(ref) && project.team.developer?(user))
+ if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
user.can?(:push_code_to_protected_branches, project)
else
user.can?(:push_code, project)
@@ -50,13 +49,25 @@ module Gitlab
end
def check(cmd, changes = nil)
+ unless actor
+ return build_status_object(false, "No user or key was provided.")
+ end
+
+ if user && !user_allowed?
+ return build_status_object(false, "Your account has been blocked.")
+ end
+
+ unless project && can_read_project?
+ return build_status_object(false, 'The project you were looking for could not be found.')
+ end
+
case cmd
when *DOWNLOAD_COMMANDS
download_access_check
when *PUSH_COMMANDS
push_access_check(changes)
else
- build_status_object(false, "Wrong command")
+ build_status_object(false, "The command you're trying to execute is not allowed.")
end
end
@@ -64,7 +75,7 @@ module Gitlab
if user
user_download_access_check
elsif deploy_key
- deploy_key_download_access_check
+ build_status_object(true)
else
raise 'Wrong actor'
end
@@ -74,39 +85,27 @@ module Gitlab
if user
user_push_access_check(changes)
elsif deploy_key
- build_status_object(false, "Deploy key not allowed to push")
+ build_status_object(false, "Deploy keys are not allowed to push code.")
else
raise 'Wrong actor'
end
end
def user_download_access_check
- if user && user_allowed? && user.can?(:download_code, project)
- build_status_object(true)
- else
- build_status_object(false, "You don't have access")
+ unless user.can?(:download_code, project)
+ return build_status_object(false, "You are not allowed to download code from this project.")
end
- end
- def deploy_key_download_access_check
- if can_read_project?
- build_status_object(true)
- else
- build_status_object(false, "Deploy key not allowed to access this project")
- end
+ build_status_object(true)
end
def user_push_access_check(changes)
- unless user && user_allowed?
- return build_status_object(false, "You don't have access")
- end
-
if changes.blank?
return build_status_object(true)
end
unless project.repository.exists?
- return build_status_object(false, "Repository does not exist")
+ return build_status_object(false, "A repository for this project does not exist yet.")
end
changes = changes.lines if changes.kind_of?(String)
@@ -136,11 +135,24 @@ module Gitlab
:push_code
end
- if user.can?(action, project)
- build_status_object(true)
- else
- build_status_object(false, "You don't have permission")
+ unless user.can?(action, project)
+ status =
+ case action
+ when :force_push_code_to_protected_branches
+ build_status_object(false, "You are not allowed to force push code to a protected branch on this project.")
+ when :remove_protected_branches
+ build_status_object(false, "You are not allowed to deleted protected branches from this project.")
+ when :push_code_to_protected_branches
+ build_status_object(false, "You are not allowed to push code to protected branches on this project.")
+ when :admin_project
+ build_status_object(false, "You are not allowed to change existing tags on this project.")
+ else # :push_code
+ build_status_object(false, "You are not allowed to push code to this project.")
+ end
+ return status
end
+
+ build_status_object(true)
end
def forced_push?(oldrev, newrev)
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 73d99b96202..8ba97184e69 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -4,7 +4,7 @@ module Gitlab
if user.can?(:write_wiki, project)
build_status_object(true)
else
- build_status_object(false, "You don't have access")
+ build_status_object(false, "You are not allowed to write to this project's wiki.")
end
end
end
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index 960fb3849b4..16ff03c38d4 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -40,7 +40,7 @@ module Gitlab
user.block unless user.blocked?
false
else
- user.activate if user.blocked?
+ user.activate if user.blocked? && !ldap_config.block_auto_created_users
true
end
else
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 63294aa54c0..fa9c0975bb8 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -1,5 +1,4 @@
require 'html/pipeline'
-require 'task_list/filter'
module Gitlab
# Custom parser for GitLab-flavored Markdown
@@ -12,21 +11,24 @@ 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'
+ autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
+ autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
- # project - the project
+ # options - options
# html_options - extra options for the reference links as given to link_to
- def gfm(text, project = @project, html_options = {})
- gfm_with_options(text, {}, project, html_options)
+ def gfm(text, options = {}, html_options = {})
+ gfm_with_options(text, options, html_options)
end
# Public: Parse the provided text with GitLab-Flavored Markdown
@@ -37,7 +39,7 @@ module Gitlab
# :reference_only_path - Use relative path for reference links
# project - the project
# html_options - extra options for the reference links as given to link_to
- def gfm_with_options(text, options = {}, project = @project, html_options = {})
+ def gfm_with_options(text, options = {}, html_options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
@@ -47,12 +49,17 @@ module Gitlab
options.reverse_merge!(
xhtml: false,
- reference_only_path: true
+ reference_only_path: true,
+ project: @project,
+ current_user: current_user
)
pipeline = HTML::Pipeline.new(filters)
context = {
+ # SanitizationFilter
+ pipeline: options[:pipeline],
+
# EmojiFilter
asset_root: Gitlab.config.gitlab.url,
asset_host: Gitlab::Application.config.asset_host,
@@ -61,10 +68,15 @@ module Gitlab
no_header_anchors: options[:no_header_anchors],
# ReferenceFilter
- current_user: current_user,
+ current_user: options[:current_user],
only_path: options[:reference_only_path],
- project: project,
- reference_class: html_options[:class]
+ project: options[:project],
+ reference_class: html_options[:class],
+
+ # RelativeLinkFilter
+ ref: @ref,
+ requested_path: @path,
+ project_wiki: @project_wiki
}
result = pipeline.call(text, context)
@@ -91,9 +103,11 @@ module Gitlab
[
Gitlab::Markdown::SanitizationFilter,
+ Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
+ Gitlab::Markdown::ExternalLinkFilter,
Gitlab::Markdown::UserReferenceFilter,
Gitlab::Markdown::IssueReferenceFilter,
@@ -104,7 +118,7 @@ module Gitlab
Gitlab::Markdown::CommitReferenceFilter,
Gitlab::Markdown::LabelReferenceFilter,
- TaskList::Filter
+ Gitlab::Markdown::TaskListFilter
]
end
end
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/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
new file mode 100644
index 00000000000..9de2b24a9da
--- /dev/null
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -0,0 +1,128 @@
+require 'html/pipeline/filter'
+require 'uri'
+
+module Gitlab
+ module Markdown
+ # HTML filter that "fixes" relative links to files in a repository.
+ #
+ # Context options:
+ # :commit
+ # :project
+ # :project_wiki
+ # :ref
+ # :requested_path
+ class RelativeLinkFilter < HTML::Pipeline::Filter
+ def call
+ return doc unless linkable_files?
+
+ doc.search('a').each do |el|
+ process_link_attr el.attribute('href')
+ end
+
+ doc.search('img').each do |el|
+ process_link_attr el.attribute('src')
+ end
+
+ doc
+ end
+
+ protected
+
+ def linkable_files?
+ context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
+ end
+
+ def process_link_attr(html_attr)
+ return if html_attr.blank?
+
+ uri = URI(html_attr.value)
+ if uri.relative? && uri.path.present?
+ html_attr.value = rebuild_relative_uri(uri).to_s
+ end
+ rescue URI::Error
+ # noop
+ end
+
+ def rebuild_relative_uri(uri)
+ file_path = relative_file_path(uri.path)
+
+ uri.path = [
+ relative_url_root,
+ context[:project].path_with_namespace,
+ path_type(file_path),
+ ref || 'master', # assume that if no ref exists we can point to master
+ file_path
+ ].compact.join('/').squeeze('/').chomp('/')
+
+ uri
+ end
+
+ def relative_file_path(path)
+ nested_path = build_nested_path(path, context[:requested_path])
+ file_exists?(nested_path) ? nested_path : path
+ end
+
+ # Covering a special case, when the link is referencing file in the same
+ # directory.
+ # If we are at doc/api/README.md and the README.md contains relative
+ # links like [Users](users.md), this takes the request
+ # path(doc/api/README.md) and replaces the README.md with users.md so the
+ # path looks like doc/api/users.md.
+ # If we are at doc/api and the README.md shown in below the tree view
+ # this takes the request path(doc/api) and adds users.md so the path
+ # looks like doc/api/users.md
+ def build_nested_path(path, request_path)
+ return request_path if path.empty?
+ return path unless request_path
+
+ parts = request_path.split('/')
+ parts.pop if path_type(request_path) != 'tree'
+ parts.push(path).join('/')
+ end
+
+ def file_exists?(path)
+ return false if path.nil?
+ repository.blob_at(current_sha, path).present? ||
+ repository.tree(current_sha, path).entries.any?
+ end
+
+ # Get the type of the given path
+ #
+ # path - String path to check
+ #
+ # Examples:
+ #
+ # path_type('doc/README.md') # => 'blob'
+ # path_type('doc/logo.png') # => 'raw'
+ # path_type('doc/api') # => 'tree'
+ #
+ # Returns a String
+ def path_type(path)
+ if repository.tree(current_sha, path).entries.any?
+ 'tree'
+ elsif repository.blob_at(current_sha, path).try(:image?)
+ 'raw'
+ else
+ 'blob'
+ end
+ end
+
+ def current_sha
+ context[:commit].try(:id) ||
+ ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
+ end
+
+ def relative_url_root
+ Gitlab.config.gitlab.relative_url_root.presence || '/'
+ end
+
+ def ref
+ context[:ref]
+ end
+
+ def repository
+ context[:project].try(:repository)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index 9a154e0b2fe..74b3a8d274f 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -8,10 +8,37 @@ module Gitlab
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist
- whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
+ # 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
+ return if customized?(whitelist[:transformers])
- # Allow `class` and `id` on all elements
- whitelist[:attributes][:all].push('class', 'id')
+ # Allow code highlighting
+ whitelist[:attributes]['pre'] = %w(class)
+ whitelist[:attributes]['span'] = %w(class)
# Allow table alignment
whitelist[:attributes]['th'] = %w(style)
@@ -23,6 +50,9 @@ module Gitlab
# 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
@@ -33,6 +63,17 @@ module Gitlab
end
end
end
+
+ def clean_spans
+ lambda do |env|
+ return unless env[:node_name] == 'span'
+ return unless env[:node].has_attribute?('class')
+
+ unless has_ancestor?(env[:node], 'pre')
+ env[:node].remove_attribute('class')
+ end
+ end
+ 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/task_list_filter.rb b/lib/gitlab/markdown/task_list_filter.rb
new file mode 100644
index 00000000000..c6eb2e2bf6d
--- /dev/null
+++ b/lib/gitlab/markdown/task_list_filter.rb
@@ -0,0 +1,23 @@
+require 'task_list/filter'
+
+module Gitlab
+ module Markdown
+ # Work around a bug in the default TaskList::Filter that adds a `task-list`
+ # class to every list element, regardless of whether or not it contains a
+ # task list.
+ #
+ # This is a (hopefully) temporary fix, pending a new release of the
+ # task_list gem.
+ #
+ # See https://github.com/github/task_list/pull/60
+ class TaskListFilter < TaskList::Filter
+ def add_css_class(node, *new_class_names)
+ if new_class_names.include?('task-list')
+ super if node.children.any? { |c| c['class'] == 'task-list-item' }
+ else
+ super
+ end
+ end
+ end
+ end
+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/markdown_helper.rb b/lib/gitlab/markup_helper.rb
index 5e3cfc0585b..f99be969d3e 100644
--- a/lib/gitlab/markdown_helper.rb
+++ b/lib/gitlab/markup_helper.rb
@@ -1,5 +1,5 @@
module Gitlab
- module MarkdownHelper
+ module MarkupHelper
module_function
# Public: Determines if a given filename is compatible with GitHub::Markup.
@@ -8,8 +8,10 @@ module Gitlab
#
# Returns boolean
def markup?(filename)
- filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki
- .mediawiki .rst .adoc .asciidoc .asc))
+ gitlab_markdown?(filename) ||
+ asciidoc?(filename) ||
+ filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki
+ .mediawiki .rst))
end
# Public: Determines if a given filename is compatible with
@@ -22,8 +24,17 @@ module Gitlab
filename.downcase.end_with?(*%w(.mdown .md .markdown))
end
+ # Public: Determines if the given filename has AsciiDoc extension.
+ #
+ # filename - Filename string to check
+ #
+ # Returns boolean
+ def asciidoc?(filename)
+ filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
+ end
+
def previewable?(filename)
- gitlab_markdown?(filename) || markup?(filename)
+ markup?(filename)
end
end
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 2f5c217d764..ba5caed6131 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -5,7 +5,7 @@
#
module Gitlab
module OAuth
- class ForbiddenAction < StandardError; end
+ class SignupDisabledError < StandardError; end
class User
attr_accessor :auth_hash, :gl_user
@@ -99,7 +99,7 @@ module Gitlab
end
def unauthorized_to_create
- raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
+ raise SignupDisabledError
end
end
end
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/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/search_results.rb b/lib/gitlab/search_results.rb
index 75a3dfe37c3..06245374bc8 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -51,11 +51,23 @@ module Gitlab
end
def issues
- Issue.where(project_id: limit_project_ids).full_search(query).order('updated_at DESC')
+ issues = Issue.where(project_id: limit_project_ids)
+ if query =~ /#(\d+)\z/
+ issues = issues.where(iid: $1)
+ else
+ issues = issues.full_search(query)
+ end
+ issues.order('updated_at DESC')
end
def merge_requests
- MergeRequest.in_projects(limit_project_ids).full_search(query).order('updated_at DESC')
+ merge_requests = MergeRequest.in_projects(limit_project_ids)
+ if query =~ /[#!](\d+)\z/
+ merge_requests = merge_requests.where(iid: $1)
+ else
+ merge_requests = merge_requests.full_search(query)
+ end
+ merge_requests.order('updated_at DESC')
end
def default_scope
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index f33b2dedf4a..37232743325 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -7,7 +7,7 @@ module Gitlab
GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
# Wait 30 seconds for running jobs to finish during graceful shutdown
SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
- SHUTDOWN_SIGNAL = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL'] || 'SIGTERM').to_s
+ SHUTDOWN_SIGNAL = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL'] || 'SIGKILL').to_s
# Create a mutex used to ensure there will be only one thread waiting to
# shut Sidekiq down
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/omni_auth/request_forgery_protection.rb b/lib/omni_auth/request_forgery_protection.rb
new file mode 100644
index 00000000000..3557522d3c9
--- /dev/null
+++ b/lib/omni_auth/request_forgery_protection.rb
@@ -0,0 +1,66 @@
+# Protects OmniAuth request phase against CSRF.
+
+module OmniAuth
+ # Based on ActionController::RequestForgeryProtection.
+ class RequestForgeryProtection
+ def initialize(env)
+ @env = env
+ end
+
+ def request
+ @request ||= ActionDispatch::Request.new(@env)
+ end
+
+ def session
+ request.session
+ end
+
+ def reset_session
+ request.reset_session
+ end
+
+ def params
+ request.params
+ end
+
+ def call
+ verify_authenticity_token
+ end
+
+ def verify_authenticity_token
+ if !verified_request?
+ Rails.logger.warn "Can't verify CSRF token authenticity" if Rails.logger
+ handle_unverified_request
+ end
+ end
+
+ private
+
+ def protect_against_forgery?
+ ApplicationController.allow_forgery_protection
+ end
+
+ def request_forgery_protection_token
+ ApplicationController.request_forgery_protection_token
+ end
+
+ def forgery_protection_strategy
+ ApplicationController.forgery_protection_strategy
+ end
+
+ def verified_request?
+ !protect_against_forgery? || request.get? || request.head? ||
+ form_authenticity_token == params[request_forgery_protection_token] ||
+ form_authenticity_token == request.headers['X-CSRF-Token']
+ end
+
+ def handle_unverified_request
+ forgery_protection_strategy.new(self).handle_unverified_request
+ end
+
+ # Sets the token value for the current session.
+ def form_authenticity_token
+ session[:_csrf_token] ||= SecureRandom.base64(32)
+ end
+ end
+end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index bea66e6cdc1..2f7aff03c2a 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -7,9 +7,14 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
def initialize(template, color_scheme, options = {})
@template = template
@color_scheme = color_scheme
- @project = @template.instance_variable_get("@project")
@options = options.dup
+ @options.reverse_merge!(
+ # Handled further down the line by Gitlab::Markdown::SanitizationFilter
+ escape_html: false,
+ project: @template.instance_variable_get("@project")
+ )
+
super(options)
end
@@ -36,10 +41,6 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def postprocess(full_document)
- unless @template.instance_variable_get("@project_wiki") || @project.nil?
- full_document = h.create_relative_links(full_document)
- end
-
h.gfm_with_options(full_document, @options)
end
end
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/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index 3a225801ff2..5d4e0740373 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -1,6 +1,8 @@
desc 'Security check via brakeman'
task :brakeman do
- if system("brakeman --skip-files lib/backup/repository.rb -w3 -z")
+ # We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
+ # requests are welcome!
+ if system(*%W(brakeman --skip-files lib/backup/repository.rb -w3 -z))
puts 'Security check succeed'
else
puts 'Security check failed'
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 1a6303b6c82..75bd41f2838 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
+ task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check
gitlab:ldap:check
gitlab:app:check}
@@ -14,6 +13,7 @@ namespace :gitlab 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,58 +328,6 @@ 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"
task check: :environment do
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 20abb2fa500..7c98ad3144f 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -35,7 +35,7 @@ namespace :gitlab do
if project
puts " * #{project.name} (#{repo_path}) exists"
else
- user = User.admins.first
+ user = User.admins.reorder("id").first
project_params = {
name: name,
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index e835d6cb9b7..afdaba11cb0 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -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
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/jasmine.rake b/lib/tasks/jasmine.rake
index 9e2cceffa19..ac307a9e929 100644
--- a/lib/tasks/jasmine.rake
+++ b/lib/tasks/jasmine.rake
@@ -7,6 +7,6 @@ task jasmine: ['jasmine:ci']
namespace :jasmine do
task :ci do
- Rake::Task['spec:javascript'].invoke
+ Rake::Task['teaspoon'].invoke
end
end
diff --git a/lib/version_check.rb b/lib/version_check.rb
new file mode 100644
index 00000000000..ea23344948c
--- /dev/null
+++ b/lib/version_check.rb
@@ -0,0 +1,18 @@
+require "base64"
+
+# This class is used to build image URL to
+# check if it is a new version for update
+class VersionCheck
+ def data
+ { version: Gitlab::VERSION }
+ end
+
+ def url
+ encoded_data = Base64.urlsafe_encode64(data.to_json)
+ "#{host}?gitlab_info=#{encoded_data}"
+ end
+
+ def host
+ 'https://version.gitlab.com/check.png'
+ end
+end
diff --git a/public/404.html b/public/404.html
index 867f193a98f..a0106bc760d 100644
--- a/public/404.html
+++ b/public/404.html
@@ -1,14 +1,15 @@
<!DOCTYPE html>
<html>
<head>
- <title>The page you were looking for doesn't exist (404)</title>
+ <title>The page you're looking for could not be found (404)</title>
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>404</h1>
- <h3>The page you were looking for doesn't exist.</h3>
+ <h3>The page you're looking for could not be found.</h3>
<hr/>
- <p>You may have mistyped the address or the page may have moved.</p>
+ <p>Make sure the address is correct and that the page hasn't moved.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
</body>
</html>
diff --git a/public/422.html b/public/422.html
index b6c37ac5386..026997b48e3 100644
--- a/public/422.html
+++ b/public/422.html
@@ -1,16 +1,16 @@
<!DOCTYPE html>
<html>
<head>
- <title>The change you wanted was rejected (422)</title>
+ <title>The change you requested was rejected (422)</title>
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- This file lives in public/422.html -->
<h1>422</h1>
- <div>
- <h2>The change you wanted was rejected.</h2>
- <p>Maybe you tried to change something you didn't have access to.</p>
- </div>
+ <h3>The change you requested was rejected.</h3>
+ <hr />
+ <p>Make sure you have access to the thing you tried to change.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
</body>
</html>
diff --git a/public/500.html b/public/500.html
index c84b9e90e4b..08c11bbd05a 100644
--- a/public/500.html
+++ b/public/500.html
@@ -1,13 +1,14 @@
<!DOCTYPE html>
<html>
<head>
- <title>We're sorry, but something went wrong (500)</title>
+ <title>Something went wrong (500)</title>
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>500</h1>
- <h3>We're sorry, but something went wrong.</h3>
+ <h3>Whoops, something went wrong on our end.</h3>
<hr/>
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
<p>Please contact your GitLab administrator if this problem persists.</p>
</body>
</html>
diff --git a/public/502.html b/public/502.html
index d171eccc927..9480a928439 100644
--- a/public/502.html
+++ b/public/502.html
@@ -6,8 +6,9 @@
</head>
<body>
<h1>502</h1>
- <h3>GitLab is not responding.</h3>
+ <h3>Whoops, GitLab is taking too much time to respond.</h3>
<hr/>
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
<p>Please contact your GitLab administrator if this problem persists.</p>
</body>
</html>
diff --git a/public/deploy.html b/public/deploy.html
index e41ed76573d..1a41b772f3c 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -1,11 +1,17 @@
<!DOCTYPE html>
<html>
<head>
- <title>Deploy in progress. Please try again in a few minutes</title>
+ <title>Deploy in progress</title>
<link href="/static.css" media="screen" rel="stylesheet" type="text/css" />
</head>
+
<body>
- <h1><center><img src="/gitlab_logo.png"/></center>Deploy in progress</h1>
- <h3>Please try again in a few minutes or contact your administrator.</h3>
+ <h1>
+ <img src="/gitlab_logo.png" /><br />
+ Deploy in progress
+ </h1>
+ <h3>Please try again in a few minutes.</h3>
+ <hr/>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
</body>
</html>
diff --git a/public/static.css b/public/static.css
index c6f92ac01d9..0a2b6060d48 100644
--- a/public/static.css
+++ b/public/static.css
@@ -2,18 +2,24 @@ body {
color: #666;
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin:0;
+ margin: 0;
width: 800px;
margin: auto;
font-size: 14px;
}
+
h1 {
font-size: 56px;
line-height: 100px;
font-weight: normal;
color: #456;
}
-h2 { font-size: 24px; color: #666; line-height: 1.5em; }
+
+h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+}
h3 {
color: #456;
diff --git a/safe/public.pem b/safe/public.pem
deleted file mode 100644
index c5ffe20a5c7..00000000000
--- a/safe/public.pem
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnp2mUaLBoHFX127ysonX
-OihiGpI4098eFfH1iAxpKHIof0vs0jFF05IUScNXJZ1U3w8G1U/unY/wGGa3NzAb
-ZfDd22eOF6X2Gfiey6U4w9dFf0/UT5x1bphlpX357yh4O9oWWuNaWD062DTbOOsJ
-U6UW2U/sZAu/QScys0Nw+gJ58t93hb4jFq+nO5IAQc6g4S8ek5YvIXOshFEpF2in
-ZLbSYowx92+9GzfjvdQ7fk0Q2ssg0zfScVa6FY8n019osz0SC3wcSd/qicdfecpu
-7oycpd9YDqk4lufE1qVMOsgE8OO4KXMrByz2f+T0p/bH9zdBa5HYylf1T7i60hIL
-kQIDAQAB
------END PUBLIC KEY-----
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
new file mode 100644
index 00000000000..3dac134a731
--- /dev/null
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Groups::AvatarsController do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, owner: user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'destroy should remove avatar from DB' do
+ delete :destroy, group_id: group.path
+ @group = assigns(:group)
+ expect(@group.avatar.present?).to be_falsey
+ expect(@group).to be_valid
+ end
+end
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
new file mode 100644
index 00000000000..ad5855df0a4
--- /dev/null
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Profiles::AvatarsController do
+ let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
+
+ before do
+ sign_in(user)
+ controller.instance_variable_set(:@user, user)
+ end
+
+ it 'destroy should remove avatar from DB' do
+ delete :destroy
+ @user = assigns(:user)
+ expect(@user.avatar.present?).to be_falsey
+ expect(@user).to be_valid
+ end
+end
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
new file mode 100644
index 00000000000..65415f21e55
--- /dev/null
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe Profiles::TwoFactorAuthsController do
+ before do
+ # `user` should be defined within the action-specific describe blocks
+ sign_in(user)
+
+ allow(subject).to receive(:current_user).and_return(user)
+ end
+
+ describe 'GET new' do
+ let(:user) { create(:user) }
+
+ 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
+ code = double('qr code')
+ expect(subject).to receive(:build_qr_code).and_return(code)
+
+ get :new
+ expect(assigns[:qr_code]).to eq code
+ end
+ end
+
+ describe 'POST create' do
+ let(:user) { create(:user) }
+ let(:pin) { 'pin-code' }
+
+ def go
+ post :create, pin_code: pin
+ end
+
+ context 'with valid pin' do
+ before do
+ expect(user).to receive(:valid_otp?).with(pin).and_return(true)
+ end
+
+ it 'sets otp_required_for_login' do
+ go
+
+ user.reload
+ expect(user.otp_required_for_login).to eq true
+ end
+
+ it 'presents plaintext codes for the user to save' do
+ expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c))
+
+ go
+
+ expect(assigns[:codes]).to match_array %w(a b c)
+ end
+
+ it 'renders create' do
+ go
+ expect(response).to render_template(:create)
+ end
+ end
+
+ context 'with invalid pin' do
+ before do
+ expect(user).to receive(:valid_otp?).with(pin).and_return(false)
+ end
+
+ it 'assigns error' do
+ go
+ expect(assigns[:error]).to eq 'Invalid pin code'
+ end
+
+ it 'assigns qr_code' do
+ code = double('qr code')
+ expect(subject).to receive(:build_qr_code).and_return(code)
+
+ go
+ expect(assigns[:qr_code]).to eq code
+ end
+
+ it 'renders new' do
+ go
+ expect(response).to render_template(:new)
+ end
+ end
+ end
+
+ describe 'POST codes' do
+ let(:user) { create(:user, :two_factor) }
+
+ it 'presents plaintext codes for the user to save' do
+ expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c))
+
+ post :codes
+ expect(assigns[:codes]).to match_array %w(a b c)
+ end
+
+ it 'persists the generated codes' do
+ post :codes
+
+ user.reload
+ expect(user.otp_backup_codes).not_to be_empty
+ end
+ end
+
+ describe 'DELETE destroy' do
+ let(:user) { create(:user, :two_factor) }
+ 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.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.otp_backup_codes).to be_nil
+ expect(user.encrypted_otp_secret).to be_nil
+ end
+
+ it 'redirects to profile_account_path' do
+ delete :destroy
+
+ expect(response).to redirect_to(profile_account_path)
+ end
+ end
+end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
new file mode 100644
index 00000000000..e79b46a3504
--- /dev/null
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Projects::AvatarsController do
+ let(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ it 'destroy should remove avatar from DB' do
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id
+ expect(project.avatar.present?).to be_falsey
+ expect(project).to be_valid
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index 19f2935f30e..9373a7af024 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 }
@@ -28,6 +28,13 @@ FactoryGirl.define do
admin true
end
+ trait :two_factor do
+ before(:create) do |user|
+ user.otp_required_for_login = true
+ user.otp_secret = User.generate_otp_secret(32)
+ end
+ end
+
factory :omniauth_user do
ignore do
extern_uid '123456'
@@ -115,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/notes.rb b/spec/factories/notes.rb
index f1c33461b55..e1009d5916e 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -25,15 +25,16 @@ FactoryGirl.define do
note "Note"
author
- factory :note_on_commit, traits: [:on_commit]
- factory :note_on_commit_diff, traits: [:on_commit, :on_diff]
- factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
- factory :note_on_merge_request, traits: [:on_merge_request]
+ factory :note_on_commit, traits: [:on_commit]
+ factory :note_on_commit_diff, traits: [:on_commit, :on_diff]
+ factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
+ factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
- factory :note_on_project_snippet, traits: [:on_project_snippet]
+ factory :note_on_project_snippet, traits: [:on_project_snippet]
+ factory :system_note, traits: [:system]
trait :on_commit do
- project factory: :project
+ project
commit_id RepoHelpers.sample_commit.id
noteable_type "Commit"
end
@@ -43,7 +44,7 @@ FactoryGirl.define do
end
trait :on_merge_request do
- project factory: :project
+ project
noteable_id 1
noteable_type "MergeRequest"
end
@@ -58,6 +59,10 @@ FactoryGirl.define do
noteable_type "Snippet"
end
+ trait :system do
+ system true
+ end
+
trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 25862614d28..00906e8087a 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -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/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 133beba7b98..16d1ca55f8d 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -11,7 +11,7 @@ describe "GitLab Flavored Markdown", feature: true do
end
before do
- Commit.any_instance.stub(title: "fix ##{issue.iid}\n\nask @#{fred.username} for details")
+ Commit.any_instance.stub(title: "fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
end
let(:commit) { project.commit }
@@ -25,25 +25,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 +57,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 +83,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 +104,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/login_spec.rb b/spec/features/login_spec.rb
new file mode 100644
index 00000000000..046a9f6191d
--- /dev/null
+++ b/spec/features/login_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+feature 'Login' do
+ describe 'with two-factor authentication' do
+ context 'with valid username/password' do
+ let(:user) { create(:user, :two_factor) }
+
+ before do
+ login_with(user)
+ expect(page).to have_content('Two-factor Authentication')
+ end
+
+ def enter_code(code)
+ fill_in 'Two-factor authentication code', with: code
+ click_button 'Verify code'
+ end
+
+ it 'does not show a "You are already signed in." error message' do
+ enter_code(user.current_otp)
+ expect(page).not_to have_content('You are already signed in.')
+ end
+
+ context 'using one-time code' do
+ it 'allows login with valid code' do
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+
+ it 'blocks login with invalid code' do
+ enter_code('foo')
+ expect(page).to have_content('Invalid two-factor code')
+ end
+
+ it 'allows login with invalid code, then valid code' do
+ enter_code('foo')
+ expect(page).to have_content('Invalid two-factor code')
+
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'using backup code' do
+ let(:codes) { user.generate_otp_backup_codes! }
+
+ before do
+ expect(codes.size).to eq 10
+
+ # Ensure the generated codes get saved
+ user.save
+ end
+
+ context 'with valid code' do
+ it 'allows login' do
+ enter_code(codes.sample)
+ expect(current_path).to eq root_path
+ end
+
+ it 'invalidates the used code' do
+ expect { enter_code(codes.sample) }.
+ to change { user.reload.otp_backup_codes.size }.by(-1)
+ end
+ end
+
+ context 'with invalid code' do
+ it 'blocks login' do
+ code = codes.sample
+ expect(user.invalidate_otp_backup_code!(code)).to eq true
+
+ user.save!
+ expect(user.reload.otp_backup_codes.size).to eq 9
+
+ enter_code(code)
+ expect(page).to have_content('Invalid two-factor code.')
+ end
+ end
+ end
+ end
+ end
+
+ describe 'without two-factor authentication' do
+ let(:user) { create(:user) }
+
+ it 'allows basic login' do
+ login_with(user)
+ expect(current_path).to eq root_path
+ end
+
+ it 'does not show a "You are already signed in." error message' do
+ login_with(user)
+ expect(page).not_to have_content('You are already signed in.')
+ end
+
+ it 'blocks invalid login' do
+ user = create(:user, password: 'not-the-default')
+
+ login_with(user)
+ expect(page).to have_content('Invalid email or password.')
+ end
+ end
+end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 1746ce128e4..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`
@@ -60,12 +62,16 @@ describe 'GitLab Markdown' do
@feat.teardown
end
- # Given a header ID, goes to that element's parent (the header), then to its
- # second sibling (the body).
+ # Given a header ID, goes to that element's parent (the header itself), then
+ # its next sibling element (the body).
def get_section(id)
@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
@@ -119,18 +125,18 @@ describe 'GitLab Markdown' do
describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do
it 'uses a permissive whitelist' do
- expect(@doc).to have_selector('b#manual-b')
- expect(@doc).to have_selector('em#manual-em')
- expect(@doc).to have_selector("code#manual-code")
+ expect(@doc).to have_selector('b:contains("b tag")')
+ expect(@doc).to have_selector('em:contains("em tag")')
+ expect(@doc).to have_selector('code:contains("code tag")')
expect(@doc).to have_selector('kbd:contains("s")')
expect(@doc).to have_selector('strike:contains(Emoji)')
- expect(@doc).to have_selector('img#manual-img')
- expect(@doc).to have_selector('br#manual-br')
- expect(@doc).to have_selector('hr#manual-hr')
+ expect(@doc).to have_selector('img[src*="smile.png"]')
+ expect(@doc).to have_selector('br')
+ expect(@doc).to have_selector('hr')
end
it 'permits span elements' do
- expect(@doc).to have_selector('span#span-class-light.light')
+ expect(@doc).to have_selector('span:contains("span tag")')
end
it 'permits table alignment' do
@@ -144,13 +150,12 @@ describe 'GitLab Markdown' do
end
it 'removes `rel` attribute from links' do
- expect(@doc).to have_selector('a#a-rel-nofollow')
- expect(@doc).not_to have_selector('a#a-rel-nofollow[rel]')
+ body = get_section('sanitizationfilter')
+ expect(body).not_to have_selector('a[rel="bookmark"]')
end
it "removes `href` from `a` elements if it's fishy" do
- expect(@doc).to have_selector('a#a-href-javascript')
- expect(@doc).not_to have_selector('a#a-href-javascript[href]')
+ expect(@doc).not_to have_selector('a[href*="javascript"]')
end
end
@@ -228,11 +233,24 @@ describe 'GitLab Markdown' do
%w(code a kbd).each do |elem|
it "ignores links inside '#{elem}' element" do
- expect(@doc.at_css("#{elem}#autolink-#{elem}").child).to be_text
+ body = get_section('autolinkfilter')
+ expect(body).not_to have_selector("#{elem} a")
end
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/password_reset_spec.rb b/spec/features/password_reset_spec.rb
new file mode 100644
index 00000000000..a34efce09ef
--- /dev/null
+++ b/spec/features/password_reset_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+feature 'Password reset' do
+ def forgot_password
+ click_on 'Forgot your password?'
+ fill_in 'Email', with: user.email
+ click_button 'Reset password'
+ user.reload
+ end
+
+ def get_reset_token
+ mail = ActionMailer::Base.deliveries.last
+ body = mail.body.encoded
+ body.scan(/reset_password_token=(.+)\"/).flatten.first
+ end
+
+ def reset_password(password = 'password')
+ visit edit_user_password_path(reset_password_token: get_reset_token)
+
+ fill_in 'New password', with: password
+ fill_in 'Confirm new password', with: password
+ click_button 'Change your password'
+ end
+
+ describe 'with two-factor authentication' do
+ let(:user) { create(:user, :two_factor) }
+
+ it 'requires login after password reset' do
+ visit root_path
+
+ forgot_password
+ reset_password
+
+ expect(page).to have_content("Your password was changed successfully.")
+ expect(page).not_to have_content("You are now signed in.")
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+
+ describe 'without two-factor authentication' do
+ let(:user) { create(:user) }
+
+ it 'automatically logs in after password reset' do
+ visit root_path
+
+ forgot_password
+ reset_password
+
+ expect(current_path).to eq root_path
+ expect(page).to have_content("Your password was changed successfully. You are now signed in.")
+ 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/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 bc023ecf793..02ab46c905a 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -54,36 +54,34 @@ After the Markdown has been turned into HTML, it gets passed through...
### SanitizationFilter
-GitLab uses <a href="http://git.io/vfW8a" class="sanitize" id="sanitize-link">HTML::Pipeline::SanitizationFilter</a>
+GitLab uses <a href="http://git.io/vfW8a">HTML::Pipeline::SanitizationFilter</a>
to sanitize the generated HTML, stripping dangerous or unwanted tags.
Its default whitelist is pretty permissive. Check it:
-<b id="manual-b">This text is bold</b> and <em id="manual-em">this text is emphasized</em>.
+<b>b tag</b> and <em>em tag</em>.
-<code id="manual-code">echo "Hello, world!"</code>
+<code>code tag</code>
Press <kbd>s</kbd> to search.
-<strike>Emoji</strike> Plain old images! <img
-src="http://www.emoji-cheat-sheet.com/graphics/emojis/smile.png" width="20"
-height="20" id="manual-img" />
+<strike>Emoji</strike> Plain old images! <img src="http://www.emoji-cheat-sheet.com/graphics/emojis/smile.png" width="20" height="20" />
Here comes a line break:
-<br id="manual-br" />
+<br />
And a horizontal rule:
-<hr id="manual-hr" />
+<hr />
As permissive as it is, we've allowed even more stuff:
-<span class="light" id="span-class-light">Span elements</span>
+<span>span tag</span>
-<a href="#" rel="nofollow" id="a-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')" id="a-href-javascript">This is a link trying to be sneaky. It gets its link removed entirely.</a>
+<a href="javascript:alert('Hi')">This is a link trying to be sneaky. It gets its link removed entirely.</a>
### Escaping
@@ -125,65 +123,72 @@ These are all plain text that should get turned into links:
But it shouldn't autolink text inside certain tags:
-- <code id="autolink-code">http://about.gitlab.com/</code>
-- <a id="autolink-a">http://about.gitlab.com/</a>
-- <kbd id="autolink-kbd">http://about.gitlab.com/</kbd>
+- <code>http://about.gitlab.com/</code>
+- <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 d4cf6540080..3307ac776fc 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -261,12 +261,26 @@ describe ApplicationHelper do
end
end
- describe 'markup_render' do
+ describe 'render_markup' do
let(:content) { 'Noël' }
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')
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(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(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/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index dd4c1d645e2..e0be2df0e5e 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -106,6 +106,16 @@ describe DiffHelper do
end
end
+ describe 'unfold_class' do
+ it 'returns empty on false' do
+ expect(unfold_class(false)).to eq('')
+ end
+
+ it 'returns a class on true' do
+ expect(unfold_class(true)).to eq('unfold js-unfold')
+ end
+ end
+
describe 'diff_line_content' do
it 'should return non breaking space when line is empty' do
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
new file mode 100644
index 00000000000..7a3e38d7e63
--- /dev/null
+++ b/spec/helpers/emails_helper_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe EmailsHelper do
+ describe 'password_reset_token_valid_time' do
+ def validate_time_string(time_limit, expected_string)
+ Devise.reset_password_within = time_limit
+ expect(password_reset_token_valid_time).to eq(expected_string)
+ end
+
+ context 'when time limit is less than 2 hours' do
+ it 'should display the time in hours using a singular unit' do
+ validate_time_string(1.hour, '1 hour')
+ end
+ end
+
+ context 'when time limit is 2 or more hours' do
+ it 'should display the time in hours using a plural unit' do
+ validate_time_string(2.hours, '2 hours')
+ end
+ end
+
+ context 'when time limit contains fractions of an hour' do
+ it 'should round down to the nearest hour' do
+ validate_time_string(96.minutes, '1 hour')
+ end
+ end
+
+ context 'when time limit is 24 or more hours' do
+ it 'should display the time in days using a singular unit' do
+ validate_time_string(24.hours, '1 day')
+ end
+ end
+
+ context 'when time limit is 2 or more days' do
+ it 'should display the time in days using a plural unit' do
+ validate_time_string(2.days, '2 days')
+ end
+ end
+
+ context 'when time limit contains fractions of a day' do
+ it 'should round down to the nearest day' do
+ validate_time_string(57.hours, '2 days')
+ end
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 2f67879efdc..bbb434638ce 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -21,12 +21,12 @@ describe GitlabMarkdownHelper do
describe "#gfm" do
it "should forward HTML options to links" do
- expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')).
+ expect(gfm("Fixed in #{commit.id}", { project: @project }, class: 'foo')).
to have_selector('a.gfm.foo')
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,82 +90,15 @@ 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
- end
-
- describe "#markdown" do
- # TODO (rspeicher): These belong in a relative link filter spec
- context 'relative links' do
- context 'with a valid repository' do
- before do
- @repository = project.repository
- @ref = 'markdown'
- end
-
- it "should handle relative urls for a file in master" do
- actual = "[GitLab API doc](doc/api/README.md)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle relative urls for a file in master with an anchor" do
- actual = "[GitLab API doc](doc/api/README.md#section)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md#section\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should not handle relative urls for the current file with an anchor" do
- actual = "[GitLab API doc](#section)\n"
- expected = "<p><a href=\"#section\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle relative urls for a directory in master" do
- actual = "[GitLab API doc](doc/api)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle absolute urls" do
- actual = "[GitLab](https://www.gitlab.com)\n"
- expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle relative urls in reference links for a file in master" do
- actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle relative urls in reference links for a directory in master" do
- actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc directory</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
-
- it "should not handle malformed relative urls in reference links for a file in master" do
- actual = "[GitLab readme]: doc/api/README.md\n"
- expected = ""
- expect(markdown(actual)).to match(expected)
- end
- end
- context 'with an empty repository' do
- before do
- @project = create(:empty_project)
- @repository = @project.repository
- end
-
- it "should not touch relative urls" do
- actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
- expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
- 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
@@ -183,6 +116,14 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
+ it "should use Asciidoctor for asciidoc files" do
+ allow(@wiki).to receive(:format).and_return(:asciidoc)
+
+ expect(helper).to receive(:asciidoc).with('wiki content')
+
+ helper.render_wiki_content(@wiki)
+ end
+
it "should use the Gollum renderer for all other file types" do
allow(@wiki).to receive(:format).and_return(:rdoc)
formatted_content_stub = double('formatted_content')
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/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/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..db5abe0cae3
--- /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-issue-update{action: '/foo'}
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..c4329b8f94a
--- /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-merge-request-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/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
index 3ebc4a4eed5..a4735af0343 100644
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -1,34 +1,23 @@
-#= require jquery
-#= require jasmine-fixture
#= require merge_request
+window.disableButtonIfEmptyField = -> null
+
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/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/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
new file mode 100644
index 00000000000..23f83339ec5
--- /dev/null
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+require 'nokogiri'
+
+module Gitlab
+ describe Asciidoc do
+
+ let(:input) { '<b>ascii</b>' }
+ let(:context) { {} }
+ let(:html) { 'H<sub>2</sub>O' }
+
+ 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 }
+
+ expect(Asciidoctor).to receive(:convert)
+ .with(input, expected_asciidoc_opts).and_return(html)
+
+ expect( render(input, context) ).to eql html
+ end
+
+ context "with asciidoc_opts" do
+
+ 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'] }
+
+ expect(Asciidoctor).to receive(:convert)
+ .with(input, expected_asciidoc_opts).and_return(html)
+
+ render(input, context, asciidoc_opts)
+ end
+ end
+ end
+
+ context "with project in context" do
+
+ let(:context) { {project: create(:project)} }
+
+ it "should filter converted input via HTML pipeline and return result" do
+ filtered_html = '<b>ASCII</b>'
+
+ allow(Asciidoctor).to receive(:convert).and_return(html)
+ expect_any_instance_of(HTML::Pipeline).to receive(:call)
+ .with(html, context)
+ .and_return(output: Nokogiri::HTML.fragment(filtered_html))
+
+ expect( render('foo', context) ).to eql filtered_html
+ end
+ end
+
+ def render(*args)
+ described_class.render(*args)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
index d0aad54f677..42c9946d2a9 100644
--- a/spec/lib/gitlab/backend/grack_auth_spec.rb
+++ b/spec/lib/gitlab/backend/grack_auth_spec.rb
@@ -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/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/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 39be9d64644..c7291689e32 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -115,18 +115,10 @@ describe Gitlab::GitAccess do
let(:actor) { key }
context 'pull code' do
- context 'allowed' do
- before { key.projects << project }
- subject { access.download_access_check }
-
- it { expect(subject.allowed?).to be_truthy }
- end
-
- context 'denied' do
- subject { access.download_access_check }
+ before { key.projects << project }
+ subject { access.download_access_check }
- it { expect(subject.allowed?).to be_falsey }
- end
+ it { expect(subject.allowed?).to be_truthy }
end
end
end
diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
deleted file mode 100644
index ab613193f41..00000000000
--- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::MarkdownHelper do
- describe '#markup?' do
- %w(textile rdoc org creole wiki
- mediawiki rst adoc asciidoc asc).each do |type|
- it "returns true for #{type} files" do
- expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy
- end
- end
-
- it 'returns false when given a non-markup filename' do
- expect(Gitlab::MarkdownHelper.markup?('README.rb')).not_to be_truthy
- end
- end
-
- describe '#gitlab_markdown?' do
- %w(mdown md markdown).each do |type|
- it "returns true for #{type} files" do
- expect(Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}")).to be_truthy
- end
- end
-
- it 'returns false when given a non-markdown filename' do
- expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy
- end
- end
-end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 707a0521ab3..2189e313d6a 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::LDAP::Access do
context 'when the user is found' do
before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) }
- context 'and the user is diabled via active directory' do
+ context 'and the user is disabled via active directory' do
before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) }
it { is_expected.to be_falsey }
@@ -36,9 +36,28 @@ describe Gitlab::LDAP::Access do
it { is_expected.to be_truthy }
- it "should unblock user in GitLab" do
- access.allowed?
- user.should_not be_blocked
+ context 'when auto-created users are blocked' do
+
+ before do
+ Gitlab::LDAP::Config.any_instance.stub(block_auto_created_users: true)
+ end
+
+ it "does not unblock user in GitLab" do
+ access.allowed?
+ user.should 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)
+ end
+
+ it "should unblock user in GitLab" do
+ access.allowed?
+ user.should_not be_blocked
+ end
end
end
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index 0bbdc11a979..a14cb2da089 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
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..cf3337b1ba1 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,60 +93,41 @@ 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.name.reverse}")
+ it 'ignores invalid label names' do
+ exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
- expect(filter(act).to_html).to eq exp
- end
+ expect(filter(act).to_html).to eq exp
end
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
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/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
new file mode 100644
index 00000000000..5ee5310825d
--- /dev/null
+++ b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe RelativeLinkFilter do
+ def filter(doc)
+ described_class.call(doc, {
+ commit: project.commit,
+ project: project,
+ project_wiki: project_wiki,
+ ref: ref,
+ requested_path: requested_path
+ })
+ end
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ def link(path)
+ %(<a href="#{path}">#{path}</a>)
+ end
+
+ let(:project) { create(:project) }
+ let(:project_path) { project.path_with_namespace }
+ let(:ref) { 'markdown' }
+ let(:project_wiki) { nil }
+ let(:requested_path) { '/' }
+
+ shared_examples :preserve_unchanged do
+ it 'does not modify any relative URL in anchor' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq 'README.md'
+ end
+
+ it 'does not modify any relative URL in image' do
+ doc = filter(image('files/images/logo-black.png'))
+ expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+ end
+ end
+
+ shared_examples :relative_to_requested do
+ it 'rebuilds URL relative to the requested path' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
+ end
+ end
+
+ context 'with a project_wiki' do
+ let(:project_wiki) { double('ProjectWiki') }
+ include_examples :preserve_unchanged
+ end
+
+ context 'without a repository' do
+ let(:project) { create(:empty_project) }
+ include_examples :preserve_unchanged
+ end
+
+ context 'with an empty repository' do
+ let(:project) { create(:project_empty_repo) }
+ include_examples :preserve_unchanged
+ end
+
+ it 'does not raise an exception on invalid URIs' do
+ act = link("://foo")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ context 'with a valid repository' do
+ it 'rebuilds relative URL for a file in the repo' do
+ doc = filter(link('doc/api/README.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
+ it 'rebuilds relative URL for a file in the repo with an anchor' do
+ doc = filter(link('README.md#section'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/README.md#section"
+ end
+
+ it 'rebuilds relative URL for a directory in the repo' do
+ doc = filter(link('doc/api/'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/tree/#{ref}/doc/api"
+ end
+
+ it 'rebuilds relative URL for an image in the repo' do
+ doc = filter(link('files/images/logo-black.png'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+ end
+
+ it 'does not modify relative URL with an anchor only' do
+ doc = filter(link('#section-1'))
+ expect(doc.at_css('a')['href']).to eq '#section-1'
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ context 'when requested path is a file in the repo' do
+ let(:requested_path) { 'doc/api/README.md' }
+ include_examples :relative_to_requested
+ end
+
+ context 'when requested path is a directory in the repo' do
+ let(:requested_path) { 'doc/api' }
+ include_examples :relative_to_requested
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
index ab909a68635..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
@@ -29,19 +27,36 @@ module Gitlab::Markdown
exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
expect(filter(act).to_html).to eq exp
end
+
+ it 'sanitizes `class` attribute on any element' do
+ act = %q{<strong class="foo">Strong</strong>}
+ expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
+ end
+
+ it 'sanitizes `id` attribute on any element' do
+ act = %q{<em id="foo">Emphasis</em>}
+ expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
+ end
end
describe 'custom whitelist' do
- it 'allows `class` attribute on any element' do
- exp = act = %q{<strong class="foo">Strong</strong>}
- expect(filter(act).to_html).to eq exp
+ 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 `id` attribute on any element' do
- exp = act = %q{<em id="foo">Emphasis</em>}
+ 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
end
+ it 'sanitizes `class` attribute from non-highlight spans' do
+ act = %q{<span class="k">def</span>}
+ expect(filter(act).to_html).to eq %q{<span>def</span>}
+ end
+
it 'allows `style` attribute on table elements' do
html = <<-HTML.strip_heredoc
<table>
@@ -77,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
new file mode 100644
index 00000000000..94f39cc966e
--- /dev/null
+++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe TaskListFilter do
+ include FilterSpecHelper
+
+ it 'does not apply `task-list` class to non-task lists' do
+ exp = act = %(<ul><li>Item</li></ul>)
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+end
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/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
new file mode 100644
index 00000000000..7e716e866b1
--- /dev/null
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::MarkupHelper do
+ describe '#markup?' do
+ %w(textile rdoc org creole wiki
+ mediawiki rst adoc ad asciidoc mdown md markdown).each do |type|
+ it "returns true for #{type} files" do
+ expect(Gitlab::MarkupHelper.markup?("README.#{type}")).to be_truthy
+ end
+ end
+
+ it 'returns false when given a non-markup filename' do
+ expect(Gitlab::MarkupHelper.markup?('README.rb')).not_to be_truthy
+ end
+ end
+
+ describe '#gitlab_markdown?' do
+ %w(mdown md markdown).each do |type|
+ it "returns true for #{type} files" do
+ expect(Gitlab::MarkupHelper.gitlab_markdown?("README.#{type}")).to be_truthy
+ end
+ end
+
+ it 'returns false when given a non-markdown filename' do
+ expect(Gitlab::MarkupHelper.gitlab_markdown?('README.rb')).not_to be_truthy
+ end
+ end
+
+ describe '#asciidoc?' do
+ %w(adoc ad asciidoc ADOC).each do |type|
+ it "returns true for #{type} files" do
+ expect(Gitlab::MarkupHelper.asciidoc?("README.#{type}")).to be_truthy
+ end
+ end
+
+ it 'returns false when given a non-asciidoc filename' do
+ expect(Gitlab::MarkupHelper.asciidoc?('README.rb')).not_to be_truthy
+ end
+ 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/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index ce3ea6c260a..baa4bd0f28f 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -20,5 +20,20 @@ describe Gitlab::Upgrader do
upgrader.stub(current_version_raw: "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/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/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index dbcf7286e45..c40ae7b5703 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -5,6 +5,8 @@ describe Notify do
include EmailSpec::Matchers
include RepoHelpers
+ new_user_address = 'newguy@example.com'
+
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
@@ -55,18 +57,9 @@ describe Notify do
end
end
- describe 'for new users, the email' do
- let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) }
-
- token = 'kETLwRaayvigPq_x3SNM'
-
- subject { Notify.new_user_email(new_user.id, token) }
-
- it_behaves_like 'an email sent from GitLab'
-
+ shared_examples 'a new user email' do |user_email, site_path|
it 'is sent to the new user' do
- is_expected.to deliver_to new_user.email
+ is_expected.to deliver_to user_email
end
it 'has the correct subject' do
@@ -74,9 +67,25 @@ describe Notify do
end
it 'contains the new user\'s login name' do
- is_expected.to have_body_text /#{new_user.email}/
+ is_expected.to have_body_text /#{user_email}/
end
+ it 'includes a link to the site' do
+ is_expected.to have_body_text /#{site_path}/
+ end
+ end
+
+ describe 'for new users, the email' do
+ let(:example_site_path) { root_path }
+ let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
+
+ token = 'kETLwRaayvigPq_x3SNM'
+
+ subject { Notify.new_user_email(new_user.id, token) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'a new user email', new_user_address
+
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
@@ -88,39 +97,26 @@ describe Notify do
)
end
- it 'includes a link to the site' do
- is_expected.to have_body_text /#{example_site_path}/
+ it 'explains the reset link expiration' do
+ is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
+ is_expected.to have_body_text(new_user_password_url)
+ is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") }
+ let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
-
- it 'is sent to the new user' do
- is_expected.to deliver_to new_user.email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Account was created for you$/i
- end
-
- it 'contains the new user\'s login name' do
- is_expected.to have_body_text /#{new_user.email}/
- end
+ it_behaves_like 'a new user email', new_user_address
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
-
- it 'includes a link to the site' do
- is_expected.to have_body_text /#{example_site_path}/
- end
end
describe 'user added ssh key' do
@@ -189,7 +185,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) }
@@ -277,7 +273,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) }
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 31ee3e99cad..e7fb43ff335 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' }
@@ -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}"
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ad2ac143d97..27eb02a870b 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
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/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/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 4e0d50d7f3f..dae7e399cfb 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
require 'spec_helper'
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 96bf74d45da..fb5111dd9f5 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
require "spec_helper"
@@ -21,4 +22,37 @@ describe ServiceHook do
describe "Associations" do
it { is_expected.to belong_to :service }
end
+
+ describe "execute" do
+ before(:each) do
+ @service_hook = create(:service_hook)
+ @data = { project_id: 1, data: {}}
+
+ WebMock.stub_request(:post, @service_hook.url)
+ end
+
+ 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'}
+ ).once
+ end
+
+ it "POSTs the data as JSON" do
+ json = @data.to_json
+
+ @service_hook.execute(@data)
+ expect(WebMock).to have_requested(:post, @service_hook.url).with(
+ 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
+ end
+ end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 810b311a40b..edb21fc2e47 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
require "spec_helper"
@@ -26,32 +27,47 @@ describe SystemHook do
it "project_create hook" do
Projects::CreateService.new(create(:user), name: 'empty').execute
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_create/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /project_create/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ ).once
end
it "project_destroy hook" do
user = create(:user)
project = create(:empty_project, namespace: user.namespace)
Projects::DestroyService.new(project, user, {}).execute
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /project_destroy/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ ).once
end
it "user_create hook" do
create(:user)
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_create/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /user_create/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ ).once
end
it "user_destroy hook" do
user = create(:user)
user.destroy
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /user_destroy/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ ).once
end
it "project_create hook" do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
+ 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'}
+ ).once
end
it "project_destroy hook" do
@@ -59,13 +75,17 @@ describe SystemHook do
project = create(:project)
project.team << [user, :master]
project.project_members.destroy_all
- expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
+ 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'}
+ ).once
end
it 'group create hook' do
create(:group)
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /group_create/
+ body: /group_create/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
).once
end
@@ -73,7 +93,8 @@ describe SystemHook do
group = create(:group)
group.destroy
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /group_destroy/
+ body: /group_destroy/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
).once
end
@@ -82,7 +103,8 @@ describe SystemHook do
user = create(:user)
group.add_user(user, Gitlab::Access::MASTER)
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_add_to_group/
+ body: /user_add_to_group/,
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
).once
end
@@ -92,7 +114,8 @@ describe SystemHook do
group.add_user(user, Gitlab::Access::MASTER)
group.group_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_remove_from_group/
+ body: /user_remove_from_group/,
+ 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 67ec9193ad7..4c3f0cbcbbf 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -13,6 +13,7 @@
# issues_events :boolean default(FALSE), not null
# merge_requests_events :boolean default(FALSE), not null
# tag_push_events :boolean default(FALSE)
+# note_events :boolean default(FALSE), not null
#
require 'spec_helper'
@@ -52,22 +53,26 @@ describe ProjectHook do
end
it "POSTs to the web hook URL" do
- @project_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @project_hook.url).once
+ @project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, @project_hook.url).with(
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}
+ ).once
end
it "POSTs the data as JSON" do
json = @data.to_json
- @project_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @project_hook.url).with(body: json).once
+ @project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, @project_hook.url).with(
+ headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}
+ ).once
end
it "catches exceptions" do
expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
expect {
- @project_hook.execute(@data)
+ @project_hook.execute(@data, 'push_hooks')
}.to raise_error
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 20d823b40e5..614b648bb52 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
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/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 97b8abc49dd..0465aa34843 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)
@@ -108,7 +129,7 @@ describe MergeRequest do
it 'detects issues mentioned in the description' do
issue2 = create(:issue, project: subject.project)
- subject.description = "Closes ##{issue2.iid}"
+ subject.description = "Closes #{issue2.to_reference}"
subject.project.stub(default_branch: subject.target_branch)
expect(subject.closes_issues).to include(issue2)
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 45171e1bf64..eb73aa763fc 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -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
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 4a6bfdb2910..ddacba58261 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -20,68 +20,88 @@
require 'spec_helper'
describe Note do
- describe "Associations" do
+ describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:noteable) }
it { is_expected.to belong_to(:author).class_name('User') }
end
- describe "Mass assignment" do
- end
-
- describe "Validation" do
+ describe 'validation' do
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
end
- describe "Voting score" do
- let(:project) { create(:project) }
+ describe '#votable?' do
+ it 'is true for issue notes' do
+ note = build(:note_on_issue)
+ expect(note).to be_votable
+ end
+
+ it 'is true for merge request notes' do
+ note = build(:note_on_merge_request)
+ expect(note).to be_votable
+ end
+
+ it 'is false for merge request diff notes' do
+ note = build(:note_on_merge_request_diff)
+ expect(note).not_to be_votable
+ end
+
+ it 'is false for commit notes' do
+ note = build(:note_on_commit)
+ expect(note).not_to be_votable
+ end
- it "recognizes a neutral note" do
- note = create(:votable_note, note: "This is not a +1 note")
+ it 'is false for commit diff notes' do
+ note = build(:note_on_commit_diff)
+ expect(note).not_to be_votable
+ end
+ end
+
+ describe 'voting score' do
+ it 'recognizes a neutral note' do
+ note = build(:votable_note, note: 'This is not a +1 note')
expect(note).not_to be_upvote
expect(note).not_to be_downvote
end
- it "recognizes a neutral emoji note" do
+ it 'recognizes a neutral emoji note' do
note = build(:votable_note, note: "I would :+1: this, but I don't want to")
expect(note).not_to be_upvote
expect(note).not_to be_downvote
end
- it "recognizes a +1 note" do
- note = create(:votable_note, note: "+1 for this")
+ it 'recognizes a +1 note' do
+ note = build(:votable_note, note: '+1 for this')
expect(note).to be_upvote
end
- it "recognizes a +1 emoji as a vote" do
- note = build(:votable_note, note: ":+1: for this")
+ it 'recognizes a +1 emoji as a vote' do
+ note = build(:votable_note, note: ':+1: for this')
expect(note).to be_upvote
end
- it "recognizes a thumbsup emoji as a vote" do
- note = build(:votable_note, note: ":thumbsup: for this")
+ it 'recognizes a thumbsup emoji as a vote' do
+ note = build(:votable_note, note: ':thumbsup: for this')
expect(note).to be_upvote
end
- it "recognizes a -1 note" do
- note = create(:votable_note, note: "-1 for this")
+ it 'recognizes a -1 note' do
+ note = build(:votable_note, note: '-1 for this')
expect(note).to be_downvote
end
- it "recognizes a -1 emoji as a vote" do
- note = build(:votable_note, note: ":-1: for this")
+ it 'recognizes a -1 emoji as a vote' do
+ note = build(:votable_note, note: ':-1: for this')
expect(note).to be_downvote
end
- it "recognizes a thumbsdown emoji as a vote" do
- note = build(:votable_note, note: ":thumbsdown: for this")
+ it 'recognizes a thumbsdown emoji as a vote' do
+ note = build(:votable_note, note: ':thumbsdown: for this')
expect(note).to be_downvote
end
end
- let(:project) { create(:project) }
-
describe "Commit notes" do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
@@ -100,10 +120,6 @@ describe Note do
it "should be recognized by #for_commit?" do
expect(note).to be_for_commit
end
-
- it "should not be votable" do
- expect(note).not_to be_votable
- end
end
describe "Commit diff line notes" do
@@ -128,461 +144,7 @@ describe Note do
end
end
- describe "Issue notes" do
- let!(:note) { create(:note_on_issue, note: "+1 from me") }
-
- it "should not be votable" do
- expect(note).to be_votable
- end
- end
-
- describe "Merge request notes" do
- let!(:note) { create(:note_on_merge_request, note: "+1 from me") }
-
- it "should be votable" do
- expect(note).to be_votable
- end
- end
-
- describe "Merge request diff line notes" do
- let!(:note) { create(:note_on_merge_request_diff, note: "+1 from me") }
-
- it "should not be votable" do
- expect(note).not_to be_votable
- end
- end
-
- describe '#create_status_change_note' do
- let(:project) { create(:project) }
- let(:thing) { create(:issue, project: project) }
- let(:author) { create(:user) }
- let(:status) { 'new_status' }
-
- subject { Note.create_status_change_note(thing, project, author, status, nil) }
-
- it 'creates and saves a Note' do
- is_expected.to be_a Note
- expect(subject.id).not_to be_nil
- end
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(thing) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(thing.project) }
- end
-
- describe '#author' do
- subject { super().author }
- it { is_expected.to eq(author) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("Status changed to #{status}") }
- end
-
- it 'appends a back-reference if a closing mentionable is supplied' do
- commit = double('commit', gfm_reference: 'commit 123456')
- n = Note.create_status_change_note(thing, project, author, status, commit)
-
- expect(n.note).to eq("Status changed to #{status} by commit 123456")
- end
- end
-
- describe '#create_assignee_change_note' do
- let(:project) { create(:project) }
- let(:thing) { create(:issue, project: project) }
- let(:author) { create(:user) }
- let(:assignee) { create(:user, username: "assigned_user") }
-
- subject { Note.create_assignee_change_note(thing, project, author, assignee) }
-
- context 'creates and saves a Note' do
- it { is_expected.to be_a Note }
-
- describe '#id' do
- subject { super().id }
- it { is_expected.not_to be_nil }
- end
- end
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(thing) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(thing.project) }
- end
-
- describe '#author' do
- subject { super().author }
- it { is_expected.to eq(author) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq('Reassigned to @assigned_user') }
- end
-
- context 'assignee is removed' do
- let(:assignee) { nil }
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq('Assignee removed') }
- end
- end
- end
-
- describe '#create_labels_change_note' do
- let(:project) { create(:project) }
- let(:thing) { create(:issue, project: project) }
- let(:author) { create(:user) }
- let(:label1) { create(:label) }
- let(:label2) { create(:label) }
- let(:added_labels) { [label1, label2] }
- let(:removed_labels) { [] }
-
- subject { Note.create_labels_change_note(thing, project, author, added_labels, removed_labels) }
-
- context 'creates and saves a Note' do
- it { is_expected.to be_a Note }
-
- describe '#id' do
- subject { super().id }
- it { is_expected.not_to be_nil }
- end
- end
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(thing) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(thing.project) }
- end
-
- describe '#author' do
- subject { super().author }
- it { is_expected.to eq(author) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("Added ~#{label1.id} ~#{label2.id} labels") }
- end
-
- context 'label is removed' do
- let(:added_labels) { [label1] }
- let(:removed_labels) { [label2] }
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("Added ~#{label1.id} and removed ~#{label2.id} labels") }
- end
- end
- end
-
- describe '#create_milestone_change_note' do
- let(:project) { create(:project) }
- let(:thing) { create(:issue, project: project) }
- let(:milestone) { create(:milestone, project: project, title: "first_milestone") }
- let(:author) { create(:user) }
-
- subject { Note.create_milestone_change_note(thing, project, author, milestone) }
-
- context 'creates and saves a Note' do
- it { is_expected.to be_a Note }
-
- describe '#id' do
- subject { super().id }
- it { is_expected.not_to be_nil }
- end
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(thing.project) }
- end
-
- describe '#author' do
- subject { super().author }
- it { is_expected.to eq(author) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("Milestone changed to first_milestone") }
- end
- end
-
- describe '#create_cross_reference_note' do
- let(:project) { create(:project) }
- let(:author) { create(:user) }
- let(:issue) { create(:issue, project: project) }
- let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
- let(:commit) { project.commit }
-
- # Test all of {issue, merge request, commit} in both the referenced and referencing
- # roles, to ensure that the correct information can be inferred from any argument.
-
- context 'issue from a merge request' do
- subject { Note.create_cross_reference_note(issue, mergereq, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(issue) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(issue.project) }
- end
-
- describe '#author' do
- subject { super().author }
- it { is_expected.to eq(author) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") }
- end
- end
-
- context 'issue from a commit' do
- subject { Note.create_cross_reference_note(issue, commit, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(issue) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in commit #{commit.sha}") }
- end
- end
-
- context 'merge request from an issue' do
- subject { Note.create_cross_reference_note(mergereq, issue, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(mergereq) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(mergereq.project) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in issue ##{issue.iid}") }
- end
- end
-
- context 'commit from a merge request' do
- subject { Note.create_cross_reference_note(commit, mergereq, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable' do
- subject { super().noteable }
- it { is_expected.to eq(commit) }
- end
-
- describe '#project' do
- subject { super().project }
- it { is_expected.to eq(project) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") }
- end
- end
-
- context 'commit contained in a merge request' do
- subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author) }
-
- it { is_expected.to be_nil }
- end
-
- context 'commit from issue' do
- subject { Note.create_cross_reference_note(commit, issue, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable_type' do
- subject { super().noteable_type }
- it { is_expected.to eq("Commit") }
- end
-
- describe '#noteable_id' do
- subject { super().noteable_id }
- it { is_expected.to be_nil }
- end
-
- describe '#commit_id' do
- subject { super().commit_id }
- it { is_expected.to eq(commit.id) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in issue ##{issue.iid}") }
- end
- end
-
- context 'commit from commit' do
- let(:parent_commit) { commit.parents.first }
- subject { Note.create_cross_reference_note(commit, parent_commit, author) }
-
- it { is_expected.to be_valid }
-
- describe '#noteable_type' do
- subject { super().noteable_type }
- it { is_expected.to eq("Commit") }
- end
-
- describe '#noteable_id' do
- subject { super().noteable_id }
- it { is_expected.to be_nil }
- end
-
- describe '#commit_id' do
- subject { super().commit_id }
- it { is_expected.to eq(commit.id) }
- end
-
- describe '#note' do
- subject { super().note }
- it { is_expected.to eq("mentioned in commit #{parent_commit.id}") }
- end
- end
- end
-
- describe '#cross_reference_exists?' do
- let(:project) { create :project }
- let(:author) { create :user }
- let(:issue) { create :issue }
- let(:commit0) { project.commit }
- let(:commit1) { project.commit('HEAD~2') }
-
- before do
- Note.create_cross_reference_note(issue, commit0, author)
- end
-
- it 'detects if a mentionable has already been mentioned' do
- expect(Note.cross_reference_exists?(issue, commit0)).to be_truthy
- end
-
- it 'detects if a mentionable has not already been mentioned' do
- expect(Note.cross_reference_exists?(issue, commit1)).to be_falsey
- end
-
- context 'commit on commit' do
- before do
- Note.create_cross_reference_note(commit0, commit1, author)
- end
-
- it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy }
- it { expect(Note.cross_reference_exists?(commit1, commit0)).to be_falsey }
- end
-
- context 'legacy note with Markdown emphasis' do
- let(:issue2) { create :issue, project: project }
- let!(:note) do
- create :note, system: true, noteable_id: issue2.id,
- noteable_type: "Issue", note: "_mentioned in issue " \
- "#{issue.project.path_with_namespace}##{issue.iid}_"
- end
-
- it 'detects if a mentionable with emphasis has been mentioned' do
- expect(Note.cross_reference_exists?(issue2, issue)).to be_truthy
- end
- end
- end
-
- describe '#cross_references_with_underscores?' do
- let(:project) { create :project, path: "first_project" }
- let(:second_project) { create :project, path: "second_project" }
-
- let(:author) { create :user }
- let(:issue0) { create :issue, project: project }
- let(:issue1) { create :issue, project: second_project }
- let!(:note) { Note.create_cross_reference_note(issue0, issue1, author) }
-
- it 'detects if a mentionable has already been mentioned' do
- expect(Note.cross_reference_exists?(issue0, issue1)).to be_truthy
- end
-
- it 'detects if a mentionable has not already been mentioned' do
- expect(Note.cross_reference_exists?(issue1, issue0)).to be_falsey
- end
-
- it 'detects that text has underscores' do
- expect(note.note).to eq("mentioned in issue #{second_project.path_with_namespace}##{issue1.iid}")
- end
- end
-
- describe '#system?' do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:other) { create(:issue, project: project) }
- let(:author) { create(:user) }
- let(:assignee) { create(:user) }
- let(:label) { create(:label) }
- let(:milestone) { create(:milestone) }
-
- it 'should recognize user-supplied notes as non-system' do
- @note = create(:note_on_issue)
- expect(@note).not_to be_system
- end
-
- it 'should identify status-change notes as system notes' do
- @note = Note.create_status_change_note(issue, project, author, 'closed', nil)
- expect(@note).to be_system
- end
-
- it 'should identify cross-reference notes as system notes' do
- @note = Note.create_cross_reference_note(issue, other, author)
- expect(@note).to be_system
- end
-
- it 'should identify assignee-change notes as system notes' do
- @note = Note.create_assignee_change_note(issue, project, author, assignee)
- expect(@note).to be_system
- end
-
- it 'should identify label-change notes as system notes' do
- @note = Note.create_labels_change_note(issue, project, author, [label], [])
- expect(@note).to be_system
- end
-
- it 'should identify milestone-change notes as system notes' do
- @note = Note.create_milestone_change_note(issue, project, author, milestone)
- expect(@note).to be_system
- end
- end
-
- describe :authorization do
+ describe 'authorization' do
before do
@p1 = create(:project)
@p2 = create(:project)
@@ -593,7 +155,7 @@ describe Note do
@abilities << Ability
end
- describe :read do
+ describe 'read' do
before do
@p1.project_members.create(user: @u2, access_level: ProjectMember::GUEST)
@p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST)
@@ -604,7 +166,7 @@ describe Note do
it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey }
end
- describe :write do
+ describe 'write' do
before do
@p1.project_members.create(user: @u2, access_level: ProjectMember::DEVELOPER)
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
@@ -615,7 +177,7 @@ describe Note do
it { expect(@abilities.allowed?(@u3, :write_note, @p1)).to be_falsey }
end
- describe :admin do
+ describe 'admin' do
before do
@p1.project_members.create(user: @u1, access_level: ProjectMember::REPORTER)
@p1.project_members.create(user: @u2, access_level: ProjectMember::MASTER)
@@ -631,6 +193,7 @@ describe Note do
it_behaves_like 'an editable mentionable' do
subject { create :note, noteable: issue, project: project }
+ let(:project) { create(:project) }
let(:issue) { create :issue, project: project }
let(:backref_text) { issue.gfm_reference }
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index e5bf9125313..ebd8b545aa7 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -48,6 +48,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
+ service_hook.should_receive(:execute)
+ @service.should_receive(:service_hook).and_return(service_hook)
+ @service.should_receive(:ci_yaml_file).with(push_sample_data)
+
+ @service.execute(push_sample_data)
+ end
+ end
end
describe "Fork registration" do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index bbaf54488be..e88615e1a2e 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(
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
+ hipchat.stub(api_version: '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
+ hipchat.stub(api_version: '')
+ 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)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 37e21a90818..48568e2a3ff 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,10 +54,17 @@ 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) }
@@ -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')
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index e37dcc75230..c81dd36ef4b 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -18,23 +18,47 @@
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_presence_of(:file_name) }
- it { is_expected.to ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to ensure_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 771709c127a..be0b70395d6 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -50,6 +50,11 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean
+# otp_backup_codes :text
# public_email :string(255) default(""), not null
#
@@ -58,7 +63,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) }
@@ -74,9 +89,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) }
@@ -170,6 +182,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)
@@ -228,6 +248,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
@@ -250,6 +271,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
@@ -552,7 +574,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) }
@@ -578,4 +599,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/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index bab8888a631..15f547e128d 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -49,10 +49,6 @@ describe API::API, api: true do
}
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,9 +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
+ Repository.any_instance.stub(
+ commit_file: false,
)
post api("/projects/#{project.id}/repository/files", user), valid_params
@@ -84,10 +80,6 @@ describe API::API, api: true do
}
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,35 +89,6 @@ 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
@@ -138,10 +101,6 @@ describe API::API, api: true do
}
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,8 +112,8 @@ 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,
+ Repository.any_instance.stub(
+ remove_file: false,
)
delete api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 4c7d15d6594..8d0ae1475c2 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,7 +5,7 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
let(:project) { create(:project) }
- let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') }
+ let(:secret_token) { File.read Gitlab.config.gitlab_shell.secret_file }
describe "GET /internal/check", no_db: true do
it do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dcd50f73326..0ed5883914b 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -349,10 +349,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/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/projects_spec.rb b/spec/requests/api/projects_spec.rb
index cc387378d3a..dbfd72e5f19 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')
end
-
+
context 'and using search' do
it 'should return searched project' do
get api('/projects', user), { search: project.name }
@@ -86,6 +86,15 @@ describe API::API, api: true do
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project3.id)
end
+
+ 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'}
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project2.id)
+ end
end
end
end
@@ -156,7 +165,7 @@ describe API::API, api: true do
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
@@ -265,7 +274,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
@@ -783,11 +792,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/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/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
new file mode 100644
index 00000000000..24e439503e7
--- /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 { Group.all.should_not include(group) }
+ it { Project.all.should_not 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 { gitlab_shell.exists?(group.path).should be_falsey }
+ it { gitlab_shell.exists?(remove_path).should 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 { gitlab_shell.exists?(group.path).should be_falsey }
+ it { gitlab_shell.exists?(remove_path).should be_truthy }
+ end
+ end
+
+ def destroy_group(group, user)
+ DestroyGroupService.new(group, user).execute
+ end
+end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index d15dff1b52b..0e5ae724bf7 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
describe Issues::CloseService do
- let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
+ let(:project) { issue.project }
before do
project.team << [user, :master]
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 22b89bec96d..a91be3b4472 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,18 +1,18 @@
require 'spec_helper'
describe Issues::UpdateService do
- let(:project) { create(:empty_project) }
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 }
before do
project.team << [user, :master]
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 879df0c9c67..0f9b65678df 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -30,11 +30,18 @@ describe MergeRequests::RefreshService do
end
context 'push to origin repo source branch' do
+ let(:refresh_service) { service.new(@project, @user) }
before do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
+ it 'should execute hooks with update action' do
+ expect(refresh_service).to have_received(:execute_hooks).
+ with(@merge_request, 'update')
+ end
+
it { expect(@merge_request.notes).not_to be_empty }
it { expect(@merge_request).to be_open }
it { expect(@fork_merge_request).to be_open }
@@ -54,11 +61,18 @@ describe MergeRequests::RefreshService do
end
context 'push to fork repo source branch' do
+ let(:refresh_service) { service.new(@fork_project, @user) }
before do
- service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
reload_mrs
end
+ it 'should execute hooks with update action' do
+ expect(refresh_service).to have_received(:execute_hooks).
+ with(@fork_merge_request, 'update')
+ end
+
it { expect(@merge_request.notes).to be_empty }
it { expect(@merge_request).to be_open }
it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') }
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 1a02299bf19..0dc3b412783 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -15,6 +15,8 @@ describe Notes::CreateService do
noteable_id: issue.id
}
+ expect(project).to receive(:execute_hooks)
+ expect(project).to receive(:execute_services)
@note = Notes::CreateService.new(project, user, opts).execute
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 2a54b2e920a..62a99d15952 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -31,7 +31,8 @@ describe NotificationService do
describe 'Notes' do
context 'issue note' do
- let(:issue) { create(:issue, assignee: create(:user)) }
+ let(:project) { create(:empty_project, :public) }
+ let(:issue) { create(:issue, project: project, assignee: create(:user)) }
let(:mentioned_issue) { create(:issue, assignee: issue.assignee) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
@@ -101,7 +102,8 @@ describe NotificationService do
end
context 'issue note mention' do
- let(:issue) { create(:issue, assignee: create(:user)) }
+ let(:project) { create(:empty_project, :public) }
+ let(:issue) { create(:issue, project: project, assignee: create(:user)) }
let(:mentioned_issue) { create(:issue, assignee: issue.assignee) }
let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
@@ -145,7 +147,8 @@ describe NotificationService do
end
context 'commit note' do
- let(:note) { create(:note_on_commit) }
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_commit, project: project) }
before do
build_team(note.project)
@@ -192,7 +195,8 @@ describe NotificationService do
end
describe 'Issues' do
- let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' }
+ let(:project) { create(:empty_project, :public) }
+ let(:issue) { create :issue, project: project, assignee: create(:user), description: 'cc @participant' }
before do
build_team(issue.project)
@@ -295,7 +299,8 @@ describe NotificationService do
end
describe 'Merge Requests' do
- let(:merge_request) { create :merge_request, assignee: create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user) }
before do
build_team(merge_request.target_project)
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
new file mode 100644
index 00000000000..cdf576cc0c1
--- /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 { Project.all.should_not include(project) }
+ it { Dir.exists?(path).should be_falsey }
+ it { Dir.exists?(remove_path).should 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 { Project.all.should_not include(project) }
+ it { Dir.exists?(path).should be_falsey }
+ it { Dir.exists?(remove_path).should be_truthy }
+ end
+
+ def destroy_project(project, user, params)
+ Projects::DestroyService.new(project, user, params).execute
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
new file mode 100644
index 00000000000..700286b585a
--- /dev/null
+++ b/spec/services/system_note_service_spec.rb
@@ -0,0 +1,381 @@
+require 'spec_helper'
+
+describe SystemNoteService do
+ let(:project) { create(:project) }
+ let(:author) { create(:user) }
+ let(:noteable) { create(:issue, project: project) }
+
+ shared_examples_for 'a system note' do
+ it 'is valid' do
+ expect(subject).to be_valid
+ end
+
+ it 'sets the noteable model' do
+ expect(subject.noteable).to eq noteable
+ end
+
+ it 'sets the project' do
+ expect(subject.project).to eq project
+ end
+
+ it 'sets the author' do
+ expect(subject.author).to eq author
+ end
+
+ it 'is a system note' do
+ expect(subject).to be_system
+ end
+ end
+
+ describe '.add_commits' do
+ subject { described_class.add_commits(noteable, project, author, new_commits, old_commits, oldrev) }
+
+ let(:noteable) { create(:merge_request, source_project: project) }
+ let(:new_commits) { noteable.commits }
+ let(:old_commits) { [] }
+ let(:oldrev) { nil }
+
+ it_behaves_like 'a system note'
+
+ describe 'note body' do
+ let(:note_lines) { subject.note.split("\n").reject(&:blank?) }
+
+ context 'without existing commits' do
+ it 'adds a message header' do
+ expect(note_lines[0]).to eq "Added #{new_commits.size} commits:"
+ end
+
+ it 'adds a message line for each commit' do
+ new_commits.each_with_index do |commit, i|
+ # Skip the header
+ expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}"
+ end
+ end
+ end
+
+ describe 'summary line for existing commits' do
+ let(:summary_line) { note_lines[1] }
+
+ context 'with one existing commit' do
+ let(:old_commits) { [noteable.commits.last] }
+
+ it 'includes the existing commit' do
+ expect(summary_line).to eq "* #{old_commits.first.short_id} - 1 commit from branch `feature`"
+ end
+ end
+
+ context 'with multiple existing commits' do
+ let(:old_commits) { noteable.commits[3..-1] }
+
+ context 'with oldrev' do
+ let(:oldrev) { noteable.commits[2].id }
+
+ it 'includes a commit range' do
+ expect(summary_line).to start_with "* #{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id}"
+ end
+
+ it 'includes a commit count' do
+ expect(summary_line).to end_with " - 2 commits from branch `feature`"
+ end
+ end
+
+ context 'without oldrev' do
+ it 'includes a commit range' do
+ expect(summary_line).to start_with "* #{old_commits[0].short_id}..#{old_commits[-1].short_id}"
+ end
+
+ it 'includes a commit count' do
+ expect(summary_line).to end_with " - 2 commits from branch `feature`"
+ end
+ end
+
+ context 'on a fork' do
+ before do
+ expect(noteable).to receive(:for_fork?).and_return(true)
+ end
+
+ it 'includes the project namespace' do
+ expect(summary_line).to end_with "`#{noteable.target_project_namespace}:feature`"
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe '.change_assignee' do
+ subject { described_class.change_assignee(noteable, project, author, assignee) }
+
+ let(:assignee) { create(:user) }
+
+ it_behaves_like 'a system note'
+
+ context 'when assignee added' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Reassigned to @#{assignee.username}"
+ end
+ end
+
+ context 'when assignee removed' do
+ let(:assignee) { nil }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'Assignee removed'
+ end
+ end
+ end
+
+ describe '.change_label' do
+ subject { described_class.change_label(noteable, project, author, added, removed) }
+
+ let(:labels) { create_list(:label, 2) }
+ let(:added) { [] }
+ let(:removed) { [] }
+
+ it_behaves_like 'a system note'
+
+ context 'with added labels' do
+ let(:added) { labels }
+ let(:removed) { [] }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq "Added ~#{labels[0].id} ~#{labels[1].id} labels"
+ end
+ end
+
+ context 'with removed labels' do
+ let(:added) { [] }
+ let(:removed) { labels }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq "Removed ~#{labels[0].id} ~#{labels[1].id} labels"
+ end
+ end
+
+ context 'with added and removed labels' do
+ let(:added) { [labels[0]] }
+ let(:removed) { [labels[1]] }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq "Added ~#{labels[0].id} and removed ~#{labels[1].id} labels"
+ end
+ end
+ end
+
+ describe '.change_milestone' do
+ subject { described_class.change_milestone(noteable, project, author, milestone) }
+
+ let(:milestone) { create(:milestone, project: project) }
+
+ it_behaves_like 'a system note'
+
+ context 'when milestone added' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Milestone changed to #{milestone.title}"
+ end
+ end
+
+ context 'when milestone removed' do
+ let(:milestone) { nil }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq 'Milestone removed'
+ end
+ end
+ end
+
+ describe '.change_status' do
+ subject { described_class.change_status(noteable, project, author, status, source) }
+
+ let(:status) { 'new_status' }
+ let(:source) { nil }
+
+ it_behaves_like 'a system note'
+
+ context 'with a source' do
+ let(:source) { double('commit', gfm_reference: 'commit 123456') }
+
+ it 'sets the note text' do
+ expect(subject.note).to eq "Status changed to #{status} by commit 123456"
+ end
+ end
+
+ context 'without a source' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Status changed to #{status}"
+ end
+ 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) }
+
+ let(:mentioner) { create(:issue, project: project) }
+
+ it_behaves_like 'a system note'
+
+ context 'when cross-reference disallowed' do
+ before do
+ expect(described_class).to receive(:cross_reference_disallowed?).and_return(true)
+ end
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when cross-reference allowed' do
+ before do
+ expect(described_class).to receive(:cross_reference_disallowed?).and_return(false)
+ end
+
+ describe 'note_body' do
+ context 'cross-project' do
+ let(:project2) { create(:project) }
+ let(:mentioner) { create(:issue, project: project2) }
+
+ context 'from Commit' do
+ let(:mentioner) { project2.repository.commit }
+
+ it 'references the mentioning commit' do
+ 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 #{mentioner.to_reference(project)}"
+ end
+ end
+ end
+
+ context 'within the same project' do
+ context 'from Commit' do
+ let(:mentioner) { project.repository.commit }
+
+ it 'references the mentioning commit' do
+ 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.to_reference}"
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe '.cross_reference?' do
+ it 'is truthy when text begins with expected text' do
+ expect(described_class.cross_reference?('mentioned in something')).to be_truthy
+ end
+
+ it 'is falsey when text does not begin with expected text' do
+ expect(described_class.cross_reference?('this is a note')).to be_falsey
+ end
+ end
+
+ describe '.cross_reference_disallowed?' do
+ context 'when mentioner is not a MergeRequest' do
+ it 'is falsey' do
+ mentioner = noteable.dup
+ expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
+ to be_falsey
+ end
+ end
+
+ context 'when mentioner is a MergeRequest' do
+ let(:mentioner) { create(:merge_request, :simple, source_project: project) }
+ let(:noteable) { project.commit }
+
+ it 'is truthy when noteable is in commits' do
+ expect(mentioner).to receive(:commits).and_return([noteable])
+ expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
+ to be_truthy
+ end
+
+ it 'is falsey when noteable is not in commits' do
+ expect(mentioner).to receive(:commits).and_return([])
+ expect(described_class.cross_reference_disallowed?(noteable, mentioner)).
+ to be_falsey
+ end
+ end
+ end
+
+ describe '.cross_reference_exists?' do
+ let(:commit0) { project.commit }
+ let(:commit1) { project.commit('HEAD~2') }
+
+ context 'issue from commit' do
+ before do
+ # Mention issue (noteable) from commit0
+ described_class.cross_reference(noteable, commit0, author)
+ end
+
+ it 'is truthy when already mentioned' do
+ expect(described_class.cross_reference_exists?(noteable, commit0)).
+ to be_truthy
+ end
+
+ it 'is falsey when not already mentioned' do
+ expect(described_class.cross_reference_exists?(noteable, commit1)).
+ to be_falsey
+ end
+ end
+
+ context 'commit from commit' do
+ before do
+ # Mention commit1 from commit0
+ described_class.cross_reference(commit0, commit1, author)
+ end
+
+ it 'is truthy when already mentioned' do
+ expect(described_class.cross_reference_exists?(commit0, commit1)).
+ to be_truthy
+ end
+
+ it 'is falsey when not already mentioned' do
+ expect(described_class.cross_reference_exists?(commit1, commit0)).
+ to be_falsey
+ end
+ end
+ end
+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/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 53fb6545553..d29c8a55c82 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}
@@ -109,19 +107,26 @@ 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
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/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/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);