summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--.rubocop.yml5
-rw-r--r--.scss-lint.yml6
-rw-r--r--CHANGELOG181
-rw-r--r--CONTRIBUTING.md26
-rw-r--r--Gemfile18
-rw-r--r--Gemfile.lock98
-rw-r--r--PROCESS.md22
-rw-r--r--README.md12
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/api.js.coffee11
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/awards_handler.coffee45
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js.coffee30
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee67
-rw-r--r--app/assets/javascripts/blob/new_blob.js.coffee20
-rw-r--r--app/assets/javascripts/commits.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee5
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee1
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee64
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee31
-rw-r--r--app/assets/javascripts/importer_status.js.coffee39
-rw-r--r--app/assets/javascripts/issuable.js.coffee84
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee38
-rw-r--r--app/assets/javascripts/issue.js.coffee23
-rw-r--r--app/assets/javascripts/issues.js.coffee51
-rw-r--r--app/assets/javascripts/labels_select.js.coffee183
-rw-r--r--app/assets/javascripts/lib/animate.js.coffee34
-rw-r--r--app/assets/javascripts/lib/url_utility.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee19
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee20
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee35
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee6
-rw-r--r--app/assets/javascripts/notes.js.coffee36
-rw-r--r--app/assets/javascripts/profile.js.coffee5
-rw-r--r--app/assets/javascripts/right_sidebar.js.coffee62
-rw-r--r--app/assets/javascripts/search.js.coffee75
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee41
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee20
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/javascripts/sidebar.js.coffee2
-rw-r--r--app/assets/javascripts/todos.js.coffee46
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee2
-rw-r--r--app/assets/javascripts/users_select.js.coffee2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss46
-rw-r--r--app/assets/stylesheets/framework/buttons.scss17
-rw-r--r--app/assets/stylesheets/framework/calendar.scss4
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss136
-rw-r--r--app/assets/stylesheets/framework/files.scss11
-rw-r--r--app/assets/stylesheets/framework/forms.scss18
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss70
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss9
-rw-r--r--app/assets/stylesheets/framework/mobile.scss9
-rw-r--r--app/assets/stylesheets/framework/modal.scss22
-rw-r--r--app/assets/stylesheets/framework/nav.scss80
-rw-r--r--app/assets/stylesheets/framework/selects.scss15
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss61
-rw-r--r--app/assets/stylesheets/framework/tables.scss4
-rw-r--r--app/assets/stylesheets/framework/timeline.scss5
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss14
-rw-r--r--app/assets/stylesheets/framework/variables.scss25
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss2
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/pages/builds.scss9
-rw-r--r--app/assets/stylesheets/pages/commit.scss26
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss18
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss16
-rw-r--r--app/assets/stylesheets/pages/diff.scss16
-rw-r--r--app/assets/stylesheets/pages/editor.scss9
-rw-r--r--app/assets/stylesheets/pages/graph.scss3
-rw-r--r--app/assets/stylesheets/pages/help.scss17
-rw-r--r--app/assets/stylesheets/pages/import.scss21
-rw-r--r--app/assets/stylesheets/pages/issuable.scss60
-rw-r--r--app/assets/stylesheets/pages/issues.scss40
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/milestone.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss19
-rw-r--r--app/assets/stylesheets/pages/notes.scss47
-rw-r--r--app/assets/stylesheets/pages/profile.scss35
-rw-r--r--app/assets/stylesheets/pages/projects.scss46
-rw-r--r--app/assets/stylesheets/pages/search.scss91
-rw-r--r--app/assets/stylesheets/pages/settings.scss14
-rw-r--r--app/assets/stylesheets/pages/status.scss2
-rw-r--r--app/assets/stylesheets/pages/todos.scss9
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/assets/stylesheets/print.scss44
-rw-r--r--app/controllers/admin/application_controller.rb8
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/hooks_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_controller.rb38
-rw-r--r--app/controllers/admin/impersonations_controller.rb26
-rw-r--r--app/controllers/admin/users_controller.rb18
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/concerns/filter_projects.rb2
-rw-r--r--app/controllers/confirmations_controller.rb9
-rw-r--r--app/controllers/help_controller.rb1
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/builds_controller.rb10
-rw-r--r--app/controllers/projects/commit_controller.rb45
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb31
-rw-r--r--app/controllers/projects/graphs_controller.rb4
-rw-r--r--app/controllers/projects/group_links_controller.rb10
-rw-r--r--app/controllers/projects/hooks_controller.rb14
-rw-r--r--app/controllers/projects/issues_controller.rb33
-rw-r--r--app/controllers/projects/merge_requests_controller.rb9
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb21
-rw-r--r--app/controllers/registrations_controller.rb11
-rw-r--r--app/controllers/search_controller.rb6
-rw-r--r--app/controllers/users_controller.rb23
-rw-r--r--app/finders/issuable_finder.rb42
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb17
-rw-r--r--app/helpers/ci_badge_helper.rb13
-rw-r--r--app/helpers/ci_status_helper.rb15
-rw-r--r--app/helpers/commits_helper.rb31
-rw-r--r--app/helpers/diff_helper.rb37
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/import_helper.rb18
-rw-r--r--app/helpers/issuables_helper.rb28
-rw-r--r--app/helpers/issues_helper.rb60
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/nav_helper.rb13
-rw-r--r--app/helpers/page_layout_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb88
-rw-r--r--app/helpers/search_helper.rb10
-rw-r--r--app/helpers/selects_helper.rb25
-rw-r--r--app/helpers/sorting_helper.rb18
-rw-r--r--app/helpers/tab_helper.rb8
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb10
-rw-r--r--app/mailers/repository_check_mailer.rb2
-rw-r--r--app/models/ability.rb29
-rw-r--r--app/models/abuse_report.rb12
-rw-r--r--app/models/application_setting.rb48
-rw-r--r--app/models/audit_event.rb14
-rw-r--r--app/models/blob.rb8
-rw-r--r--app/models/broadcast_message.rb14
-rw-r--r--app/models/ci/build.rb100
-rw-r--r--app/models/ci/commit.rb160
-rw-r--r--app/models/ci/runner.rb19
-rw-r--r--app/models/ci/runner_project.rb12
-rw-r--r--app/models/ci/trigger.rb13
-rw-r--r--app/models/ci/trigger_request.rb12
-rw-r--r--app/models/ci/variable.rb14
-rw-r--r--app/models/commit.rb19
-rw-r--r--app/models/commit_status.rb90
-rw-r--r--app/models/concerns/internal_id.rb10
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/models/concerns/mentionable.rb4
-rw-r--r--app/models/concerns/milestoneish.rb2
-rw-r--r--app/models/concerns/statuseable.rb81
-rw-r--r--app/models/deploy_key.rb15
-rw-r--r--app/models/deploy_keys_project.rb11
-rw-r--r--app/models/email.rb11
-rw-r--r--app/models/event.rb18
-rw-r--r--app/models/external_issue.rb6
-rw-r--r--app/models/forked_project_link.rb11
-rw-r--r--app/models/generic_commit_status.rb34
-rw-r--r--app/models/group.rb17
-rw-r--r--app/models/hooks/project_hook.rb23
-rw-r--r--app/models/hooks/service_hook.rb20
-rw-r--r--app/models/hooks/system_hook.rb23
-rw-r--r--app/models/hooks/web_hook.rb46
-rw-r--r--app/models/identity.rb12
-rw-r--r--app/models/issue.rb67
-rw-r--r--app/models/key.rb15
-rw-r--r--app/models/label.rb22
-rw-r--r--app/models/label_link.rb12
-rw-r--r--app/models/lfs_object.rb12
-rw-r--r--app/models/lfs_objects_project.rb11
-rw-r--r--app/models/member.rb19
-rw-r--r--app/models/members/group_member.rb19
-rw-r--r--app/models/members/project_member.rb19
-rw-r--r--app/models/merge_request.rb38
-rw-r--r--app/models/merge_request_diff.rb15
-rw-r--r--app/models/milestone.rb19
-rw-r--r--app/models/namespace.rb15
-rw-r--r--app/models/note.rb23
-rw-r--r--app/models/oauth_access_token.rb15
-rw-r--r--app/models/personal_snippet.rb16
-rw-r--r--app/models/project.rb104
-rw-r--r--app/models/project_import_data.rb10
-rw-r--r--app/models/project_services/asana_service.rb21
-rw-r--r--app/models/project_services/assembla_service.rb21
-rw-r--r--app/models/project_services/bamboo_service.rb21
-rw-r--r--app/models/project_services/buildkite_service.rb25
-rw-r--r--app/models/project_services/builds_email_service.rb21
-rw-r--r--app/models/project_services/campfire_service.rb21
-rw-r--r--app/models/project_services/ci_service.rb21
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb21
-rw-r--r--app/models/project_services/drone_ci_service.rb21
-rw-r--r--app/models/project_services/emails_on_push_service.rb21
-rw-r--r--app/models/project_services/external_wiki_service.rb21
-rw-r--r--app/models/project_services/flowdock_service.rb21
-rw-r--r--app/models/project_services/gemnasium_service.rb21
-rw-r--r--app/models/project_services/gitlab_ci_service.rb21
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb21
-rw-r--r--app/models/project_services/hipchat_service.rb25
-rw-r--r--app/models/project_services/irker_service.rb21
-rw-r--r--app/models/project_services/issue_tracker_service.rb23
-rw-r--r--app/models/project_services/jira_service.rb23
-rw-r--r--app/models/project_services/pivotaltracker_service.rb21
-rw-r--r--app/models/project_services/pushover_service.rb21
-rw-r--r--app/models/project_services/redmine_service.rb21
-rw-r--r--app/models/project_services/slack_service.rb28
-rw-r--r--app/models/project_services/slack_service/merge_message.rb2
-rw-r--r--app/models/project_services/slack_service/note_message.rb2
-rw-r--r--app/models/project_services/slack_service/wiki_page_message.rb53
-rw-r--r--app/models/project_services/teamcity_service.rb21
-rw-r--r--app/models/project_snippet.rb18
-rw-r--r--app/models/protected_branch.rb12
-rw-r--r--app/models/release.rb12
-rw-r--r--app/models/repository.rb124
-rw-r--r--app/models/security_event.rb14
-rw-r--r--app/models/sent_notification.rb14
-rw-r--r--app/models/service.rb25
-rw-r--r--app/models/snippet.rb20
-rw-r--r--app/models/subscription.rb13
-rw-r--r--app/models/todo.rb18
-rw-r--r--app/models/user.rb67
-rw-r--r--app/models/users_star_project.rb11
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/services/ci/create_builds_service.rb27
-rw-r--r--app/services/ci/create_trigger_request_service.rb4
-rw-r--r--app/services/ci/image_for_build_service.rb11
-rw-r--r--app/services/commits/change_service.rb47
-rw-r--r--app/services/commits/cherry_pick_service.rb19
-rw-r--r--app/services/commits/revert_service.rb46
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/create_commit_builds_service.rb10
-rw-r--r--app/services/create_tag_service.rb44
-rw-r--r--app/services/git_push_service.rb16
-rw-r--r--app/services/git_tag_push_service.rb30
-rw-r--r--app/services/issuable_base_service.rb28
-rw-r--r--app/services/issues/move_service.rb15
-rw-r--r--app/services/merge_requests/build_service.rb39
-rw-r--r--app/services/notes/create_service.rb11
-rw-r--r--app/services/projects/destroy_service.rb4
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/system_hooks_service.rb12
-rw-r--r--app/services/system_note_service.rb6
-rw-r--r--app/services/wiki_pages/base_service.rb27
-rw-r--r--app/services/wiki_pages/create_service.rb14
-rw-r--r--app/services/wiki_pages/update_service.rb11
-rw-r--r--app/views/admin/application_settings/_form.html.haml21
-rw-r--r--app/views/admin/builds/_build.html.haml26
-rw-r--r--app/views/admin/builds/index.html.haml3
-rw-r--r--app/views/admin/hooks/index.html.haml46
-rw-r--r--app/views/admin/logs/show.html.haml2
-rw-r--r--app/views/admin/users/_form.html.haml6
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml7
-rw-r--r--app/views/devise/confirmations/almost_there.haml10
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/devise/shared/_signup_box.html.haml11
-rw-r--r--app/views/doorkeeper/applications/index.html.haml5
-rw-r--r--app/views/events/_event.html.haml7
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/snippets/index.html.haml2
-rw-r--r--app/views/groups/activity.html.haml1
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml1
-rw-r--r--app/views/groups/issues.html.haml3
-rw-r--r--app/views/groups/merge_requests.html.haml3
-rw-r--r--app/views/groups/milestones/index.html.haml3
-rw-r--r--app/views/groups/projects.html.haml1
-rw-r--r--app/views/groups/show.html.haml34
-rw-r--r--app/views/help/_shortcuts.html.haml14
-rw-r--r--app/views/help/ui.html.haml18
-rw-r--r--app/views/import/base/create.js.haml4
-rw-r--r--app/views/import/bitbucket/status.html.haml20
-rw-r--r--app/views/import/fogbugz/status.html.haml15
-rw-r--r--app/views/import/github/status.html.haml19
-rw-r--r--app/views/import/gitlab/status.html.haml15
-rw-r--r--app/views/import/gitorious/status.html.haml15
-rw-r--r--app/views/import/google_code/status.html.haml19
-rw-r--r--app/views/layouts/_page.html.haml6
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise_empty.html.haml17
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/group_settings.html.haml3
-rw-r--r--app/views/layouts/header/_default.html.haml8
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml7
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml20
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml40
-rw-r--r--app/views/layouts/nav/_profile.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/profile.html.haml3
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/merge_request_status_email.html.haml2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/notify/note_merge_request_email.text.erb2
-rw-r--r--app/views/notify/note_snippet_email.html.haml1
-rw-r--r--app/views/notify/note_snippet_email.text.erb8
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml3
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/profiles/keys/_key_table.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml1
-rw-r--r--app/views/profiles/passwords/edit.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml1
-rw-r--r--app/views/profiles/show.html.haml4
-rw-r--r--app/views/projects/_activity.html.haml5
-rw-r--r--app/views/projects/_builds_settings.html.haml3
-rw-r--r--app/views/projects/_last_commit.html.haml9
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_readme.html.haml6
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml6
-rw-r--r--app/views/projects/blob/_text.html.haml25
-rw-r--r--app/views/projects/blob/diff.html.haml8
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/builds/index.html.haml6
-rw-r--r--app/views/projects/builds/show.html.haml13
-rw-r--r--app/views/projects/ci/builds/_build.html.haml22
-rw-r--r--app/views/projects/commit/_builds.html.haml69
-rw-r--r--app/views/projects/commit/_change.html.haml (renamed from app/views/projects/commit/_revert.html.haml)20
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml71
-rw-r--r--app/views/projects/commit/_commit_box.html.haml27
-rw-r--r--app/views/projects/commit/branches.html.haml1
-rw-r--r--app/views/projects/commit/show.html.haml5
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/commits/_head.html.haml3
-rw-r--r--app/views/projects/commits/show.html.haml4
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml45
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml31
-rw-r--r--app/views/projects/deploy_keys/index.html.haml69
-rw-r--r--app/views/projects/diffs/_diffs.html.haml6
-rw-r--r--app/views/projects/diffs/_file.html.haml26
-rw-r--r--app/views/projects/empty.html.haml8
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml13
-rw-r--r--app/views/projects/graphs/ci.html.haml2
-rw-r--r--app/views/projects/graphs/commits.html.haml2
-rw-r--r--app/views/projects/graphs/languages.html.haml2
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/group_links/index.html.haml79
-rw-r--r--app/views/projects/hooks/_project_hook.html.haml15
-rw-r--r--app/views/projects/hooks/index.html.haml167
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml5
-rw-r--r--app/views/projects/issues/_new_branch.html.haml16
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml121
-rw-r--r--app/views/projects/issues/update.js.haml3
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml14
-rw-r--r--app/views/projects/merge_requests/edit.html.haml4
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml59
-rw-r--r--app/views/projects/merge_requests/update.js.haml3
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/_merged_buttons.haml11
-rw-r--r--app/views/projects/milestones/show.html.haml17
-rw-r--r--app/views/projects/network/_head.html.haml2
-rw-r--r--app/views/projects/notes/_discussion.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml13
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml6
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml9
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml6
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml2
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml56
-rw-r--r--app/views/projects/protected_branches/index.html.haml58
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml7
-rw-r--r--app/views/projects/show.html.haml14
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml10
-rw-r--r--app/views/projects/triggers/_trigger.html.haml8
-rw-r--r--app/views/projects/triggers/index.html.haml119
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/repository_check_mailer/notify.html.haml3
-rw-r--r--app/views/repository_check_mailer/notify.text.haml3
-rw-r--r--app/views/search/_category.html.haml105
-rw-r--r--app/views/search/_filter.html.haml78
-rw-r--r--app/views/search/_form.html.haml19
-rw-r--r--app/views/search/_results.html.haml17
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml5
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_labels_row.html.haml3
-rw-r--r--app/views/shared/_service_settings.html.haml8
-rw-r--r--app/views/shared/_sort_dropdown.html.haml5
-rw-r--r--app/views/shared/issuable/_filter.html.haml10
-rw-r--r--app/views/shared/issuable/_form.html.haml24
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml44
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml20
-rw-r--r--app/views/shared/issuable/_nav.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml65
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/projects/_dropdown.html.haml11
-rw-r--r--app/views/shared/projects/_project.html.haml7
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/sherlock/file_samples/show.html.haml2
-rw-r--r--app/views/sherlock/queries/show.html.haml2
-rw-r--r--app/views/sherlock/transactions/index.html.haml2
-rw-r--r--app/views/sherlock/transactions/show.html.haml2
-rw-r--r--app/views/users/show.html.haml12
-rw-r--r--app/views/votes/_votes_block.html.haml22
-rw-r--r--app/workers/post_receive.rb4
-rw-r--r--app/workers/repository_check/batch_worker.rb4
-rw-r--r--app/workers/repository_check/single_repository_worker.rb34
-rwxr-xr-xbin/background_jobs2
-rwxr-xr-xbin/rails5
-rwxr-xr-xbin/rake5
-rwxr-xr-xbin/rspec5
-rwxr-xr-xbin/spinach5
-rwxr-xr-xbin/spring12
-rwxr-xr-xbin/teaspoon8
-rwxr-xr-xbin/web4
-rw-r--r--config/application.rb25
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/gitlab.teatro.yml1
-rw-r--r--config/gitlab.yml.example7
-rw-r--r--config/initializers/1_settings.rb26
-rw-r--r--config/initializers/metrics.rb39
-rw-r--r--config/initializers/rack_attack.rb.example3
-rw-r--r--config/initializers/rack_attack_git_basic_auth.rb4
-rw-r--r--config/initializers/sentry.rb3
-rw-r--r--config/initializers/session_store.rb2
-rw-r--r--config/initializers/trusted_proxies.rb3
-rw-r--r--config/routes.rb20
-rw-r--r--db/fixtures/development/10_merge_requests.rb5
-rw-r--r--db/fixtures/development/14_builds.rb2
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb21
-rw-r--r--db/migrate/20160226114608_add_trigram_indexes_for_searching.rb11
-rw-r--r--db/migrate/20160227120001_add_event_field_for_web_hook.rb5
-rw-r--r--db/migrate/20160227120047_add_event_to_services.rb5
-rw-r--r--db/migrate/20160310124959_add_due_date_to_issues.rb6
-rw-r--r--db/migrate/20160412173416_add_fields_to_ci_commit.rb8
-rw-r--r--db/migrate/20160412173417_update_ci_commit.rb35
-rw-r--r--db/migrate/20160412173418_add_ci_commit_indexes.rb19
-rw-r--r--db/migrate/20160413115152_add_token_to_web_hooks.rb5
-rw-r--r--db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb5
-rw-r--r--db/migrate/20160419120017_add_metrics_packet_size.rb5
-rw-r--r--db/migrate/20160421130527_disable_repository_checks.rb11
-rw-r--r--db/migrate/20160508194200_remove_wall_enabled_from_projects.rb5
-rw-r--r--db/schema.rb24
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/high_availability/README.md35
-rw-r--r--doc/administration/high_availability/database.md116
-rw-r--r--doc/administration/high_availability/gitlab.md131
-rw-r--r--doc/administration/high_availability/load_balancer.md63
-rw-r--r--doc/administration/high_availability/nfs.md116
-rw-r--r--doc/administration/high_availability/redis.md62
-rw-r--r--doc/administration/repository_checks.md3
-rw-r--r--doc/administration/troubleshooting/sidekiq.md162
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/build_triggers.md12
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/issues.md15
-rw-r--r--doc/api/licenses.md147
-rw-r--r--doc/api/merge_requests.md22
-rw-r--r--doc/api/users.md80
-rw-r--r--doc/ci/api/builds.md108
-rw-r--r--doc/ci/docker/using_docker_build.md87
-rw-r--r--doc/ci/docker/using_docker_images.md12
-rw-r--r--doc/ci/examples/README.md4
-rw-r--r--doc/ci/examples/php.md8
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md10
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md6
-rw-r--r--doc/ci/runners/README.md6
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/services/postgres.md2
-rw-r--r--doc/ci/services/redis.md2
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/ci/triggers/README.md6
-rw-r--r--doc/ci/variables/README.md22
-rw-r--r--doc/ci/yaml/README.md49
-rw-r--r--doc/customization/libravatar.md13
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/instrumentation.md129
-rw-r--r--doc/development/performance.md258
-rw-r--r--doc/development/testing.md1
-rw-r--r--doc/downgrade_ee_to_ce/README.md82
-rw-r--r--doc/install/installation.md56
-rw-r--r--doc/install/requirements.md31
-rw-r--r--doc/integration/github.md18
-rw-r--r--doc/intro/README.md1
-rw-r--r--doc/monitoring/performance/gitlab_configuration.md2
-rw-r--r--doc/monitoring/performance/grafana_configuration.md75
-rw-r--r--doc/monitoring/performance/influxdb_configuration.md2
-rw-r--r--doc/monitoring/performance/influxdb_schema.md2
-rw-r--r--doc/operations/sidekiq_memory_killer.md2
-rw-r--r--doc/public_access/public_access.md3
-rw-r--r--doc/raketasks/backup_restore.md35
-rw-r--r--doc/system_hooks/system_hooks.md113
-rw-r--r--doc/update/8.6-to-8.7.md4
-rw-r--r--doc/update/README.md93
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--doc/workflow/README.md1
-rw-r--r--doc/workflow/cherry_pick_changes.md53
-rw-r--r--doc/workflow/img/cherry_pick_changes_commit.pngbin0 -> 353067 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_commit_modal.pngbin0 -> 312659 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_mr.pngbin0 -> 252085 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_mr_modal.pngbin0 -> 225569 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_github.md14
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md4
-rw-r--r--doc/workflow/merge_requests.md6
-rw-r--r--doc/workflow/merge_requests/commit_compare.pngbin89631 -> 110376 bytes
-rw-r--r--doc/workflow/merge_requests/merge_request_diff.pngbin120422 -> 166226 bytes
-rw-r--r--doc/workflow/merge_requests/merge_request_diff_without_whitespace.pngbin98887 -> 121476 bytes
-rw-r--r--doc/workflow/shortcuts.pngbin25005 -> 90936 bytes
-rw-r--r--docker/README.md6
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/commits/tags.feature46
-rw-r--r--features/project/create.feature16
-rw-r--r--features/project/deploy_keys.feature1
-rw-r--r--features/project/issues/filter_labels.feature1
-rw-r--r--features/project/source/browse_files.feature13
-rw-r--r--features/search.feature5
-rw-r--r--features/steps/dashboard/todos.rb10
-rw-r--r--features/steps/group/milestones.rb6
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/project/commits/tags.rb90
-rw-r--r--features/steps/project/commits/user_lookup.rb3
-rw-r--r--features/steps/project/create.rb26
-rw-r--r--features/steps/project/deploy_keys.rb14
-rw-r--r--features/steps/project/forked_merge_requests.rb2
-rw-r--r--features/steps/project/hooks.rb4
-rw-r--r--features/steps/project/issues/filter_labels.rb4
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/project/source/browse_files.rb4
-rw-r--r--features/steps/search.rb1
-rw-r--r--features/steps/shared/builds.rb6
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--features/steps/shared/project.rb2
-rw-r--r--features/steps/user.rb4
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/commit_statuses.rb21
-rw-r--r--lib/api/commits.rb8
-rw-r--r--lib/api/entities.rb16
-rw-r--r--lib/api/helpers.rb16
-rw-r--r--lib/api/internal.rb6
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/licenses.rb58
-rw-r--r--lib/api/milestones.rb10
-rw-r--r--lib/api/project_hooks.rb4
-rw-r--r--lib/api/project_snippets.rb15
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/api/users.rb10
-rw-r--r--lib/banzai/filter/external_link_filter.rb5
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb3
-rw-r--r--lib/banzai/filter/label_reference_filter.rb36
-rw-r--r--lib/banzai/filter/sanitization_filter.rb2
-rw-r--r--lib/ci/api/api.rb2
-rw-r--r--lib/ci/api/builds.rb33
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb118
-rw-r--r--lib/ci/status.rb19
-rw-r--r--lib/file_size_validator.rb8
-rw-r--r--lib/gitlab/akismet_helper.rb12
-rw-r--r--lib/gitlab/backend/shell.rb18
-rw-r--r--lib/gitlab/bitbucket_import/client.rb2
-rw-r--r--lib/gitlab/git_access.rb11
-rw-r--r--lib/gitlab/github_import/client.rb15
-rw-r--r--lib/gitlab/github_import/comment_formatter.rb21
-rw-r--r--lib/gitlab/github_import/importer.rb37
-rw-r--r--lib/gitlab/github_import/issue_formatter.rb8
-rw-r--r--lib/gitlab/github_import/label_formatter.rb23
-rw-r--r--lib/gitlab/github_import/milestone_formatter.rb48
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb8
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/highlight.rb13
-rw-r--r--lib/gitlab/metrics.rb16
-rw-r--r--lib/gitlab/metrics/instrumentation.rb37
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb1
-rw-r--r--lib/gitlab/push_data_builder.rb5
-rw-r--r--lib/gitlab/sanitizers/svg.rb37
-rw-r--r--lib/gitlab/sanitizers/svg/whitelist.rb107
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb4
-rw-r--r--lib/gitlab/url_builder.rb6
-rwxr-xr-xlib/support/init.d/gitlab12
-rw-r--r--lib/support/nginx/gitlab3
-rw-r--r--lib/support/nginx/gitlab-ssl3
-rw-r--r--lib/tasks/gitlab/db.rake7
-rw-r--r--public/503.html54
-rwxr-xr-xscripts/prepare_build.sh2
-rw-r--r--spec/controllers/admin/impersonation_controller_spec.rb19
-rw-r--r--spec/controllers/admin/impersonations_controller_spec.rb95
-rw-r--r--spec/controllers/admin/users_controller_spec.rb49
-rw-r--r--spec/controllers/commit_controller_spec.rb51
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb20
-rw-r--r--spec/controllers/import/github_controller_spec.rb2
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb50
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb39
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb16
-rw-r--r--spec/controllers/users_controller_spec.rb25
-rw-r--r--spec/factories/abuse_reports.rb12
-rw-r--r--spec/factories/broadcast_messages.rb14
-rw-r--r--spec/factories/forked_project_links.rb11
-rw-r--r--spec/factories/label_links.rb12
-rw-r--r--spec/factories/labels.rb13
-rw-r--r--spec/factories/lfs_objects.rb12
-rw-r--r--spec/factories/lfs_objects_projects.rb11
-rw-r--r--spec/factories/merge_requests.rb29
-rw-r--r--spec/factories/notes.rb21
-rw-r--r--spec/factories/oauth_access_tokens.rb15
-rw-r--r--spec/factories/project_hooks.rb4
-rw-r--r--spec/factories/project_wikis.rb7
-rw-r--r--spec/factories/projects.rb50
-rw-r--r--spec/factories/releases.rb12
-rw-r--r--spec/factories/todos.rb18
-rw-r--r--spec/factories/wiki_pages.rb9
-rw-r--r--spec/features/builds_spec.rb30
-rw-r--r--spec/features/dashboard/label_filter_spec.rb29
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb27
-rw-r--r--spec/features/issues/filter_by_labels_spec.rb167
-rw-r--r--spec/features/issues/filter_issues_spec.rb9
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb79
-rw-r--r--spec/features/issues/move_spec.rb10
-rw-r--r--spec/features/issues/new_branch_button_spec.rb11
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues_spec.rb172
-rw-r--r--spec/features/login_spec.rb2
-rw-r--r--spec/features/markdown_spec.rb7
-rw-r--r--spec/features/merge_requests/cherry_pick_spec.rb44
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb10
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb6
-rw-r--r--spec/features/merge_requests/toggle_whitespace_changes.rb22
-rw-r--r--spec/features/milestone_spec.rb35
-rw-r--r--spec/features/project/shortcuts_spec.rb21
-rw-r--r--spec/features/projects/commit/builds_spec.rb27
-rw-r--r--spec/features/projects/commits/cherry_pick_spec.rb67
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb63
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb61
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb39
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb20
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb83
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb44
-rw-r--r--spec/features/projects_spec.rb27
-rw-r--r--spec/features/runners_spec.rb16
-rw-r--r--spec/features/security/project/internal_access_spec.rb10
-rw-r--r--spec/features/security/project/private_access_spec.rb6
-rw-r--r--spec/features/security/project/public_access_spec.rb12
-rw-r--r--spec/features/signup_spec.rb55
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb62
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb41
-rw-r--r--spec/features/tags/master_updates_tag_spec.rb42
-rw-r--r--spec/features/tags/master_views_tags_spec.rb73
-rw-r--r--spec/features/todos/todos_spec.rb81
-rw-r--r--spec/features/users_spec.rb16
-rw-r--r--spec/finders/issues_finder_spec.rb16
-rw-r--r--spec/fixtures/sanitized.svg50
-rw-r--r--spec/fixtures/unsanitized.svg50
-rw-r--r--spec/helpers/blob_helper_spec.rb12
-rw-r--r--spec/helpers/ci_status_helper_spec.rb6
-rw-r--r--spec/helpers/commits_helper_spec.rb29
-rw-r--r--spec/helpers/diff_helper_spec.rb20
-rw-r--r--spec/helpers/import_helper_spec.rb25
-rw-r--r--spec/helpers/issues_helper_spec.rb36
-rw-r--r--spec/helpers/labels_helper_spec.rb8
-rw-r--r--spec/helpers/projects_helper_spec.rb45
-rw-r--r--spec/initializers/trusted_proxies_spec.rb51
-rw-r--r--spec/javascripts/merge_request_widget_spec.js.coffee55
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb42
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb6
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb343
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb6
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb26
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb40
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb30
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb40
-rw-r--r--spec/lib/gitlab/github_import/label_formatter_spec.rb19
-rw-r--r--spec/lib/gitlab/github_import/milestone_formatter_spec.rb82
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb58
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb51
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics_spec.rb24
-rw-r--r--spec/lib/gitlab/push_data_builder_spec.rb15
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb9
-rw-r--r--spec/mailers/notify_spec.rb12
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb12
-rw-r--r--spec/models/application_setting_spec.rb46
-rw-r--r--spec/models/broadcast_message_spec.rb14
-rw-r--r--spec/models/build_spec.rb16
-rw-r--r--spec/models/ci/commit_spec.rb389
-rw-r--r--spec/models/ci/runner_project_spec.rb12
-rw-r--r--spec/models/ci/runner_spec.rb19
-rw-r--r--spec/models/ci/trigger_spec.rb13
-rw-r--r--spec/models/ci/variable_spec.rb14
-rw-r--r--spec/models/commit_spec.rb8
-rw-r--r--spec/models/commit_status_spec.rb98
-rw-r--r--spec/models/concerns/issuable_spec.rb43
-rw-r--r--spec/models/concerns/statuseable_spec.rb (renamed from spec/lib/ci/status_spec.rb)41
-rw-r--r--spec/models/deploy_key_spec.rb15
-rw-r--r--spec/models/deploy_keys_project_spec.rb11
-rw-r--r--spec/models/email_spec.rb11
-rw-r--r--spec/models/event_spec.rb81
-rw-r--r--spec/models/external_issue_spec.rb15
-rw-r--r--spec/models/forked_project_link_spec.rb11
-rw-r--r--spec/models/generic_commit_status_spec.rb34
-rw-r--r--spec/models/group_spec.rb15
-rw-r--r--spec/models/hooks/web_hook_spec.rb46
-rw-r--r--spec/models/identity_spec.rb12
-rw-r--r--spec/models/issue_spec.rb61
-rw-r--r--spec/models/key_spec.rb15
-rw-r--r--spec/models/label_link_spec.rb12
-rw-r--r--spec/models/label_spec.rb21
-rw-r--r--spec/models/member_spec.rb19
-rw-r--r--spec/models/merge_request_spec.rb33
-rw-r--r--spec/models/milestone_spec.rb23
-rw-r--r--spec/models/namespace_spec.rb15
-rw-r--r--spec/models/note_spec.rb21
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb95
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb17
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb81
-rw-r--r--spec/models/project_services/campfire_service_spec.rb42
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb49
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb13
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb17
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb (renamed from spec/models/external_wiki_service_spec.rb)17
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb14
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb16
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb14
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb18
-rw-r--r--spec/models/project_services/irker_service_spec.rb14
-rw-r--r--spec/models/project_services/jira_service_spec.rb26
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb42
-rw-r--r--spec/models/project_services/pushover_service_spec.rb20
-rw-r--r--spec/models/project_services/redmine_service_spec.rb49
-rw-r--r--spec/models/project_services/slack_service/merge_message_spec.rb4
-rw-r--r--spec/models/project_services/slack_service/note_message_spec.rb2
-rw-r--r--spec/models/project_services/slack_service/wiki_page_message_spec.rb74
-rw-r--r--spec/models/project_services/slack_service_spec.rb34
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb95
-rw-r--r--spec/models/project_snippet_spec.rb16
-rw-r--r--spec/models/project_spec.rb78
-rw-r--r--spec/models/protected_branch_spec.rb12
-rw-r--r--spec/models/release_spec.rb12
-rw-r--r--spec/models/repository_spec.rb184
-rw-r--r--spec/models/service_spec.rb21
-rw-r--r--spec/models/snippet_spec.rb16
-rw-r--r--spec/models/todo_spec.rb18
-rw-r--r--spec/models/user_spec.rb63
-rw-r--r--spec/requests/api/builds_spec.rb2
-rw-r--r--spec/requests/api/commit_status_spec.rb19
-rw-r--r--spec/requests/api/commits_spec.rb41
-rw-r--r--spec/requests/api/issues_spec.rb21
-rw-r--r--spec/requests/api/licenses_spec.rb136
-rw-r--r--spec/requests/api/merge_requests_spec.rb28
-rw-r--r--spec/requests/api/milestones_spec.rb31
-rw-r--r--spec/requests/api/notes_spec.rb41
-rw-r--r--spec/requests/api/project_hooks_spec.rb14
-rw-r--r--spec/requests/api/project_snippets_spec.rb87
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb6
-rw-r--r--spec/requests/api/users_spec.rb18
-rw-r--r--spec/requests/ci/api/builds_spec.rb66
-rw-r--r--spec/services/ci/create_builds_service_spec.rb4
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb2
-rw-r--r--spec/services/create_tag_service_spec.rb53
-rw-r--r--spec/services/git_push_service_spec.rb30
-rw-r--r--spec/services/git_tag_push_service_spec.rb24
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb2
-rw-r--r--spec/services/issues/create_service_spec.rb63
-rw-r--r--spec/services/issues/move_service_spec.rb38
-rw-r--r--spec/services/issues/update_service_spec.rb11
-rw-r--r--spec/services/merge_requests/build_service_spec.rb181
-rw-r--r--spec/services/merge_requests/update_service_spec.rb11
-rw-r--r--spec/services/notification_service_spec.rb65
-rw-r--r--spec/services/projects/import_service_spec.rb13
-rw-r--r--spec/services/system_note_service_spec.rb9
-rw-r--r--spec/services/todo_service_spec.rb19
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci.yml17
-rw-r--r--spec/support/issue_tracker_service_shared_example.rb7
-rw-r--r--spec/support/project_hook_data_shared_example.rb17
-rw-r--r--spec/support/repo_helpers.rb2
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb15
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb57
810 files changed, 13036 insertions, 6352 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1dc49ca336d..85730e1b687 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -115,6 +115,11 @@ bundler:audit:
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+db-migrate-reset:
+ stage: test
+ script:
+ - RAILS_ENV=test bundle exec rake db:migrate:reset
+
# Ruby 2.2 jobs
spec:feature:ruby22:
diff --git a/.rubocop.yml b/.rubocop.yml
index 2fda0b03119..9f179efa3ce 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -728,7 +728,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity:
Enabled: true
- Max: 17
+ Max: 18
#################### Lint ################################
@@ -953,10 +953,9 @@ Performance/DoubleStartEndWith:
Performance/EndWith:
Enabled: false
-# TODO: Enable LstripRstrip Cop.
# Use `strip` instead of `lstrip.rstrip`.
Performance/LstripRstrip:
- Enabled: false
+ Enabled: true
# TODO: Enable RangeInclude Cop.
# Use `Range#cover?` instead of `Range#include?`.
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 835a4a88c44..66f9975d4ce 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -65,7 +65,7 @@ linters:
# Reports when you have an empty rule set.
EmptyRule:
- enabled: false
+ enabled: true
# Reports when you have an @extend directive.
ExtendDirective:
@@ -244,11 +244,11 @@ linters:
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
- enabled: false
+ enabled: true
# URLs should always be enclosed within quotes.
UrlQuotes:
- enabled: false
+ enabled: true
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
diff --git a/CHANGELOG b/CHANGELOG
index 5c375fcdb39..7d5f424eaec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,26 +1,105 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.7.0 (unreleased)
+v 8.8.0 (unreleased)
+ - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
+ - Use a case-insensitive comparison in sanitizing URI schemes
+ - Project#open_branches has been cleaned up and no longer loads entire records into memory.
+ - Escape HTML in commit titles in system note messages
+ - Improve multiple branch push performance by memoizing permission checking
+ - Log to application.log when an admin starts and stops impersonating a user
+ - Updated gitlab_git to 10.1.0
+ - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists
+ - Reduce delay in destroying a project from 1-minute to immediately
+ - Make build status canceled if any of the jobs was canceled and none failed
+ - Upgrade Sidekiq to 4.1.2
+ - Sanitize repo paths in new project error message
+ - Bump mail_room to 0.7.0 to fix stuck IDLE connections
+ - Remove future dates from contribution calendar graph.
+ - Support e-mail notifications for comments on project snippets
+ - Use ActionDispatch Remote IP for Akismet checking
+ - Fix error when visiting commit builds page before build was updated
+ - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
+ - Update SVG sanitizer to conform to SVG 1.1
+ - Updated search UI
+ - Display informative message when new milestone is created
+ - Sanitize milestones and labels titles
+ - Support multi-line tag messages. !3833 (Calin Seciu)
+ - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
+ - Added button to toggle whitespaces changes on diff view
+ - Backport GitHub Enterprise import support from EE
+ - Create tags using Rugged for performance reasons. !3745
+ - API: Expose Issue#user_notes_count. !3126 (Anton Popov)
+ - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
+ - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
+ - Added multiple colors for labels in dropdowns when dups happen.
+ - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
+ - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
+ - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
+ - Expire repository exists? and has_visible_content? caches after a push if necessary
+ - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
+ - Fix adding a todo for private group members (Ahmad Sherif)
+
+v 8.7.4
+ - Fix always showing build notification message when switching between merge requests
+ - Links for Redmine issue references are generated correctly again (Benedikt Huss)
+
+v 8.7.3
+ - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
+ - Merge request widget displays TeamCity build state and code coverage correctly again.
+ - Fix the line code when importing PR review comments from GitHub. !4010
+ - Wikis are now initialized on legacy projects when checking repositories
+
+v 8.7.2
+ - The "New Branch" button is now loaded asynchronously
+ - Fix error 500 when trying to create a wiki page
+ - Updated spacing between notification label and button
+ - Label titles in filters are now escaped properly
+
+v 8.7.1
+ - Throttle the update of `project.last_activity_at` to 1 minute. !3848
+ - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849
+ - Fix license detection to detect all license files, not only known licenses. !3878
+ - Use the `can?` helper instead of `current_user.can?`. !3882
+ - Prevent users from deleting Webhooks via API they do not own
+ - Fix Error 500 due to stale cache when projects are renamed or transferred
+ - Update width of search box to fix Safari bug. !3900 (Jedidiah)
+ - Use the `can?` helper instead of `current_user.can?`
+
+v 8.7.0
+ - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented
+ - Fix vulnerability that made it possible to gain access to private labels and milestones
+ - The number of InfluxDB points stored per UDP packet can now be configured
+ - Fix error when cross-project label reference used with non-existent project
+ - Transactions for /internal/allowed now have an "action" tag set
+ - Method instrumentation now uses Module#prepend instead of aliasing methods
+ - Repository.clean_old_archives is now instrumented
+ - Add support for environment variables on a job level in CI configuration file
+ - SQL query counts are now tracked per transaction
- The Projects::HousekeepingService class has extra instrumentation
- All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
+ - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan)
- Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
+ - Restrict user profiles when public visibility level is restricted.
+ - Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389
+ - Make shared runners text in box configurable
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Make /profile/keys/new redirect to /profile/keys for back-compat. !3717
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
+ - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński)
- Expose label description in API (Mariusz Jachimowicz)
- API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling)
@@ -28,6 +107,8 @@ v 8.7.0 (unreleased)
- Fix a bug whith trailing slash in teamcity_url (Charles May)
- Allow back dating on issues when created or updated through the API
- Allow back dating on issue notes when created through the API
+ - Propose license template when creating a new LICENSE file
+ - API: Expose /licenses and /licenses/:key
- Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
@@ -35,8 +116,9 @@ v 8.7.0 (unreleased)
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
+ - Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu)
- - Add automated repository integrity checks
+ - Add automated repository integrity checks (OFF by default)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
@@ -45,6 +127,7 @@ v 8.7.0 (unreleased)
- Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
+ - Make before_script and after_script overridable on per-job (Kamil Trzciński)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups
@@ -52,34 +135,77 @@ v 8.7.0 (unreleased)
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
+ - Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling)
+ - While signing up, don't persist the user password across form redisplays
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
+ - Update email confirmation interface
- API: Expose user location (Robert Schilling)
- API: Do not leak group existence via return code (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
- Update number of Todos in the sidebar when it's marked as "Done". !3600
+ - Sanitize branch names created for confidential issues
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
- API: User can leave a project through the API when not master or owner. !3613
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
+ - Diff design updates (colors, button styles, etc)
+ - Copying and pasting a diff no longer pastes the line numbers or +/-
+ - Add null check to formData when updating profile content to fix Firefox bug
+ - Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling)
+ - Add Slack notifications when Wiki is edited (Sebastian Klier)
- Diffs load at the correct point when linking from from number
- Selected diff rows highlight
- Fix emoji categories in the emoji picker
+ - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
- Add encrypted credentials for imported projects and migrate old ones
+ - Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller)
- Author and participants are displayed first on users autocompletion
+ - Show number sign on external issue reference text (Florent Baldino)
+ - Updated print style for issues
+ - Use GitHub Issue/PR number as iid to keep references
+ - Import GitHub labels
+ - Add option to filter by "Owned projects" on dashboard page
+ - Import GitHub milestones
+ - Execute system web hooks on push to the project
+ - Allow enable/disable push events for system hooks
+ - Fix GitHub project's link in the import page when provider has a custom URL
+ - Add RAW build trace output and button on build page
+ - Add incremental build trace update into CI API
+
+v 8.6.8
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent XSS via label drop-down
+ - Prevent information disclosure via milestone API
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.6.7
+ - Fix persistent XSS vulnerability in `commit_person_link` helper
+ - Fix persistent XSS vulnerability in Label and Milestone dropdowns
+ - Fix vulnerability that made it possible to enumerate private projects belonging to group
v 8.6.6
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
- Fix revoking of authorized OAuth applications (Connor Shea). !3690
+ - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
+ - Issuable header is consistent between issues and merge requests
+ - Improved spacing in issuable header on mobile
v 8.6.5
- Fix importing from GitHub Enterprise. !3529
@@ -209,6 +335,20 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
+v 8.5.12
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.5.11
+ - Fix persistent XSS vulnerability in `commit_person_link` helper
+
v 8.5.10
- Fix a 2FA authentication spoofing vulnerability.
@@ -356,6 +496,20 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
+v 8.4.10
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via Git branch and tag names
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via snippet API
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.4.9
+ - Fix persistent XSS vulnerability in `commit_person_link` helper
+
v 8.4.8
- Fix a 2FA authentication spoofing vulnerability.
@@ -478,6 +632,18 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
+v 8.3.9
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via custom issue tracker URL
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.3.8
+ - Fix persistent XSS vulnerability in `commit_person_link` helper
+
v 8.3.7
- Fix a 2FA authentication spoofing vulnerability.
@@ -584,6 +750,17 @@ v 8.3.0
- Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
+v 8.2.5
+ - Prevent privilege escalation via "impersonate" feature
+ - Prevent privilege escalation via notes API
+ - Prevent privilege escalation via project webhook API
+ - Prevent XSS via `window.opener`
+ - Prevent information disclosure via project labels
+ - Prevent information disclosure via new merge request page
+
+v 8.2.4
+ - Bump Git version requirement to 2.7.4
+
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1f26a5d7eaf..9fe4cf7b0f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,7 +38,7 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
abbreviation.
-If you have read this guide and want to know how the GitLab [core team][core-team]
+If you have read this guide and want to know how the GitLab [core team]
operates please see [the GitLab contributing process](PROCESS.md).
## Contributor license agreement
@@ -135,12 +135,23 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
-of the project cannot add labels. You can instead ask one of the [core team][core-team]
-members to add the label `feature proposal` to the issue.
+of the project cannot add labels. You can instead ask one of the [core team]
+members to add the label `feature proposal` to the issue or add the following
+code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
+You are encouraged to use the template below for feature proposals.
+
+```
+## Description including problem, use cases, benefits, and/or goals
+
+## Proposal
+
+## Links / references
+```
+
For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
@@ -323,6 +334,7 @@ request is as follows:
[shell command guidelines](doc/development/shell_commands.md)
1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md).
+1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get
@@ -343,12 +355,11 @@ is it will be merged (quickly). After that you can send more MRs to enhance it.
For examples of feedback on merge requests please look at already
[closed merge requests][closed-merge-requests]. If you would like quick feedback
on your merge request feel free to mention one of the Merge Marshalls in the
-[core team][core-team] or one of the
-[Merge request coaches](https://about.gitlab.com/team/).
+[core team] or one of the [Merge request coaches](https://about.gitlab.com/team/).
Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the
-[Thoughtbot code review guide] into account.
+[code review guidelines](doc/development/code_review.md) into account.
### Merge request description format
@@ -496,7 +507,7 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
-[core-team]: https://about.gitlab.com/core-team/
+[core team]: https://about.gitlab.com/core-team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
@@ -522,4 +533,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
-[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
diff --git a/Gemfile b/Gemfile
index 199ef65d922..3e5c604ae06 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,8 +19,8 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.4'
+gem 'doorkeeper', '~> 3.1'
gem 'devise-async', '~> 0.9.0'
-gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
@@ -178,7 +178,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0'
#cal-heatmap
-gem 'cal-heatmap-rails', '~> 3.5.0'
+gem 'cal-heatmap-rails', '~> 3.6.0'
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
@@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input
gem "loofah", "~> 2.0.3"
+# Working with license
+gem 'licensee', '~> 8.0.0'
+
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.1'
@@ -214,7 +217,7 @@ gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.0.0'
+gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
@@ -239,8 +242,7 @@ group :development do
gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false
- gem "annotate", "~> 2.7.0"
- gem "letter_opener", '~> 1.1.2'
+ gem 'letter_opener_web', '~> 1.3.0'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
gem 'bullet', require: false
@@ -267,7 +269,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
- gem 'rspec-rails', '~> 3.3.0'
+ gem 'rspec-rails', '~> 3.4.0'
gem 'rspec-retry'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
@@ -315,9 +317,9 @@ end
gem "newrelic_rpm", '~> 3.14'
-gem 'octokit', '~> 3.8.0'
+gem 'octokit', '~> 4.3.0'
-gem "mail_room", "~> 0.6.1"
+gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8'
diff --git a/Gemfile.lock b/Gemfile.lock
index ad7d7c18559..86b9142ef27 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -51,9 +51,6 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
- annotate (2.7.0)
- activerecord (>= 3.2, < 6.0)
- rake (~> 10.4)
arel (6.0.3)
asana (0.4.0)
faraday (~> 0.9)
@@ -103,7 +100,7 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
- cal-heatmap-rails (3.5.1)
+ cal-heatmap-rails (3.6.0)
capybara (2.6.2)
addressable
mime-types (>= 1.16)
@@ -134,7 +131,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
- concurrent-ruby (1.0.0)
+ concurrent-ruby (1.0.2)
connection_pool (2.2.0)
coveralls (0.8.13)
json (~> 1.8)
@@ -175,7 +172,7 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
- doorkeeper (2.2.2)
+ doorkeeper (3.1.0)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
@@ -186,7 +183,7 @@ GEM
encryptor (1.3.0)
equalizer (0.0.11)
erubis (2.7.0)
- escape_utils (1.1.0)
+ escape_utils (1.1.1)
eventmachine (1.0.8)
excon (0.45.4)
execjs (2.6.0)
@@ -336,7 +333,7 @@ GEM
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
- github-linguist (4.7.5)
+ github-linguist (4.7.6)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
@@ -346,14 +343,14 @@ GEM
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-grit (2.7.3)
+ gitlab-grit (2.8.1)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
- mime-types (~> 1.15)
+ mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (10.0.0)
+ gitlab_git (10.1.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -431,8 +428,8 @@ GEM
json
ipaddress (0.8.2)
jquery-atwho-rails (1.3.2)
- jquery-rails (4.0.5)
- rails-dom-testing (~> 1.0)
+ jquery-rails (4.1.1)
+ rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -450,8 +447,14 @@ GEM
kgio (2.10.0)
launchy (2.4.3)
addressable (~> 2.3)
- letter_opener (1.1.2)
+ letter_opener (1.4.1)
launchy (~> 2.2)
+ letter_opener_web (1.3.0)
+ actionmailer (>= 3.2)
+ letter_opener (~> 1.0)
+ railties (>= 3.2)
+ licensee (8.0.0)
+ rugged (>= 0.24b)
listen (3.0.5)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
@@ -461,9 +464,9 @@ GEM
systemu (~> 2.6.2)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mail_room (0.6.1)
+ mail_room (0.7.0)
method_source (0.8.2)
- mime-types (1.25.1)
+ mime-types (2.99.1)
mimemagic (0.3.0)
mini_portile2 (2.0.0)
minitest (5.7.0)
@@ -485,8 +488,8 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
- octokit (3.8.0)
- sawyer (~> 0.6.0, >= 0.5.3)
+ octokit (4.3.0)
+ sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
@@ -627,7 +630,7 @@ GEM
recaptcha (1.0.2)
json
redcarpet (3.3.3)
- redis (3.2.2)
+ redis (3.3.0)
redis-actionpack (4.0.1)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -658,29 +661,29 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.3.0)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-core (3.3.2)
- rspec-support (~> 3.3.0)
- rspec-expectations (3.3.1)
+ rspec (3.4.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-core (3.4.4)
+ rspec-support (~> 3.4.0)
+ rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-mocks (3.3.2)
+ rspec-support (~> 3.4.0)
+ rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.3.0)
- rspec-rails (3.3.3)
+ rspec-support (~> 3.4.0)
+ rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
- rspec-core (~> 3.3.0)
- rspec-expectations (~> 3.3.0)
- rspec-mocks (~> 3.3.0)
- rspec-support (~> 3.3.0)
+ rspec-core (~> 3.4.0)
+ rspec-expectations (~> 3.4.0)
+ rspec-mocks (~> 3.4.0)
+ rspec-support (~> 3.4.0)
rspec-retry (0.4.5)
rspec-core
- rspec-support (3.3.0)
+ rspec-support (3.4.1)
rubocop (0.38.0)
parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1)
@@ -712,8 +715,8 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
- sawyer (0.6.0)
- addressable (~> 2.3.5)
+ sawyer (0.7.0)
+ addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10)
scss_lint (0.47.1)
rake (>= 0.9, < 11)
@@ -734,10 +737,9 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (4.0.1)
+ sidekiq (4.1.2)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
- json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2)
@@ -888,7 +890,6 @@ DEPENDENCIES
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
- annotate (~> 2.7.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
@@ -903,7 +904,7 @@ DEPENDENCIES
bullet
bundler-audit
byebug
- cal-heatmap-rails (~> 3.5.0)
+ cal-heatmap-rails (~> 3.6.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
@@ -920,7 +921,7 @@ DEPENDENCIES
devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
- doorkeeper (~> 2.2.0)
+ doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
@@ -951,14 +952,15 @@ DEPENDENCIES
httparty (~> 0.13.3)
influxdb (~> 0.2)
jquery-atwho-rails (~> 1.3.2)
- jquery-rails (~> 4.0.0)
+ jquery-rails (~> 4.1.0)
jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3)
- letter_opener (~> 1.1.2)
+ letter_opener_web (~> 1.3.0)
+ licensee (~> 8.0.0)
loofah (~> 2.0.3)
- mail_room (~> 0.6.1)
+ mail_room (~> 0.7)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@@ -968,7 +970,7 @@ DEPENDENCIES
newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.0.0)
- octokit (~> 3.8.0)
+ octokit (~> 4.3.0)
omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6)
@@ -1008,7 +1010,7 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
- rspec-rails (~> 3.3.0)
+ rspec-rails (~> 3.4.0)
rspec-retry
rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1)
@@ -1055,4 +1057,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.11.2
+ 1.12.3
diff --git a/PROCESS.md b/PROCESS.md
index cad45d23df9..fe3a963110d 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -59,7 +59,7 @@ core team members will mention this person.
Workflow labels are purposely not very detailed since that would be hard to keep
updated as you would need to re-evaluate them after every comment. We optionally
-use functional labels on demand when want to group related issues to get an
+use functional labels on demand when we want to group related issues to get an
overview (for example all issues related to RVM, to tackle them in one go) and
to add details to the issue.
@@ -73,6 +73,7 @@ in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
- ~customer is an issue reported by enterprise subscribers. This label should
be accompanied by *bug* or *feature proposal* labels.
+
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
## Functional labels
@@ -105,6 +106,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
+## Feature Freeze
+
+5 working days before the 22nd the stable branches for the upcoming release will
+be frozen for major changes. Merge requests may still be merged into master
+during this period. By freezing the stable branches prior to a release there's
+no need to worry about last minute merge requests potentially breaking a lot of
+things.
+
+What is considered to be a major change is determined on a case by case basis as
+this definition depends very much on the context of changes. For example, a 5
+line change might have a big impact on the entire application. Ultimately the
+decision will be made by those reviewing a merge request and the release
+manager.
+
+During the feature freeze all merge requests that are meant to go into the next
+release should have the correct milestone assigned _and_ have the label
+~"Pick into Stable" set. Merge requests without a milestone and this label will
+not be merged into any stable branches.
+
## Copy & paste responses
### Improperly formatted issue
diff --git a/README.md b/README.md
index afa60116ebb..418d06a45a5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# GitLab
-[![build status](https://ci.gitlab.com/projects/1/status.svg?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
+[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
@@ -20,6 +20,10 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Completely free and open source (MIT Expat license)
- Powered by [Ruby on Rails](https://github.com/rails/rails)
+## Hiring
+
+We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/).
+
## Editions
There are two editions of GitLab:
@@ -31,11 +35,11 @@ There are two editions of GitLab:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
-- [Subscriptions](https://about.gitlab.com/subscription/)
+- [Subscriptions](https://about.gitlab.com/pricing/)
- [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
-- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
+- [GitLab Enterprise Edition](https://about.gitlab.com/features/#enterprise) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Requirements
@@ -80,7 +84,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
## GitLab release cycle
-For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
+For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md).
## Upgrading
diff --git a/VERSION b/VERSION
index 91ab1f99daf..d5a967c3933 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.7.0-pre
+8.8.0-pre
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index f3ed9a66715..dd1bbb37551 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -5,6 +5,7 @@
group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json"
labels_path: "/api/:version/projects/:id/labels"
+ license_path: "/api/:version/licenses/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@@ -92,6 +93,16 @@
).done (projects) ->
callback(projects)
+ # Return text for a specific license
+ licenseText: (key, data, callback) ->
+ url = Api.buildUrl(Api.license_path).replace(':key', key)
+
+ $.ajax(
+ url: url
+ data: data
+ ).done (license) ->
+ callback(license)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 6f435e4c542..bffce5a0c0f 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -174,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
+ gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
# Flash
if (flash = $(".flash-container")).length > 0
@@ -204,6 +204,7 @@ $ ->
$('.header-content .title').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
+ $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 589caf011ed..c18c9984c1f 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -105,7 +105,7 @@ class @AwardsHandler
@postEmoji awardUrl, emoji, =>
@addAwardToEmojiBar(emoji)
- $(".emoji-menu").removeClass "is-visible"
+ $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
@@ -168,7 +168,7 @@ class @AwardsHandler
@resetTooltip(award_block)
resetTooltip: (award) ->
- award.tooltip("destroy")
+ award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
@@ -194,13 +194,13 @@ class @AwardsHandler
$currentBlock.removeClass 'hidden'
resolveNameToCssClass: (emoji) ->
- emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+ emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
- if emoji_icon.length > 0
- unicodeName = emoji_icon.data("unicode-name")
+ if emojiIcon.length > 0
+ unicodeName = emojiIcon.data('unicode-name')
else
# Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
"emoji-#{unicodeName}"
@@ -217,45 +217,42 @@ class @AwardsHandler
scrollTop: $('.awards').offset().top - 80
}, 200)
- normilizeEmojiName: (emoji) ->
- @aliases[emoji] || emoji
-
addEmojiToFrequentlyUsedList: (emoji) ->
- frequently_used_emojis = @getFrequentlyUsedEmojis()
- frequently_used_emojis.push(emoji)
- $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis.push(emoji)
+ $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: ->
- frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
- _.compact(_.uniq(frequently_used_emojis))
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
+ _.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
- frequently_used_emojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>")
for emoji in frequently_used_emojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
- $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+ $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: ->
- $("input.emoji-search").on 'keyup', (ev) =>
+ $('input.emoji-search').keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
- $("ul.emoji-menu-search, h5.emoji-search").remove()
+ $('ul.emoji-menu-search, h5.emoji-search').remove()
if term
# Generate a search result block
- h5 = $("<h5>").text("Search results").addClass("emoji-search")
- found_emojis = @searchEmojis(term).show()
- ul = $("<ul>").addClass("emoji-menu-list emoji-menu-search").append(found_emojis)
- $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
- $(".emoji-menu-content").append(h5).append(ul)
+ h5 = $('<h5>').text('Search results').addClass('emoji-search')
+ foundEmojis = @searchEmojis(term).show()
+ ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
+ $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
+ $('.emoji-menu-content').append(h5).append(ul)
else
- $(".emoji-menu-content").children().show()
+ $('.emoji-menu-content').children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee
new file mode 100644
index 00000000000..e17eaa75dc1
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee
@@ -0,0 +1,30 @@
+class @BlobLicenseSelector
+ licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
+
+ constructor: (editor) ->
+ @$licenseSelector = $('.js-license-selector')
+ $fileNameInput = $('#file_name')
+
+ initialFileNameValue = if $fileNameInput.length
+ $fileNameInput.val()
+ else if $('.editor-file-name').length
+ $('.editor-file-name').text().trim()
+
+ @toggleLicenseSelector(initialFileNameValue)
+
+ if $fileNameInput
+ $fileNameInput.on 'keyup blur', (e) =>
+ @toggleLicenseSelector($(e.target).val())
+
+ $('select.license-select').on 'change', (e) ->
+ data =
+ project: $(this).data('project')
+ fullname: $(this).data('fullname')
+ Api.licenseText $(this).val(), data, (license) ->
+ editor.setValue(license.content, -1)
+
+ toggleLicenseSelector: (fileName) =>
+ if @licenseRegex.test(fileName)
+ @$licenseSelector.show()
+ else
+ @$licenseSelector.hide()
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 390e41ed8d4..eea9aa972ee 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -1,44 +1,39 @@
class @EditBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
+ constructor: (assets_path, ace_mode = null) ->
+ ace.config.set "modePath", "#{assets_path}/ace"
ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
+ @editor = ace.edit("editor")
+ @editor.focus()
+ @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
# Before a form submission, move the content from the Ace editor into the
# submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
+ $('form').submit =>
+ $("#file-content").val(@editor.getValue())
+
+ @initModePanesAndLinks()
+ new BlobLicenseSelector(@editor)
- editModePanes = $(".js-edit-mode-pane")
- editModeLinks = $(".js-edit-mode a")
- editModeLinks.click (event) ->
- event.preventDefault()
- currentLink = $(this)
- paneId = currentLink.attr("href")
- currentPane = editModePanes.filter(paneId)
- editModeLinks.parent().removeClass "active hover"
- currentLink.parent().addClass "active hover"
- editModePanes.hide()
- if paneId is "#preview"
- currentPane.fadeIn 200
- $.post currentLink.data("preview-url"),
- content: editor.getValue()
- , (response) ->
- currentPane.empty().append response
- currentPane.syntaxHighlight()
- return
+ initModePanesAndLinks: ->
+ @$editModePanes = $(".js-edit-mode-pane")
+ @$editModeLinks = $(".js-edit-mode a")
+ @$editModeLinks.click @editModeLinkClickHandler
- else
- currentPane.fadeIn 200
- editor.focus()
- return
+ editModeLinkClickHandler: (event) =>
+ event.preventDefault()
+ currentLink = $(event.target)
+ paneId = currentLink.attr("href")
+ currentPane = @$editModePanes.filter(paneId)
+ @$editModeLinks.parent().removeClass "active hover"
+ currentLink.parent().addClass "active hover"
+ @$editModePanes.hide()
+ currentPane.fadeIn 200
+ if paneId is "#preview"
+ $.post currentLink.data("preview-url"),
+ content: @editor.getValue()
+ , (response) ->
+ currentPane.empty().append response
+ currentPane.syntaxHighlight()
- editor: ->
- return @editor
+ else
+ @editor.focus()
diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee
deleted file mode 100644
index 68c5e5195e3..00000000000
--- a/app/assets/javascripts/blob/new_blob.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-class @NewBlob
- constructor: (assets_path, mode)->
- ace.config.set "modePath", assets_path + '/ace'
- ace.config.loadModule "ace/ext/searchbox"
- if mode
- ace_mode = mode
- editor = ace.edit("editor")
- editor.focus()
- @editor = editor
-
- if ace_mode
- editor.getSession().setMode "ace/mode/" + ace_mode
-
- # Before a form submission, move the content from the Ace editor into the
- # submitted textarea
- $('form').submit ->
- $("#file-content").val(editor.getValue())
-
- editor: ->
- return @editor
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index ffd3627b1b0..0acb4c1955e 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,7 +1,7 @@
class @CommitsList
@timer = null
- @init: (ref, limit) ->
+ @init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 610fa990fc7..e00ca2984b9 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -17,6 +17,7 @@ class Dispatcher
switch page
when 'projects:issues:index'
Issues.init()
+ Issuable.init()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
@@ -59,7 +60,7 @@ class Dispatcher
new ZenMode()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
- MergeRequests.init()
+ Issuable.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
@@ -109,6 +110,8 @@ class Dispatcher
new BuildArtifacts()
when 'projects:group_links:index'
new GroupsSelect()
+ when 'search:show'
+ new Search()
switch path.first()
when 'admin'
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 6eb8d27ee2b..e2194589b38 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -61,6 +61,7 @@ class @DropzoneInput
return
drop: ->
+ $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus()
return
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
new file mode 100644
index 00000000000..a4304786cbb
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -0,0 +1,64 @@
+class @DueDateSelect
+ constructor: ->
+ $loading = $('.js-issuable-update .due_date')
+ .find('.block-loading')
+ .hide()
+
+ $('.js-due-date-select').each (i, dropdown) ->
+ $dropdown = $(dropdown)
+ $dropdownParent = $dropdown.closest('.dropdown')
+ $datePicker = $dropdownParent.find('.js-due-date-calendar')
+ $block = $dropdown.closest('.block')
+ $selectbox = $dropdown.closest('.selectbox')
+ $value = $block.find('.value')
+ $sidebarValue = $('.js-due-date-sidebar-value', $block)
+
+ fieldName = $dropdown.data('field-name')
+ abilityName = $dropdown.data('ability-name')
+ issueUpdateURL = $dropdown.data('issue-update')
+
+ $dropdown.glDropdown(
+ hidden: ->
+ $selectbox.hide()
+ $value.removeAttr('style')
+ )
+
+ addDueDate = ->
+ # Create the post date
+ value = $("input[name='#{fieldName}']").val()
+ date = new Date value.replace(new RegExp('-', 'g'), ',')
+ mediumDate = $.datepicker.formatDate 'M d, yy', date
+
+ data = {}
+ data[abilityName] = {}
+ data[abilityName].due_date = value
+
+ $.ajax(
+ type: 'PUT'
+ url: issueUpdateURL
+ data: data
+ beforeSend: ->
+ $loading.fadeIn()
+ $dropdown.trigger('loading.gl.dropdown')
+ $selectbox.hide()
+ $value.removeAttr('style')
+
+ $value.html(mediumDate)
+ $sidebarValue.html(mediumDate)
+ ).done (data) ->
+ $dropdown.trigger('loaded.gl.dropdown')
+ $dropdown.dropdown('toggle')
+ $loading.fadeOut()
+
+ $datePicker.datepicker(
+ dateFormat: 'yy-mm-dd',
+ defaultDate: $("input[name='#{fieldName}']").val()
+ altField: "input[name='#{fieldName}']"
+ onSelect: ->
+ addDueDate()
+ )
+
+ $(document)
+ .off 'click', '.ui-datepicker-header a'
+ .on 'click', '.ui-datepicker-header a', (e) ->
+ e.stopImmediatePropagation()
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 2dc37257e22..1d1bfeb2e77 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -32,10 +32,8 @@ class GitLabDropdownFilter
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
- if keyCode is 13 and @input.val() isnt ""
- if @options.enterCallback
- @options.enterCallback()
- return
+ if keyCode is 13
+ return false
clearTimeout timeout
timeout = setTimeout =>
@@ -132,7 +130,6 @@ class GitLabDropdown
@filterInput = @getElement(FILTER_INPUT)
@highlight = false
@filterInputBlur = true
- @enterCallback = true
} = @options
self = @
@@ -157,6 +154,9 @@ class GitLabDropdown
@fullData = data
@parseData @fullData
+
+ if @options.filterable
+ @filterInput.trigger 'keyup'
}
# Init filterable
@@ -178,15 +178,15 @@ class GitLabDropdown
callback: (data) =>
currentIndex = -1
@parseData data
- enterCallback: =>
- if @enterCallback
- @selectRowAtIndex 0
# Event listeners
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
+ @dropdown.on 'keyup', (e) =>
+ if e.which is 27 # Escape key
+ $('.dropdown-menu-close', @dropdown).trigger 'click'
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
@@ -224,6 +224,9 @@ class GitLabDropdown
menu.toggleClass PAGE_TWO_CLASS
+ # Focus first visible input on active page
+ @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
+
parseData: (data) ->
@renderedData = data
@@ -243,7 +246,8 @@ class GitLabDropdown
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
- if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
+
+ if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
@@ -378,7 +382,6 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
@@ -389,13 +392,13 @@ class GitLabDropdown
else
selectedObject
else
- if !value?
- field.remove()
-
- if not @options.multiSelect
+ if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove()
+ if !value?
+ field.remove()
+
# Toggle active class for the tick mark
el.addClass ACTIVE_CLASS
diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee
index be8d225e73b..b0edc895649 100644
--- a/app/assets/javascripts/importer_status.js.coffee
+++ b/app/assets/javascripts/importer_status.js.coffee
@@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate()
initStatusPage: ->
- $(".js-add-to-import").click (event) =>
- new_namespace = null
- tr = $(event.currentTarget).closest("tr")
- id = tr.attr("id").replace("repo_", "")
- if tr.find(".import-target input").length > 0
- new_namespace = tr.find(".import-target input").prop("value")
- tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
- $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
-
- $(".js-import-all").click (event) =>
- $(".js-add-to-import").each ->
- $(this).click()
+ $('.js-add-to-import')
+ .off 'click'
+ .on 'click', (e) =>
+ new_namespace = null
+ $btn = $(e.currentTarget)
+ $tr = $btn.closest('tr')
+ id = $tr.attr('id').replace('repo_', '')
+ if $tr.find('.import-target input').length > 0
+ new_namespace = $tr.find('.import-target input').prop('value')
+ $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
+
+ $btn
+ .disable()
+ .addClass 'is-loading'
+
+ $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
+
+ $('.js-import-all')
+ .off 'click'
+ .on 'click', (e) ->
+ $btn = $(@)
+ $btn
+ .disable()
+ .addClass 'is-loading'
+
+ $('.js-add-to-import').each ->
+ $(this).trigger('click')
setAutoUpdate: ->
setInterval (=>
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
new file mode 100644
index 00000000000..afffed63ac5
--- /dev/null
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -0,0 +1,84 @@
+@Issuable =
+ init: ->
+ Issuable.initTemplates()
+ Issuable.initSearch()
+
+ initTemplates: ->
+ Issuable.labelRow = _.template(
+ '<% _.each(labels, function(label){ %>
+ <span class="label-row">
+ <a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
+ </span>
+ <% }); %>'
+ )
+
+ initSearch: ->
+ @timer = null
+ $('#issue_search')
+ .off 'keyup'
+ .on 'keyup', ->
+ clearTimeout(@timer)
+ @timer = setTimeout( ->
+ Issuable.filterResults $('#issue_search_form')
+ , 500)
+
+ toggleLabelFilters: ->
+ $filteredLabels = $('.filtered-labels')
+ if $filteredLabels.find('.label-row').length > 0
+ $filteredLabels.removeClass('hidden')
+ else
+ $filteredLabels.addClass('hidden')
+
+ filterResults: (form) =>
+ formData = form.serialize()
+
+ $('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
+ formAction = form.attr('action')
+ issuesUrl = formAction
+ issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
+ issuesUrl += formData
+ $.ajax
+ type: 'GET'
+ url: formAction
+ data: formData
+ complete: ->
+ $('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
+ success: (data) ->
+ $('.issues-holder, .merge-requests-holder').html(data.html)
+ # Change url so if user reload a page - search results are saved
+ history.replaceState {page: issuesUrl}, document.title, issuesUrl
+ Issuable.reload()
+ Issuable.updateStateFilters()
+ $filteredLabels = $('.filtered-labels')
+
+ if typeof Issuable.labelRow is 'function'
+ $filteredLabels.html(Issuable.labelRow(data))
+
+ Issuable.toggleLabelFilters()
+
+ dataType: "json"
+
+ reload: ->
+ if Issues.created
+ Issues.initChecks()
+
+ $('#filter_issue_search').val($('#issue_search').val())
+
+ updateStateFilters: ->
+ stateFilters = $('.issues-state-filters')
+ newParams = {}
+ paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
+
+ for paramKey in paramKeys
+ newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
+
+ if stateFilters.length
+ stateFilters.find('a').each ->
+ initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
+ labelNameValues = gl.utils.getParameterValues('label_name[]')
+ if labelNameValues
+ labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
+ newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
+ else
+ newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
+ $(this).attr 'href', newUrl
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 2f19513a831..3c491ebfc4c 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -9,21 +9,29 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
- $block = $(@).parents('.block')
- $selectbox = $block.find('.selectbox')
- if $selectbox.is(':visible')
- $selectbox.hide()
- $block.find('.value').show()
- else
- $selectbox.show()
- $block.find('.value').hide()
-
- if $selectbox.is(':visible')
- setTimeout (->
- $block.find('.dropdown-menu-toggle').trigger 'click'
- ), 0
-
+ $(document)
+ .off 'click', '.issuable-sidebar .dropdown-content a'
+ .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
+ e.preventDefault()
+
+ $(document)
+ .off 'click', '.edit-link'
+ .on 'click', '.edit-link', (e) ->
+ e.preventDefault()
+
+ $block = $(@).parents('.block')
+ $selectbox = $block.find('.selectbox')
+ if $selectbox.is(':visible')
+ $selectbox.hide()
+ $block.find('.value').show()
+ else
+ $selectbox.show()
+ $block.find('.value').hide()
+
+ if $selectbox.is(':visible')
+ setTimeout ->
+ $block.find('.dropdown-menu-toggle').trigger 'click'
+ , 0
$(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index c7d74a12f99..157361404e0 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -12,6 +12,7 @@ class @Issue
@initMergeRequests()
@initRelatedBranches()
+ @initCanCreateBranch()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
@@ -92,3 +93,25 @@ class @Issue
.success (data) ->
if 'html' of data
$container.html(data.html)
+
+ initCanCreateBranch: ->
+ $container = $('div#new-branch')
+
+ # If the user doesn't have the required permissions the container isn't
+ # rendered at all.
+ return unless $container
+
+ $.getJSON($container.data('path'))
+ .error ->
+ $container.find('.checking').hide()
+ $container.find('.unavailable').show()
+
+ new Flash('Failed to check if a new branch can be created.', 'alert')
+ .success (data) ->
+ if data.can_create_branch
+ $container.find('.checking').hide()
+ $container.find('.available').show()
+ $container.find('a').attr('disabled', false)
+ else
+ $container.find('.checking').hide()
+ $container.find('.unavailable').show()
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index 0d9f2094c2a..3330e6c68ad 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -1,6 +1,6 @@
@Issues =
init: ->
- Issues.initSearch()
+ Issues.created = true
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@@ -15,10 +15,6 @@
else
$(this).html totalIssues - 1
- reload: ->
- Issues.initChecks()
- $('#filter_issue_search').val($('#issue_search').val())
-
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
@@ -26,51 +22,6 @@
$(".selected_issue").bind "change", Issues.checkChanged
- # Update state filters if present in page
- updateStateFilters: ->
- stateFilters = $('.issues-state-filters')
- newParams = {}
- paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
-
- for paramKey in paramKeys
- newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
-
- if stateFilters.length
- stateFilters.find('a').each ->
- initialUrl = $(this).attr 'href'
- $(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout( ->
- Issues.filterResults $("#issue_search_form")
- , 500)
-
- filterResults: (form) =>
- $('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
- formAction = form.attr('action')
- formData = form.serialize()
- issuesUrl = formAction
- issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
- issuesUrl += formData
-
- $.ajax
- type: "GET"
- url: formAction
- data: formData
- complete: ->
- $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.issues-holder, .merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issuesUrl}, document.title, issuesUrl
- Issues.reload()
- Issues.updateStateFilters()
- dataType: "json"
-
checkChanged: ->
checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index bc80980acb7..995fd768603 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -6,7 +6,7 @@ class @LabelsSelect
labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
- if selectedLabel?
+ if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
@@ -16,33 +16,32 @@ class @LabelsSelect
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
+ $form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
- $loading = $block.find('.block-loading').fadeOut()
-
- if newLabelField.length
- $newLabelCreateButton = $('.js-new-label-btn')
- $colorPreview = $('.js-dropdown-label-color-preview')
- $newLabelError = $dropdown.parent().find('.js-label-error')
- $newLabelError.hide()
+ $newLabelError = $('.js-label-error')
+ $colorPreview = $('.js-dropdown-label-color-preview')
+ $newLabelCreateButton = $('.js-new-label-btn')
- # Suggested colors in the dropdown to chose from pre-chosen colors
- $('.suggest-colors-dropdown a').on 'click', (e) ->
+ $newLabelError.hide()
+ $loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
- <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
- <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
- <%= label.title %>
+ <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>">
+ <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
+ <%= _.escape(label.title) %>
</span>
</a>
<% }); %>'
- );
+ )
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
- if newLabelField.length and $dropdown.hasClass 'js-extra-options'
+ if newLabelField.length
+
+ # Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
@@ -81,26 +80,25 @@ class @LabelsSelect
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
+ saveLabel = ->
+ # Create new label with API
+ Api.newLabel projectId, {
+ name: newLabelField.val()
+ color: newColorField.val()
+ }, (label) ->
+ $newLabelCreateButton.enable()
+
+ if label.message?
+ $newLabelError
+ .text label.message
+ .show()
+ else
+ $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
@@ -111,24 +109,7 @@ class @LabelsSelect
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
-
- if newLabelField.val() isnt '' and newColorField.val() isnt ''
- $newLabelError.hide()
- $('.js-new-label-btn').disable()
-
- # Create new label with API
- Api.newLabel projectId, {
- name: newLabelField.val()
- color: newColorField.val()
- }, (label) ->
- $('.js-new-label-btn').enable()
-
- if label.message?
- $newLabelError
- .text label.message
- .show()
- else
- $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
+ saveLabel()
saveLabelData = ->
selected = $dropdown
@@ -171,7 +152,7 @@ class @LabelsSelect
.find('a')
.each((i) ->
setTimeout(=>
- glAnimate($(@), 'pulse')
+ gl.animate.animate($(@), 'pulse')
,200 * i
)
)
@@ -182,6 +163,21 @@ class @LabelsSelect
$.ajax(
url: labelUrl
).done (data) ->
+ data = _.chain data
+ .groupBy (label) ->
+ label.title
+ .map (label) ->
+ color = _.map label, (dup) ->
+ dup.color
+
+ return {
+ id: label[0].id
+ title: label[0].title
+ color: color
+ duplicate: color.length > 1
+ }
+ .value()
+
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
@@ -197,21 +193,47 @@ class @LabelsSelect
if data.length > 2
data.splice 2, 0, 'divider'
+
callback data
renderRow: (label) ->
- selectedClass = ''
- if $selectbox.find("input[type='hidden']\
- [name='#{$dropdown.data('field-name')}']\
- [value='#{label.id}']").length
- selectedClass = 'is-active'
+ removesAll = label.id is 0 or not label.id?
+
+ selectedClass = []
+ if $form.find("input[type='hidden']\
+ [name='#{$dropdown.data('fieldName')}']\
+ [value='#{this.id(label)}']").length
+ selectedClass.push 'is-active'
+
+ if $dropdown.hasClass('js-multiselect') and removesAll
+ selectedClass.push 'dropdown-clear-active'
+
+ if label.duplicate
+ spacing = 100 / label.color.length
+
+ # Reduce the colors to 4
+ label.color = label.color.filter (color, i) ->
+ i < 4
+
+ color = _.map(label.color, (color, i) ->
+ percentFirst = Math.floor(spacing * i)
+ percentSecond = Math.floor(spacing * (i + 1))
+ "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
+ ).join(',')
+ color = "linear-gradient(#{color})"
+ else
+ if label.color?
+ color = label.color[0]
- color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
+ if color
+ colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
+ else
+ colorEl = ''
"<li>
- <a href='#' class='#{selectedClass}'>
- #{color}
- #{label.title}
+ <a href='#' class='#{selectedClass.join(' ')}'>
+ #{colorEl}
+ #{_.escape(label.title)}
</a>
</li>"
filterable: true
@@ -219,37 +241,56 @@ class @LabelsSelect
fields: ['title']
selectable: true
- toggleLabel: (selected) ->
+ toggleLabel: (selected, el) ->
+ selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
+
if selected and selected.title?
- selected.title
+ if selected_labels.length > 1
+ "#{selected.title} +#{selected_labels.length - 1} more"
+ else
+ selected.title
+ else if not selected and selected_labels.length isnt 0
+ if selected_labels.length > 1
+ "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
+ else if selected_labels.length is 1
+ $(selected_labels).text()
else
defaultLabel
fieldName: $dropdown.data('field-name')
id: (label) ->
- if label.isAny?
- ''
- else if $dropdown.hasClass "js-filter-submit"
- label.title
+ if $dropdown.hasClass("js-filter-submit") and not label.isAny?
+ _.escape label.title
else
label.id
hidden: ->
+ page = $('body').data 'page'
+ isIssueIndex = page is 'projects:issues:index'
+ isMRIndex = page is 'projects:merge_requests:index'
+
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
- saveLabelData()
+ if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
+ selectedLabels = $dropdown
+ .closest('form')
+ .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
+ Issuable.filterResults $dropdown.closest('form')
+ else if $dropdown.hasClass('js-filter-submit')
+ $dropdown.closest('form').submit()
+ else
+ saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
- isMRIndex = page is page is 'projects:merge_requests:index'
-
+ isMRIndex = page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
- selectedLabel = label.title
-
- Issues.filterResults $dropdown.closest('form')
+ if not $dropdown.hasClass 'js-multiselect'
+ selectedLabel = label.title
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee
index 8f892b5a2b9..ec3b44d6126 100644
--- a/app/assets/javascripts/lib/animate.js.coffee
+++ b/app/assets/javascripts/lib/animate.js.coffee
@@ -1,13 +1,39 @@
((w) ->
+ if not w.gl? then w.gl = {}
+ if not gl.animate? then gl.animate = {}
- w.glAnimate = ($el, animation, done) ->
+ gl.animate.animate = ($el, animation, options, done) ->
+ if options?.cssStart?
+ $el.css(options.cssStart)
$el
- .removeClass()
+ .removeClass(animation + ' animated')
.addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
- $(this).removeClass()
+ $(this).removeClass(animation + ' animated')
+ if done?
+ done()
+ if options?.cssEnd?
+ $el.css(options.cssEnd)
return
return
- return
+ gl.animate.animateEach = ($els, animation, time, options, done) ->
+ dfd = $.Deferred()
+ if not $els.length
+ dfd.resolve()
+ $els.each((i) ->
+ setTimeout(=>
+ $this = $(@)
+ gl.animate.animate($this, animation, options, =>
+ if i is $els.length - 1
+ dfd.resolve()
+ if done?
+ done()
+ )
+ ,time * i
+ )
+ return
+ )
+ return dfd.promise()
+ return
) window \ No newline at end of file
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee
index abd556e0b4e..6a00932c028 100644
--- a/app/assets/javascripts/lib/url_utility.js.coffee
+++ b/app/assets/javascripts/lib/url_utility.js.coffee
@@ -3,16 +3,20 @@
w.gl ?= {}
w.gl.utils ?= {}
- w.gl.utils.getUrlParameter = (sParam) ->
+ # Returns an array containing the value(s) of the
+ # of the key passed as an argument
+ w.gl.utils.getParameterValues = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
+ values = []
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
- return if sParameterName[1] is undefined then true else sParameterName[1]
+ values.push(sParameterName[1])
i++
+ values
# #
# @param {Object} params - url keys and value to merge
@@ -28,4 +32,12 @@
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl
+ # removes parameter query string from url. returns the modified url
+ w.gl.utils.removeParamQueryString = (url, param) ->
+ url = decodeURIComponent(url)
+ urlVariables = url.split('&')
+ (
+ variables for variables in urlVariables when variables.indexOf(param) is -1
+ ).join('&')
+
) window
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 1ab6e5114bc..372732d0aac 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -87,8 +87,8 @@ class @MergeRequestTabs
if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight()
- $el = $("#{container} #{window.location.hash}")
- $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
+ $el = $("#{container} #{window.location.hash}:not(.match)")
+ $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -176,16 +176,17 @@ class @MergeRequestTabs
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
- $diffLine = $(locationHash)
+ $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
- if $diffLine.is ':not(tr)'
- $diffLine = $("td#{locationHash}, td#{hashClassString}")
+ if not $diffLine.is 'tr'
+ $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else
- $diffLine = $('td', $diffLine)
+ $diffLine = $diffLine.find('td')
- $diffLine.addClass 'hll'
- diffLineTop = $diffLine.offset().top
- navBarHeight = $('.navbar-gitlab').outerHeight()
+ if $diffLine.length
+ $diffLine.addClass 'hll'
+ diffLineTop = $diffLine.offset().top
+ navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) ->
return if @buildsLoaded
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 065626beeb8..f58647988a2 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -9,11 +9,12 @@ class @MergeRequestWidget
constructor: (@opts) ->
$('#modal_merge_info').modal(show: false)
@firstCICheck = true
- @readyForCICheck = true
+ @readyForCICheck = false
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
@addEventListeners()
+ @getCIStatus(false)
@pollCIStatus()
notifyPermissions()
@@ -68,20 +69,18 @@ class @MergeRequestWidget
$.getJSON @opts.ci_status_url, (data) =>
@readyForCICheck = true
- if @firstCICheck
- @firstCICheck = false
- @opts.ci_status = data.status
-
- if @opts.ci_status is ''
- @opts.ci_status = data.status
+ if data.status is ''
return
- if data.status isnt @opts.ci_status and data.status?
+ if @firstCICheck || data.status isnt @opts.ci_status and data.status?
+ @opts.ci_status = data.status
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
- if showNotification
+ # The first check should only update the UI, a notification
+ # should only be displayed on status changes
+ if showNotification and not @firstCICheck
status = @ciLabelForStatus(data.status)
if status is "preparing"
@@ -104,8 +103,7 @@ class @MergeRequestWidget
@close()
Turbolinks.visit _this.opts.builds_path
)
-
- @opts.ci_status = data.status
+ @firstCICheck = false
showCIStatus: (state) ->
$('.ci_widget').hide()
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
deleted file mode 100644
index b3c73ffce5d..00000000000
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# * Filter merge requests
-#
-@MergeRequests =
- init: ->
- MergeRequests.initSearch()
-
- # Make sure we trigger ajax request only after user stop typing
- initSearch: ->
- @timer = null
- $("#issue_search").keyup ->
- clearTimeout(@timer)
- @timer = setTimeout(MergeRequests.filterResults, 500)
-
- filterResults: =>
- form = $("#issue_search_form")
- search = $("#issue_search").val()
- $('.merge-requests-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '?' + form.serialize()
-
- $.ajax
- type: "GET"
- url: form.attr('action')
- data: form.serialize()
- complete: ->
- $('.merge-requests-holder').css("opacity", '1.0')
- success: (data) ->
- $('.merge-requests-holder').html(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: issues_url}, document.title, issues_url
- MergeRequests.reload()
- dataType: "json"
-
- reload: ->
- $('#filter_issue_search').val($('#issue_search').val())
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 6bd4e885a03..345a0e447af 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -24,7 +24,7 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
- '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
@@ -71,7 +71,7 @@ class @MilestoneSelect
defaultLabel
fieldName: $dropdown.data('field-name')
text: (milestone) ->
- milestone.title
+ _.escape(milestone.title)
id: (milestone) ->
if !useId
milestone.name
@@ -97,7 +97,7 @@ class @MilestoneSelect
selectedMilestone = selected.name
else
selectedMilestone = ''
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 74ae897b84a..e8a92b8012d 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -75,6 +75,9 @@ class @Notes
# when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh
+ # when a key is clicked on the notes
+ $(document).on "keydown", ".js-note-text", @keydownNoteText
+
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
@@ -92,10 +95,19 @@ class @Notes
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard"
+ $(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container'
+ keydownNoteText: (e) ->
+ $this = $(this)
+ if $this.val() is '' and e.which is 38 #aka the up key
+ myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
+ if myLastNote.length
+ myLastNoteEditBtn = myLastNote.find('.js-note-edit')
+ myLastNoteEditBtn.trigger('click', [true, myLastNote])
+
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@@ -343,7 +355,7 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels
###
- showEditForm: (e) ->
+ showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault()
note = $(this).closest(".note")
note.addClass "is-editting"
@@ -354,9 +366,27 @@ class @Notes
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
- new GLForm form
+ done = ($noteText) ->
+ # Neat little trick to put the cursor at the end
+ noteTextVal = $noteText.val()
+ $noteText.val('').val(noteTextVal);
- form.find(".js-note-text").focus()
+ new GLForm form
+ if scrollTo? and myLastNote?
+ # scroll to the bottom
+ # so the open of the last element doesn't make a jump
+ $('html, body').scrollTop($(document).height());
+ $('html, body').animate({
+ scrollTop: myLastNote.offset().top - 150
+ }, 500, ->
+ $noteText = form.find(".js-note-text")
+ $noteText.focus()
+ done($noteText)
+ );
+ else
+ $noteText = form.find('.js-note-text')
+ $noteText.focus()
+ done($noteText)
###
Called in response to clicking the edit note link
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index f4a2562885d..26a12423521 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -45,9 +45,10 @@ class @Profile
saveForm: ->
self = @
-
formData = new FormData(@form[0])
- formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
+
+ avatarBlob = @avatarGlCrop.getBlob()
+ formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
$.ajax
url: @form.attr('action')
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
index 67403554340..2d084b76cfe 100644
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ b/app/assets/javascripts/right_sidebar.js.coffee
@@ -1,10 +1,12 @@
class @Sidebar
constructor: (currentUser) ->
+ @sidebar = $('aside')
+
@addEventListeners()
addEventListeners: ->
- $('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
- $('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
+ @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
+ $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
@@ -30,26 +32,56 @@ class @Sidebar
else
i.show()
-
sidebarCollapseClicked: (e) ->
+ sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.openDropdown($block);
- $('aside')
- .find('.gutter-toggle')
- .trigger('click')
- $editLink = $block.find('.edit-link')
+ openDropdown: (blockOrName) ->
+ $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
+
+ $block.find('.edit-link').trigger('click')
- if $editLink.length
- $editLink.trigger('click')
- $block.addClass('collapse-after-update')
- $('.page-with-sidebar').addClass('with-overlay')
+ if not @isOpen()
+ @setCollapseAfterUpdate($block)
+ @toggleSidebar('open')
- sidebarDropdownHidden: (e) ->
+ setCollapseAfterUpdate: ($block) ->
+ $block.addClass('collapse-after-update')
+ $('.page-with-sidebar').addClass('with-overlay')
+
+ onSidebarDropdownHidden: (e) ->
+ sidebar = e.data
+ e.preventDefault()
$block = $(@).closest('.block')
+ sidebar.sidebarDropdownHidden($block)
+
+ sidebarDropdownHidden: ($block) ->
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
- $('aside')
- .find('.gutter-toggle')
- .trigger('click') \ No newline at end of file
+ @toggleSidebar('hide')
+
+ triggerOpenSidebar: ->
+ @sidebar
+ .find('.js-sidebar-toggle')
+ .trigger('click')
+
+ toggleSidebar: (action = 'toggle') ->
+ if action is 'toggle'
+ @triggerOpenSidebar()
+
+ if action is 'open'
+ @triggerOpenSidebar() if not @isOpen()
+
+ if action is 'hide'
+ @triggerOpenSidebar() is @isOpen()
+
+ isOpen: ->
+ @sidebar.is('.right-sidebar-expanded')
+
+ getBlock: (name) ->
+ @sidebar.find(".block.#{name}")
+
+
diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee
new file mode 100644
index 00000000000..661e1195f60
--- /dev/null
+++ b/app/assets/javascripts/search.js.coffee
@@ -0,0 +1,75 @@
+class @Search
+ constructor: ->
+ $groupDropdown = $('.js-search-group-dropdown')
+ $projectDropdown = $('.js-search-project-dropdown')
+ @eventListeners()
+
+ $groupDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'group_id'
+ data: (term, callback) ->
+ Api.groups term, null, (data) ->
+ data.unshift(
+ name: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name
+ toggleLabel: (obj) ->
+ "#{$groupDropdown.data('default-label')} #{obj.name}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ $projectDropdown.glDropdown(
+ selectable: true
+ filterable: true
+ fieldName: 'project_id'
+ data: (term, callback) ->
+ Api.projects term, 'id', (data) ->
+ data.unshift(
+ name_with_namespace: 'Any'
+ )
+ data.splice 1, 0, 'divider'
+
+ callback(data)
+ id: (obj) ->
+ obj.id
+ text: (obj) ->
+ obj.name_with_namespace
+ toggleLabel: (obj) ->
+ "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
+ clicked: =>
+ @submitSearch()
+ )
+
+ eventListeners: ->
+ $(document)
+ .off 'keyup', '.js-search-input'
+ .on 'keyup', '.js-search-input', @searchKeyUp
+
+ $(document)
+ .off 'click', '.js-search-clear'
+ .on 'click', '.js-search-clear', @clearSearchField
+
+ submitSearch: ->
+ $('.js-search-form').submit()
+
+ searchKeyUp: ->
+ $input = $(@)
+
+ if $input.val() is ''
+ $('.js-search-clear').addClass 'hidden'
+ else
+ $('.js-search-clear').removeClass 'hidden'
+
+ clearSearchField: ->
+ $('.js-search-input')
+ .val ''
+ .trigger 'keyup'
+ .focus()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index 100e3aac535..f3d66004138 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -2,34 +2,35 @@ class @Shortcuts
constructor: ->
@enabledHelp = []
Mousetrap.reset()
- Mousetrap.bind('?', @selectiveHelp)
+ Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
- selectiveHelp: (e) =>
- Shortcuts.showHelp(e, @enabledHelp)
+ onToggleHelp: (e) =>
+ e.preventDefault()
+ @toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) =>
$(document).triggerHandler('markdown-preview:toggle', [e])
- @showHelp: (e, location) ->
- if $('#modal-shortcuts').length > 0
- $('#modal-shortcuts').modal('show')
- else
- url = '/help/shortcuts'
- url = gon.relative_url_root + url if gon.relative_url_root?
- $.ajax(
- url: url,
- dataType: 'script',
- success: (e) ->
- if location and location.length > 0
- $(l).show() for l in location
- else
- $('.hidden-shortcut').show()
- $('.js-more-help-button').remove()
- )
- e.preventDefault()
+ toggleHelp: (location) ->
+ $modal = $('#modal-shortcuts')
+
+ if $modal.length
+ $modal.modal('toggle')
+ return
+
+ $.ajax(
+ url: gon.shortcuts_path,
+ dataType: 'script',
+ success: (e) ->
+ if location and location.length > 0
+ $(l).show() for l in location
+ else
+ $('.hidden-shortcut').show()
+ $('.js-more-help-button').remove()
+ )
@focusSearch: (e) ->
$('#search').focus()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index bbf02f1db24..ad9b3c1c6bf 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -4,18 +4,8 @@
class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
- Mousetrap.bind('a', ->
- $('.block.assignee .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('m', ->
- $('.block.milestone .edit-link').trigger('click')
- return false
- )
- Mousetrap.bind('r', =>
- @replyWithSelectedText()
- return false
- )
+ Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
+ Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
Mousetrap.bind('j', =>
@prevIssue()
return false
@@ -28,7 +18,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@editIssue()
return false
)
-
+ Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
@@ -71,3 +61,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
editIssue: ->
$editBtn = $('.issuable-edit')
Turbolinks.visit($editBtn.attr('href'))
+
+ openSidebarDropdown: (name) ->
+ sidebar.openDropdown(name)
+ return false
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 8decaedd87b..f39504e0645 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
+ Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
@enabledHelp.push('.hidden-shortcut.project')
@findAndFollowLink: (selector) ->
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index 860d4f438d0..ea4ac52da31 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -12,7 +12,7 @@ toggleSidebar = ->
niceScrollBars.updateScrollBar();
), 300
-$(document).on("click", '.toggle-nav-collapse', (e) ->
+$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault()
toggleSidebar()
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
index 00d2b641723..10bef96f43d 100644
--- a/app/assets/javascripts/todos.js.coffee
+++ b/app/assets/javascripts/todos.js.coffee
@@ -1,5 +1,11 @@
class @Todos
- constructor: (@name) ->
+ constructor: (opts = {}) ->
+ {
+ @el = $('.js-todos-options')
+ } = opts
+
+ @perPage = @el.data('perPage')
+
@clearListeners()
@initBtnListeners()
@@ -26,6 +32,7 @@ class @Todos
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
+ @redirectIfNeeded data.count
@clearDone $this.closest('li')
@updateBadges data
@@ -57,11 +64,46 @@ class @Todos
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
+ getTotalPages: ->
+ @el.data('totalPages')
+
+ getCurrentPage: ->
+ @el.data('currentPage')
+
+ getTodosPerPage: ->
+ @el.data('perPage')
+
+ redirectIfNeeded: (total) ->
+ currPages = @getTotalPages()
+ currPage = @getCurrentPage()
+
+ # Refresh if no remaining Todos
+ if not total
+ location.reload()
+ return
+
+ # Do nothing if no pagination
+ return if not currPages
+
+ newPages = Math.ceil(total / @getTodosPerPage())
+ url = location.href # Includes query strings
+
+ # If new total of pages is different than we have now
+ if newPages isnt currPages
+ # Redirect to previous page if there's one available
+ if currPages > 1 and currPage is currPages
+ pageParams =
+ page: currPages - 1
+ url = gl.utils.mergeUrlParams(pageParams, url)
+
+ Turbolinks.visit(url)
+
goToTodoUrl: (e)->
todoLink = $(this).data('url')
return unless todoLink
- if e.metaKey
+ # Allow Meta-Click or Mouse3-click to open in a new tab
+ if e.metaKey or e.which is 2
e.preventDefault()
window.open(todoLink,'_blank')
else
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
index 09b7eec9104..c2aeffe2381 100644
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ b/app/assets/javascripts/user_tabs.js.coffee
@@ -92,7 +92,7 @@ class @UserTabs
@setCurrentAction(action)
activateTab: (action) ->
- @parentEl.find(".nav-links .#{action}-tab a").tab('show')
+ @parentEl.find(".nav-links .js-#{action}-tab a").tab('show')
setTab: (source, action) ->
return if @loaded[action] is true
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index a7e934936e9..b80b1b861cc 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -158,7 +158,7 @@ class @UsersSelect
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id
- Issues.filterResults $dropdown.closest('form')
+ Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index c85ab9148d0..560de9fc0bd 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -25,6 +25,7 @@
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
+@import "framework/modal.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
@import "framework/progress.scss";
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 5aa425dab6c..f5ce70b606b 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -28,6 +28,7 @@
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
+ &.s70 { width: 70px; height: 70px; margin-right: 14px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 62b2af0dbf7..434a26d57c6 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -1,5 +1,5 @@
.light-well {
- background-color: #f8fafc;
+ background-color: $background-color;
padding: 15px;
}
@@ -18,7 +18,7 @@
line-height: 36px;
}
-.gray-content-block {
+.row-content-block {
margin-top: 0;
margin-bottom: -$gl-padding;
background-color: $background-color;
@@ -81,6 +81,11 @@
margin-left: 10px;
}
}
+
+ &.build-content {
+ background-color: $white-light;
+ border-top: none;
+ }
}
.cover-block {
@@ -113,7 +118,7 @@
line-height: 1.1;
h1 {
- color: #313236;
+ color: $gl-gray-dark;
margin-bottom: 6px;
font-size: 23px;
}
@@ -150,6 +155,41 @@
right: auto;
}
}
+
+ &.groups-cover-block {
+ background: $white-light;
+ border-bottom: 1px solid $border-color;
+ text-align: left;
+ padding: 24px 0;
+
+ .group-info {
+ .cover-title {
+ margin-top: 9px;
+ }
+
+ p {
+ margin-bottom: 0;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ text-align: center;
+
+ .avatar {
+ float: none;
+ }
+ }
+ }
+
+ .group-info {
+
+ h1 {
+ display: inline;
+ font-weight: normal;
+ font-size: 24px;
+ color: $gl-title-color;
+ }
+ }
}
.block-connector {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e8c0172680d..eaf85bb17ca 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -59,7 +59,7 @@
}
@mixin btn-gray {
- @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
+ @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark);
}
@mixin btn-white {
@@ -139,11 +139,19 @@
pointer-events: auto !important;
}
+ &[disabled] {
+ pointer-events: none !important;
+ }
+
.caret {
margin-left: 5px;
}
}
+.btn-lg {
+ padding: 12px 20px;
+}
+
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
@@ -243,3 +251,10 @@
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
+
+.btn-build {
+ margin-left: 10px;
+ i {
+ color: $gl-icon-color;
+ }
+}
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 0b3af592d4a..11f39d583bd 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -54,6 +54,10 @@
fill: #254e77 !important;
}
+ .future {
+ visibility: hidden;
+ }
+
.domain-background {
fill: none;
shape-rendering: crispedges;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 2ade341c9dd..3386523dbf7 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -11,6 +11,7 @@
.prepend-top-10 { margin-top: 10px }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px }
+.prepend-left-5 { margin-left: 5px }
.prepend-left-10 { margin-left: 10px }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ba6c7930cdc..4bf3a050403 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -80,7 +80,7 @@
padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
- border-radius: $dropdown-border-radius;
+ border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -248,7 +248,7 @@
.dropdown-title {
position: relative;
- padding: 0 25px 15px;
+ padding: 0 25px 10px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
@@ -278,7 +278,7 @@
right: 5px;
width: 20px;
height: 20px;
- top: -1px;
+ top: -3px;
}
.dropdown-menu-back {
@@ -320,7 +320,7 @@
}
}
-.dropdown-input-field {
+.dropdown-input-field, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
@@ -358,6 +358,13 @@
border-top: 1px solid $dropdown-divider-color;
}
+.dropdown-due-date-footer {
+ padding-top: 0;
+ margin-left: 10px;
+ margin-right: 10px;
+ border-top: 0;
+}
+
.dropdown-footer-list {
font-size: 14px;
@@ -395,3 +402,122 @@
height: 15px;
border-radius: $border-radius-base;
}
+
+.dropdown-menu-due-date {
+ .dropdown-content {
+ max-height: 230px;
+ }
+
+ .ui-widget {
+ table {
+ margin: 0;
+ }
+
+ &.ui-datepicker-inline {
+ padding: 0 10px;
+ border: 0;
+ width: 100%;
+ }
+
+ .ui-datepicker-header {
+ padding: 0 8px 10px;
+ border: 0;
+
+ .ui-icon {
+ background: none;
+ font-size: 20px;
+ text-indent: 0;
+
+ &:before {
+ display: block;
+ position: relative;
+ top: -2px;
+ color: $dropdown-title-btn-color;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+ }
+
+ .ui-state-active,
+ .ui-state-hover {
+ color: $md-link-color;
+ background-color: $calendar-hover-bg;
+ }
+
+ .ui-datepicker-prev,
+ .ui-datepicker-next {
+ top: 0;
+ height: 15px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: transparent;
+ border: 0;
+
+ .ui-icon:before {
+ color: $md-link-color;
+ }
+ }
+ }
+
+ .ui-datepicker-prev {
+ left: 0;
+
+ .ui-icon:before {
+ content: '\f104';
+ text-align: left;
+ }
+ }
+
+ .ui-datepicker-next {
+ right: 0;
+
+ .ui-icon:before {
+ content: '\f105';
+ text-align: right;
+ }
+ }
+
+ td {
+ padding: 0;
+ border: 1px solid $calendar-border-color;
+
+ &:first-child {
+ border-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ a {
+ line-height: 17px;
+ border: 0;
+ border-radius: 0;
+ }
+ }
+
+ .ui-datepicker-title {
+ color: $gl-gray;
+ font-size: 15px;
+ line-height: 1;
+ font-weight: normal;
+ }
+ }
+
+ th {
+ padding: 2px 0;
+ color: $calendar-header-color;
+ font-weight: normal;
+ text-transform: lowercase;
+ border-top: 1px solid $calendar-border-color;
+ }
+
+ .ui-datepicker-unselectable {
+ background-color: $calendar-unselectable-bg;
+ }
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 789df42fb66..61d9954c6c8 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -38,12 +38,14 @@
.filename {
&.old {
+ display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
+ display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
@@ -82,10 +84,6 @@
}
}
- &.blob_file {
-
- }
-
&.blob-no-preview {
background: #eee;
text-shadow: 0 1px 2px #fff;
@@ -129,6 +127,11 @@
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
+
+ i {
+ float: none;
+ margin-right: 0;
+ }
}
td.lines {
padding: 0;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 54cb5461113..558b133f593 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -78,6 +78,24 @@ label {
border-radius: 3px;
}
+.select-wrapper {
+ position: relative;
+
+ .caret {
+ position: absolute;
+ right: 10px;
+ top: $gl-padding;
+ color: $gray-darkest;
+ pointer-events: none;
+ }
+}
+
+.select-control {
+ padding-left: 10px;
+ padding-right: 10px;
+ -webkit-appearance: none;
+}
+
.form-control-inline {
display: inline;
}
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index c83cf881596..dc4668877f2 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -24,6 +24,10 @@
background-color: $color-darker;
a {
color: #fff;
+
+ h3 {
+ color: #fff;
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 3f015427d07..8190a97ed58 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -8,7 +8,7 @@ header {
&.navbar-empty {
height: 58px;
background: #fff;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid $btn-gray-hover;
.center-logo {
margin: 11px 0;
@@ -22,13 +22,21 @@ header {
}
&.navbar-gitlab {
- padding: 0 20px;
+ padding: 0 16px;
z-index: 100;
margin-bottom: 0;
- min-height: $header-height;
- background-color: #fff;
+ height: $header-height;
+ background-color: $background-color;
border: none;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid $border-color;
+
+ @media (max-width: $screen-xs-min) {
+ padding: 0 16px;
+ }
+
+ &.with-horizontal-nav {
+ border-bottom: none;
+ }
.container-fluid {
width: 100% !important;
@@ -47,7 +55,7 @@ header {
text-align: center;
&:hover, &:focus, &:active {
- background-color: #fff;
+ background-color: $background-color;
}
}
@@ -56,22 +64,54 @@ header {
margin: 6px 0;
border-radius: 0;
position: absolute;
- right: 2px;
+ right: -10px;
+ padding: 6px 10px;
&:hover {
- background-color: #eee;
+ background-color: $btn-gray-hover;
}
+
&.active {
color: $gl-icon-color;
}
}
}
+
+ &.header-collapsed {
+ padding: 0 16px;
+ }
+
+ .side-nav-toggle {
+ display: none;
+ position: absolute;
+ left: -10px;
+ margin: 6px 0;
+ padding: 6px 10px;
+ border: none;
+ background-color: $background-color;
+
+ &:hover {
+ background-color: $btn-gray-hover;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ }
+ }
}
.header-content {
position: relative;
height: $header-height;
- padding-right: 20px;
+ padding-right: 40px;
+
+ @media (max-width: $screen-xs-min) {
+ padding-left: 40px;
+ }
@media (min-width: $screen-sm-min) {
padding-right: 0;
@@ -122,6 +162,10 @@ header {
}
}
+ .project-item-select-holder {
+ display: inline;
+ }
+
.impersonation i {
color: $red-normal;
}
@@ -137,6 +181,10 @@ header {
@media (min-width: $screen-md-min) {
@include collapsed-header;
}
+
+ @media (max-width: $screen-xs-min) {
+ margin-left: 0;
+ }
}
.header-expanded {
@@ -145,6 +193,10 @@ header {
@media (min-width: $screen-md-min) {
margin-left: $sidebar_width;
}
+
+ @media (max-width: $screen-xs-min) {
+ margin-left: 0;
+ }
}
@media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 7f7b7c806e7..8bfc0d583c5 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,7 +5,7 @@
*/
.status-box {
-
+
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 0f32d36d59c..fd885b38680 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -90,3 +90,12 @@
box-shadow: none;
width: 100%;
}
+
+.md {
+ &.md-preview-holder {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 66180f38a4f..33cbee85987 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -30,7 +30,7 @@
}
.rss-btn {
- display: none !important;
+ display: none;
}
.project-home-links {
@@ -70,13 +70,6 @@
display: none;
}
- .issue-details {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
-
%ul.notes .note-role, .note-actions {
display: none;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
new file mode 100644
index 00000000000..26ad2870aa0
--- /dev/null
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -0,0 +1,22 @@
+.modal-body {
+ position: relative;
+ overflow-y: auto;
+ padding: 15px;
+
+ .form-actions {
+ margin: -$gl-padding+1;
+ margin-top: 15px;
+ }
+
+ .text-danger {
+ font-weight: bold;
+ }
+}
+
+body.modal-open {
+ overflow: hidden;
+}
+
+.modal .modal-dialog {
+ width: 860px;
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 192d53b048a..f2d7dc625e0 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -26,8 +26,8 @@
}
&.active a {
- color: #000;
- border-bottom: 2px solid #4688f1;
+ border-bottom: 2px solid $link-underline-blue;
+ color: $black;
}
.badge {
@@ -140,6 +140,12 @@
}
}
+ .project-filter-form {
+ input {
+ background-color: $background-color;
+ }
+ }
+
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
@@ -185,3 +191,73 @@
}
}
}
+
+.layout-nav {
+ position: fixed;
+ top: $header-height;
+ width: 100%;
+ z-index: 1;
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
+ transition-duration: .3s;
+
+ .container-fluid {
+ position: relative;
+ }
+
+ .controls {
+ float: right;
+ padding: 7px 0 0;
+
+ i {
+ color: $layout-link-gray;
+ }
+
+ .fa-rss,
+ .fa-cog {
+ font-size: 16px;
+ }
+
+ .fa-caret-down {
+ margin-left: 5px;
+ color: $gl-icon-color;
+ }
+
+ .dropdown {
+ margin-left: 7px;
+ }
+ }
+
+ .nav-links {
+ border-bottom: none;
+ height: 51px;
+ white-space: nowrap;
+ overflow-x: auto;
+
+ li {
+
+ a {
+ padding-top: 10px;
+ }
+
+ a, i {
+ color: $layout-link-gray;
+ }
+
+ &.active {
+ a, i {
+ color: $black;
+ }
+ }
+
+ .badge {
+ color: $gl-icon-color;
+ }
+ }
+ }
+
+}
+
+.page-with-layout-nav {
+ margin-top: 50px;
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2fab387e17..6efc6ec1e4b 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -7,13 +7,11 @@
.select2-choice {
background: #fff;
border-color: $input-border;
- border-color: $border-white-light;
height: 35px;
padding: $gl-vert-padding $gl-btn-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
-
- @include border-radius($border-radius-default);
+ border-radius: $border-radius-base;
.select2-arrow {
background-image: none;
@@ -121,9 +119,6 @@
}
}
-.select2-container-multi .select2-choices .select2-search-choice {
-}
-
.select2-drop-active {
margin-top: 6px;
font-size: 14px;
@@ -202,6 +197,14 @@
}
}
+.select2-highlighted {
+ .group-result {
+ .group-path {
+ color: #fff;
+ }
+ }
+}
+
.group-result {
.group-image {
float: left;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 18189e985c4..bd91f51708c 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -3,6 +3,7 @@
position: absolute;
width: 58px;
cursor: pointer;
+ margin-top: 8px;
}
.page-with-sidebar {
@@ -62,7 +63,7 @@
float: left;
height: $header-height;
width: 100%;
- padding: 11px 0 11px 22px;
+ padding-left: 22px;
overflow: hidden;
outline: none;
transition-duration: .3s;
@@ -85,7 +86,7 @@
margin: 0;
margin-left: 50px;
font-size: 19px;
- line-height: 41px;
+ line-height: 50px;
font-weight: normal;
}
}
@@ -97,7 +98,7 @@
}
.sidebar-user {
- padding: 9px 22px;
+ padding: 7px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
@@ -209,15 +210,33 @@
}
}
+.sidebar-wrapper {
+ &.hidden-nav {
+ width: 0;
+ }
+}
+
.page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ padding-left: 0;
+ }
+
.sidebar-wrapper {
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
+
.header-logo {
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
+
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
@@ -243,17 +262,35 @@
.collapse-nav a {
width: $sidebar_collapsed_width;
+
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ }
}
.sidebar-user {
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
+ @media (max-width: $screen-xs-min) {
+ width: 0;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
.username {
display: none;
}
}
}
+
+ .layout-nav {
+ padding-right: $sidebar_collapsed_width;
+
+ @media (max-width: $screen-xs-min) {
+ padding-right: 0;;
+ }
+ }
}
.page-sidebar-expanded {
@@ -263,6 +300,10 @@
padding-left: $sidebar_width;
}
+ @media (max-width: $screen-xs-min) {
+ padding-left: 0;
+ }
+
.sidebar-wrapper {
width: $sidebar_width;
@@ -280,6 +321,20 @@
}
}
}
+
+ .layout-nav {
+ @media (max-width: $screen-xs-min) {
+ padding-right: 0;;
+ }
+
+ @media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
+ padding-right: 62px;
+ }
+
+ @media (min-width: $screen-md-min) {
+ padding-right: $sidebar_width;
+ }
+ }
}
.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 75b770ae5a2..b42075c98d0 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -32,13 +32,11 @@ table {
th {
background-color: $background-color;
font-weight: normal;
- font-size: 15px;
- border-bottom: 1px solid $border-color;
+ border-bottom: none;
}
td {
border-color: $table-border-color;
- border-bottom: 1px solid $border-color;
}
}
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index b91f2f6f898..29501069d27 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -11,7 +11,7 @@
border-bottom: 1px solid $border-white-light;
&:target {
- background: $row-hover;
+ background: $line-target-blue;
}
.avatar {
@@ -39,8 +39,7 @@
.diff-file {
border: 1px solid $border-color;
border-bottom: none;
- margin-left: 0;
- margin-right: 0;
+ margin: 0;
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 96bab7880c2..6a45c34ccbb 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -81,7 +81,7 @@
// Labels
.label {
- padding: 2px 4px;
+ padding: 4px 5px;
font-size: 13px;
font-style: normal;
font-weight: normal;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index c72af5dad0a..371c1bf17e1 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding;
//== Code
//
//##
-$pre-bg: #f8fafc !default;
+$pre-bg: $background-color !default;
$pre-color: $gl-gray !default;
-$pre-border-color: #e7e9ed;
+$pre-border-color: $border-color;
$table-bg-accent: $background-color;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0a5b4b8834c..2779cd56788 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -42,14 +42,14 @@
margin: 24px 0 12px;
padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed;
- color: #313236;
+ color: $gl-gray-dark;
}
h2 {
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px;
- color: #313236;
+ color: $gl-gray-dark;
}
h3 {
@@ -205,6 +205,10 @@ h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
+.light-header {
+ font-weight: 600;
+}
+
/** CODE **/
pre {
font-family: $monospace_font;
@@ -259,3 +263,9 @@ h1, h2, h3, h4 {
color: $gl-gray;
}
}
+
+.text-right-lg {
+ @media (min-width: $screen-lg-min) {
+ text-align: right;
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f910cf61817..84e74db06b0 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -12,7 +12,7 @@ $gutter_inner_width: 258px;
*/
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
-$table-border-color: #eef0f2;
+$table-border-color: #ececec;
$background-color: #fafafa;
/*
@@ -20,7 +20,7 @@ $background-color: #fafafa;
*/
$gl-font-size: 15px;
$gl-title-color: #333;
-$gl-text-color: #555;
+$gl-text-color: #5c5c5c;
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
@@ -30,6 +30,7 @@ $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
+$gl-gray-dark: #313236;
$gl-header-color: $gl-title-color;
/*
@@ -65,16 +66,18 @@ $gl-padding-top: 10px;
$row-hover: #f4f8fe;
$progress-color: #c0392b;
$avatar_radius: 50%;
-$header-height: 58px;
+$header-height: 50px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
-$ssh-key-icon-color: #8f8f8f;
-$ssh-key-icon-size: 18px;
+$settings-icon-size: 18px;
$provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1;
+$link-underline-blue: #4a8bee;
+$layout-link-gray: #7e7c7c;
+$todo-alert-blue: #428bca;
/*
* Color schema
@@ -109,6 +112,7 @@ $red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
+$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4;
@@ -168,8 +172,12 @@ $line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
+$line-number-select: #fbf2da;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
+$line-target-blue: #eaf3fc;
+$line-select-yellow: #fcf8e7;
+$line-select-yellow-dark: #f0e2bd;
/*
* Fonts
*/
@@ -179,7 +187,6 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
-$dropdown-border-radius: 2px;
$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
@@ -208,6 +215,7 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
$btn-active-gray: #ececec;
$btn-placeholder-gray: #c7c7c7;
$btn-white-active: #848484;
+$btn-gray-hover: #eee;
/*
* Award emoji
@@ -241,3 +249,8 @@ $note-form-border-color: #e5e5e5;
$note-toolbar-color: #959494;
$zen-control-hover-color: #111;
+
+$calendar-header-color: #b8b8b8;
+$calendar-hover-bg: #ecf3fe;
+$calendar-border-color: rgba(#000, .1);
+$calendar-unselectable-bg: #faf9f9;
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 28253d4ccb4..80a509a7c1a 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -111,8 +111,6 @@
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
-
- .gh { } /* Generic Heading & Diff Header */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 1ff6ad75e07..31a4e3deaac 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -21,11 +21,6 @@
// Diff line
.line_holder {
- td.diff-line-num.hll:not(.empty-cell),
- td.line_content.hll:not(.empty-cell) {
- background-color: #f8eec7;
- border-color: darken(#f8eec7, 15%);
- }
.diff-line-num {
&.old {
@@ -37,11 +32,16 @@
background-color: $line-number-new;
border-color: $line-added-dark;
}
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
}
.line_content {
&.old {
- background: $line-removed;
+ background-color: $line-removed;
span.idiff {
background-color: $line-removed-dark;
@@ -58,7 +58,11 @@
&.match {
color: $black-transparent;
- background: $match-line;
+ background-color: $match-line;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
}
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 201f3e5ca46..aa41565f812 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -83,3 +83,12 @@
}
}
}
+
+table.builds {
+
+ .build-link {
+ a {
+ color: $gl-dark-link-color;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 358d2f4ab9d..c2cd227571f 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -31,9 +31,23 @@
}
.commit-committer-link,
.commit-author-link {
- color: #444;
+ color: $gl-gray;
font-weight: bold;
}
+
+ .time_ago {
+ margin-left: 8px;
+ }
+
+ .fa-clipboard {
+ color: $dropdown-title-btn-color;
+ }
+
+ .commit-info {
+ &.branches {
+ margin-left: 8px;
+ }
+ }
}
.commit-box {
@@ -42,7 +56,7 @@
.commit-title {
margin: 0;
font-size: 23px;
- color: #313236;
+ color: $gl-gray-dark;
}
.commit-description {
@@ -83,6 +97,14 @@
}
}
+.commit-action-buttons {
+ i {
+ color: $gl-icon-color;
+ font-size: 13px;
+ margin-right: 3px;
+ }
+}
+
/*
* Commit message textarea for web editor and
* custom merge request message
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
new file mode 100644
index 00000000000..125f495d6d4
--- /dev/null
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -0,0 +1,18 @@
+.well-confirmation {
+ margin-bottom: 20px;
+ border-bottom: 1px solid #eee;
+
+ > h1 {
+ font-weight: 400;
+ }
+
+ .lead {
+ margin-bottom: 20px;
+ }
+}
+
+.confirmation-content {
+ a {
+ color: $md-link-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5917f089720..5e61e61d85c 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,5 +1,5 @@
.detail-page-header {
- padding: 11px 0;
+ padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
@@ -16,18 +16,13 @@
.issue_created_ago, .author_link {
white-space: nowrap;
}
-
- .issue-meta {
- display: inline-block;
- line-height: 20px;
- }
}
.detail-page-description {
.title {
margin: 0;
font-size: 23px;
- color: #313236;
+ color: $gl-gray-dark;
}
.description {
@@ -41,4 +36,11 @@
}
}
}
+
+ .wiki {
+ code {
+ white-space: pre-wrap;
+ word-break: keep-all;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 183f22a1b24..1a7d5f9666e 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -34,6 +34,7 @@
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
+ -webkit-overflow-scrolling: auto;
.unfold {
cursor: pointer;
@@ -86,7 +87,7 @@
}
span {
- white-space: pre;
+ white-space: pre-wrap;
}
}
}
@@ -97,7 +98,11 @@
}
td.line_content.parallel {
- width: 50%;
+ width: 46%;
+ }
+
+ .add-diff-note {
+ margin-left: -65px;
}
}
@@ -126,8 +131,13 @@
margin: 0;
padding: 0 0.5em;
border: none;
+
&.parallel {
display: table-cell;
+
+ span {
+ word-break: break-all;
+ }
}
}
@@ -335,7 +345,7 @@
}
.diff-file .line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
.diff-wrap-lines .line_content {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 0f0592a0ab8..8981f070a20 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -26,6 +26,10 @@
line-height: 42px;
padding-top: 7px;
padding-bottom: 7px;
+
+ .pull-right {
+ height: 20px;
+ }
}
.editor-ref {
@@ -53,4 +57,9 @@
.select2 {
float: right;
}
+
+ .encoding-selector,
+ .license-selector {
+ display: inline-block;
+ }
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4e5c4ed84b6..f7f9a9bb770 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -18,9 +18,6 @@
}
.graphs {
- .graph-author-commits-count {
- }
-
.graph-author-email {
float: right;
color: #777;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 604f1700cf8..4a95b7b852e 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -55,23 +55,6 @@
}
}
-.modal-body {
- position: relative;
- overflow-y: auto;
- padding: 15px;
- .form-actions {
- margin: -$gl-padding+1;
- }
-}
-
-body.modal-open {
- overflow: hidden;
-}
-
-.modal .modal-dialog {
- width: 860px;
-}
-
.documentation {
padding: 7px;
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 6a99cd9cb94..84cc35239f9 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -16,3 +16,24 @@ i.icon-gitorious-big {
width: 18px;
height: 18px;
}
+
+.import-jobs-from-col,
+.import-jobs-to-col {
+ width: 40%;
+}
+
+.import-jobs-status-col {
+ width: 20%;
+}
+
+.btn-import {
+ .loading-icon {
+ display: none;
+ }
+
+ &.is-loading {
+ .loading-icon {
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6bd90a23620..1cf3023ecc9 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -128,6 +128,7 @@
top: 58px;
bottom: 0;
right: 0;
+ z-index: 10;
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
@@ -241,16 +242,20 @@
}
}
- .btn {
+ .issuable-pager {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
+
+ &.btn-primary {
+ @extend .btn-primary
+ }
}
- a:not(.btn) {
+ a:not(.issuable-pager) {
&:hover {
color: $md-link-color;
text-decoration: none;
@@ -273,10 +278,6 @@
}
}
-.btn-default.gutter-toggle {
- margin-top: 4px;
-}
-
.detail-page-description {
small {
color: $gray-darkest;
@@ -322,3 +323,50 @@
padding-top: 7px;
}
}
+
+.issuable-status-box {
+ float: none;
+ display: inline-block;
+ margin-top: 0;
+
+ @media (max-width: $screen-xs-max) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+}
+
+.issuable-header {
+ position: relative;
+ padding-left: 45px;
+ padding-right: 45px;
+ line-height: 35px;
+
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+.issuable-actions {
+ padding-top: 10px;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ padding-top: 0;
+ }
+}
+
+.issuable-gutter-toggle {
+ @media (max-width: $screen-sm-max) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+}
+
+.issuable-meta {
+ display: inline-block;
+ line-height: 18px;
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6a1d28590c2..fc9db97132d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
- margin-top: 5px;
-
- .btn-group {
- width: 100%;
-
- ul {
- width: 100%;
- text-align: center;
- }
- }
.btn {
width: 100%;
-
- &:first-child:not(:last-child) {
-
- }
-
- &:not(:first-child):not(:last-child) {
- margin-top: 10px;
- }
-
- &:last-child:not(:first-child) {
- margin-top: 10px;
- }
- }
- }
-
- .issue {
- &:hover .issue-actions {
- display: none !important;
- }
-
- .issue-updated-at {
- display: none;
}
}
}
@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color;
margin-left: 52px;
}
-
-.editor-details {
- display: block;
-
- @media (min-width: $screen-sm-min) {
- display: inline-block;
- }
-}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 4ef548ffbe7..c4005ba1e69 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -104,7 +104,7 @@
font-weight: 600;
font-size: 17px;
margin: 5px 0;
- color: #313236;
+ color: $gl-gray-dark;
}
p:last-child {
@@ -136,7 +136,7 @@
}
.label-branch {
- color: #313236;
+ color: $gl-gray-dark;
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
@@ -272,3 +272,19 @@
display: inline-block;
width: 250px;
}
+
+.table-holder {
+ .builds {
+
+ th {
+ background-color: $white-light;
+ color: $gl-placeholder-color;
+ }
+
+ th,
+ td {
+ padding: 16px;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index d0e72a4422c..b94f524b513 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -28,7 +28,7 @@ li.milestone {
// Issue title
span a {
- color: rgba(0,0,0,0.64);
+ color: $gl-text-color;
}
}
}
@@ -51,7 +51,7 @@ li.milestone {
margin-top: 7px;
.issuable-number {
- color: rgba(0,0,0,0.44);
+ color: $gl-placeholder-color;
margin-right: 5px;
}
.avatar {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 07c707e7b77..7fa13e66b43 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -42,6 +42,7 @@
.note-textarea {
display: block;
padding: 10px 0;
+ color: $gl-gray;
font-family: $regular_font;
border: 0;
@@ -61,11 +62,11 @@
padding: $gl-padding-top $gl-padding;
border: 1px solid $note-form-border-color;
border-radius: $border-radius-base;
+ transition: border-color ease-in-out 0.15s,
+ box-shadow ease-in-out 0.15s;
&.is-focused {
- border-color: $focus-border-color;
- box-shadow: 0 0 2px $black-transparent,
- 0 0 4px rgba($focus-border-color, .4);
+ @extend .form-control:focus;
.comment-toolbar,
.nav-links {
@@ -83,18 +84,6 @@
border-color: $gl-success;
}
}
-
- p {
- code {
- white-space: normal;
- }
-
- pre {
- code {
- white-space: pre;
- }
- }
- }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 9d808cce152..8ad47a50541 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -80,16 +80,8 @@ ul.notes {
@include md-typography;
// On diffs code should wrap nicely and not overflow
- p {
- code {
- white-space: normal;
- }
-
- pre {
- code {
- white-space: pre;
- }
- }
+ code {
+ white-space: pre-wrap;
}
// Reset ul style types since we're nested inside a ul already
@@ -116,10 +108,10 @@ ul.notes {
border-color: darken(#f5f5f5, 8%);
margin: 10px 0;
}
- }
- a {
- word-break: break-all;
+ code {
+ word-break: keep-all;
+ }
}
}
@@ -136,7 +128,7 @@ ul.notes {
margin-right: 10px;
}
.line_content {
- white-space: pre;
+ white-space: pre-wrap;
}
}
@@ -175,6 +167,11 @@ ul.notes {
.notes {
background-color: $white-light;
}
+
+ a code {
+ top: 0;
+ margin-right: 0;
+ }
}
}
}
@@ -190,6 +187,9 @@ ul.notes {
}
}
+ .author_link {
+ color: $gl-gray;
+ }
}
.note-headline-light,
@@ -197,6 +197,12 @@ ul.notes {
color: $notes-light-color;
}
+.discussion-headline-light {
+ a {
+ color: $gl-link-color;
+ }
+}
+
/**
* Actions for Discussions/Notes
*/
@@ -208,6 +214,17 @@ ul.notes {
color: $notes-action-color;
}
+.discussion-actions {
+ @media (max-width: $screen-md-max) {
+ float: none;
+ margin-left: 0;
+
+ .note-action-button {
+ margin-left: 0;
+ }
+ }
+}
+
.note-action-button,
.discussion-action-button {
display: inline-block;
@@ -285,7 +302,7 @@ ul.notes {
padding: 4px;
font-size: 16px;
color: $gl-link-color;
- margin-left: -60px;
+ margin-left: -56px;
position: absolute;
z-index: 10;
width: 32px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index a9656e5cae7..843379a3f54 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -18,7 +18,8 @@
}
.account-btn-link,
-.profile-settings-sidebar a {
+.profile-settings-sidebar a,
+.settings-sidebar a {
color: $md-link-color;
}
@@ -123,12 +124,6 @@
}
}
-.key-icon {
- color: $ssh-key-icon-color;
- font-size: $ssh-key-icon-size;
- line-height: 42px;
-}
-
.key-created-at {
line-height: 42px;
}
@@ -180,14 +175,6 @@
}
}
-.profile-settings-message {
- line-height: 32px;
- color: $warning-message-color;
- background-color: $warning-message-bg;
- border: 1px solid $warning-message-border;
- border-radius: $border-radius-base;
-}
-
.oauth-applications {
form {
display: inline-block;
@@ -218,3 +205,21 @@
text-align: center;
}
}
+
+.user-profile {
+ @media (max-width: $screen-xs-max) {
+ .cover-block {
+ padding-top: 20px;
+ }
+
+ .cover-controls {
+ position: static;
+ margin-bottom: 20px;
+
+ .btn {
+ display: inline-block;
+ width: 46%;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index fcca9d4faf5..c20f04653fc 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -178,7 +178,7 @@
.option-title {
font-weight: normal;
display: inline-block;
- color: #313236;
+ color: $gl-gray-dark;
}
.option-descr {
@@ -202,8 +202,31 @@
min-width: 200px;
}
-.deploy-project-label {
- margin: 1px;
+.deploy-key-content {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+
+ &:last-child {
+ float: right;
+ }
+ }
+}
+
+.deploy-key-projects {
+ @media (min-width: $screen-sm-min) {
+ line-height: 42px;
+ }
+}
+
+a.deploy-project-label {
+ padding: 5px;
+ margin-right: 5px;
+ color: $gl-gray;
+ background-color: $row-hover;
+
+ &:hover {
+ color: $gl-link-color;
+ }
}
.vs-public {
@@ -256,12 +279,6 @@
}
}
-table.table.protected-branches-list tr.no-border {
- th, td {
- border: 0;
- }
-}
-
.project-import .btn {
float: left;
margin-right: 10px;
@@ -474,3 +491,14 @@ pre.light-well {
color: #fff;
}
}
+
+.protected-branches-list {
+ a {
+ color: $gl-gray;
+ font-weight: 600;
+
+ &:hover {
+ color: $gl-link-color;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index f0f3744c6fa..2bff70c8c64 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -10,17 +10,6 @@
}
}
-.search-holder {
- max-width: 600px;
- margin: 0 auto;
- margin-bottom: 20px;
-
- input {
- border-color: #bbb;
- font-weight: bold;
- }
-}
-
.search {
margin-right: 10px;
margin-left: 10px;
@@ -159,7 +148,85 @@
&.has-location-badge {
.search-input-wrap {
- width: 78%;
+ width: 68%;
}
}
}
+
+.search-holder {
+ @media (min-width: $screen-sm-min) {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ .search-field-holder {
+ -webkit-flex: 1 0 auto;
+ -ms-flex: 1 0 auto;
+ flex: 1 0 auto;
+ position: relative;
+ margin-right: 0;
+
+ @media (min-width: $screen-sm-min) {
+ margin-right: 5px;
+ }
+ }
+
+ .search-icon {
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ color: $gray-darkest;
+ pointer-events: none;
+ }
+
+ .search-text-input {
+ padding-left: $gl-padding + 15px;
+ padding-right: $gl-padding + 15px;
+ }
+
+ .btn-search {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: auto;
+ margin-top: 0;
+ margin-left: 5px;
+ }
+ }
+
+ .dropdown {
+ @media (min-width: $screen-sm-min) {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ }
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ margin-top: 5px;
+
+ @media (min-width: $screen-sm-min) {
+ width: 160px;
+ margin-top: 0;
+ }
+ }
+}
+
+.search-clear {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ padding: 0;
+ color: $gray-darkest;
+ line-height: 0;
+ background: none;
+ border: 0;
+
+ &:hover,
+ &:focus {
+ color: $gl-link-color;
+ outline: none;
+ }
+}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
new file mode 100644
index 00000000000..3fb70085713
--- /dev/null
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -0,0 +1,14 @@
+.settings-list-icon {
+ color: $gl-placeholder-color;
+ font-size: $settings-icon-size;
+ line-height: 42px;
+}
+
+.settings-message {
+ padding: 5px;
+ line-height: 1.3;
+ color: $warning-message-color;
+ background-color: $warning-message-bg;
+ border: 1px solid $warning-message-border;
+ border-radius: $border-radius-base;
+}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index dbb6daf0d70..2370d35924e 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -1,7 +1,7 @@
.container-fluid {
.ci-status {
padding: 2px 7px;
- margin-right: 5px;
+ margin-right: 10px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 75f78569e3c..e51c3491dae 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -6,9 +6,16 @@
.navbar-nav {
li {
.badge.todos-pending-count {
- background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
+ background: $todo-alert-blue;
+ margin-left: -17px;
+ font-size: 11px;
+ color: white;
+ padding: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 3px;
}
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 25b5e95583e..a84fc2e0318 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -16,7 +16,7 @@
tr {
> td, > th {
- line-height: 26px;
+ line-height: 23px;
}
&:hover {
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index 1be0551ad3b..a30b6492572 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -1,17 +1,37 @@
-/* Generic print styles */
-header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
-.profiler-results {display: none;}
-
-/* Styles targeted specifically at printing files */
-.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
-.file-title {display: none;}
-.file-holder {border: none;}
-
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
.wiki h1 {font-size: 30px;}
.wiki h2 {font-size: 22px;}
.wiki h3 {font-size: 18px; font-weight: bold; }
-.sidebar-wrapper { display: none; }
-.nav { display: none; }
-.btn { display: none; }
+header,
+nav,
+nav.main-nav,
+nav.navbar-collapse,
+nav.navbar-collapse.collapse,
+.profiler-results,
+.tree-ref-holder,
+.tree-holder .breadcrumb,
+.blob-commit-info,
+.file-title,
+.file-holder,
+.sidebar-wrapper,
+.nav,
+.btn,
+ul.notes-form,
+.merge-request-ci-status .ci-status-link:after,
+.issuable-gutter-toggle,
+.gutter-toggle,
+.issuable-details .content-block-small,
+.edit-link,
+.note-action-button {
+ display: none!important;
+}
+
+.page-gutter {
+ padding-top: 0;
+ padding-left: 0;
+}
+
+.right-sidebar {
+ top: 0;
+}
diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 9083bfb41cf..cf795d977ce 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin'
def authenticate_admin!
- return render_404 unless current_user.is_admin?
- end
-
- def authorize_impersonator!
- if session[:impersonator_id]
- User.find_by!(username: session[:impersonator_id]).admin?
- end
+ render_404 unless current_user.is_admin?
end
end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b4a28b8dd3f..ec22548ddeb 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:admin_notification_email,
:user_oauth_applications,
:shared_runners_enabled,
+ :shared_runners_text,
:max_artifacts_size,
:metrics_enabled,
:metrics_host,
@@ -92,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key,
:email_author_in_body,
:repository_checks_enabled,
+ :metrics_packet_size,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 0bd19c49d8f..4e85b6b4cf2 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -39,6 +39,12 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :enable_ssl_verification)
+ params.require(:hook).permit(
+ :enable_ssl_verification,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
deleted file mode 100644
index bf98af78615..00000000000
--- a/app/controllers/admin/impersonation_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-class Admin::ImpersonationController < Admin::ApplicationController
- skip_before_action :authenticate_admin!, only: :destroy
-
- before_action :user
- before_action :authorize_impersonator!
-
- def create
- if @user.blocked?
- flash[:alert] = "You cannot impersonate a blocked user"
-
- redirect_to admin_user_path(@user)
- else
- session[:impersonator_id] = current_user.username
- session[:impersonator_return_to] = admin_user_path(@user)
-
- warden.set_user(user, scope: 'user')
-
- flash[:alert] = "You are impersonating #{user.username}."
-
- redirect_to root_path
- end
- end
-
- def destroy
- redirect = session[:impersonator_return_to]
-
- warden.set_user(user, scope: 'user')
-
- session[:impersonator_return_to] = nil
- session[:impersonator_id] = nil
-
- redirect_to redirect || root_path
- end
-
- def user
- @user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
- end
-end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
new file mode 100644
index 00000000000..8be35f00a77
--- /dev/null
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -0,0 +1,26 @@
+class Admin::ImpersonationsController < Admin::ApplicationController
+ skip_before_action :authenticate_admin!
+ before_action :authenticate_impersonator!
+
+ def destroy
+ original_user = current_user
+
+ warden.set_user(impersonator, scope: :user)
+
+ Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}")
+
+ session[:impersonator_id] = nil
+
+ redirect_to admin_user_path(original_user)
+ end
+
+ private
+
+ def impersonator
+ @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+ end
+
+ def authenticate_impersonator!
+ render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9abf08d0e19..f2f654c7bcd 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -31,6 +31,24 @@ class Admin::UsersController < Admin::ApplicationController
user
end
+ def impersonate
+ if user.blocked?
+ flash[:alert] = "You cannot impersonate a blocked user"
+
+ redirect_to admin_user_path(user)
+ else
+ session[:impersonator_id] = current_user.id
+
+ warden.set_user(user, scope: :user)
+
+ Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}")
+
+ flash[:alert] = "You are now impersonating #{user.username}"
+
+ redirect_to root_path
+ end
+ end
+
def block
if user.block
redirect_back_or_admin_user(notice: "Successfully blocked")
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1c53b0b21a3..17b3f49aed1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- current_application_settings.after_sign_out_path || new_user_session_path
+ current_application_settings.after_sign_out_path.presence || new_user_session_path
end
def abilities
diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb
index f63b703d101..586f97c5eb4 100644
--- a/app/controllers/concerns/filter_projects.rb
+++ b/app/controllers/concerns/filter_projects.rb
@@ -10,6 +10,8 @@ module FilterProjects
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.non_archived if params[:archived].blank?
+ projects = projects.personal(current_user) if params[:personal].present? && current_user
+
projects
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index af1faca93f6..7b66ad3f92c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -1,7 +1,16 @@
class ConfirmationsController < Devise::ConfirmationsController
+ def almost_there
+ flash[:notice] = nil
+ render layout: "devise_empty"
+ end
+
protected
+ def after_resending_confirmation_instructions_path_for(resource)
+ users_almost_there_path
+ end
+
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
after_sign_in_path_for(resource)
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 55050615473..9b5c43b17e2 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end
def ui
+ @user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end
private
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 74150ad606b..be872a93fee 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- view = params[:view] || cookies[:diff_view]
- cookies.permanent[:diff_view] = params[:view] = view if view
+ cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index f159e169f6d..b8b9e78427d 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,7 +1,7 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
- before_action :authorize_update_build!, except: [:index, :show, :status]
+ before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
def index
@@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController
notice: "Build has been sucessfully erased!"
end
+ def raw
+ if @build.has_trace?
+ send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
+ else
+ render_404
+ end
+ end
+
private
def build
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 576fa3cedb2..a202cb38692 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
before_action :define_show_vars, only: [:show, :builds]
- before_action :authorize_edit_tree!, only: [:revert]
+ before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
def show
apply_diff_view_cookie!
@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end
def cancel_builds
- ci_commit.builds.running_or_pending.each(&:cancel)
+ ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
- ci_commit.builds.latest.failed.each do |build|
+ ci_builds.latest.failed.each do |build|
if build.retryable?
Ci::Build.retry(build)
end
@@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController
end
def revert
- assign_revert_commit_vars
+ assign_change_commit_vars(@commit.revert_branch_name)
return render_404 if @target_branch.blank?
- create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.",
- success_path: successful_revert_path, failure_path: failed_revert_path)
+ create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
+ success_path: successful_change_path, failure_path: failed_change_path)
end
+
+ def cherry_pick
+ assign_change_commit_vars(@commit.cherry_pick_branch_name)
+
+ return render_404 if @target_branch.blank?
- private
-
- def revert_type_title
- @commit.merged_merge_request ? 'merge request' : 'commit'
+ create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
+ success_path: successful_change_path, failure_path: failed_change_path)
end
- def successful_revert_path
+ private
+
+ def successful_change_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commits_url(@project.namespace, @project, @target_branch)
end
- def failed_revert_path
+ def failed_change_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commit_url(@project.namespace, @project, params[:id])
@@ -94,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
- def ci_commit
- @ci_commit ||= project.ci_commit(commit.sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: commit.sha)
+ end
+
+ def ci_builds
+ @ci_builds ||= Ci::Build.where(commit: ci_commits)
end
def define_show_vars
@@ -108,17 +117,17 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
- @statuses = ci_commit.statuses if ci_commit
+ @statuses = CommitStatus.where(commit: ci_commits)
+ @builds = Ci::Build.where(commit: ci_commits)
end
- def assign_revert_commit_vars
+ def assign_change_commit_vars(mr_source_branch)
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
- @mr_source_branch = @commit.revert_branch_name
+ @mr_source_branch = mr_source_branch
@mr_target_branch = @target_branch
@commit_params = {
commit: @commit,
- revert_type_title: revert_type_title,
create_merge_request: params[:create_merge_request].present? || different_project?
}
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 1420b96840c..a52c614b259 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -15,7 +15,7 @@ class Projects::CommitsController < Projects::ApplicationController
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else
- @repository.commits(@ref, @path, @limit, @offset)
+ @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 7d09288bc80..83d5ced9be8 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -7,31 +7,24 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
- @enabled_keys = @project.deploy_keys
-
- @available_keys = accessible_keys - @enabled_keys
- @available_project_keys = current_user.project_deploy_keys - @enabled_keys
- @available_public_keys = DeployKey.are_public - @enabled_keys
-
- # Public keys that are already used by another accessible project are already
- # in @available_project_keys.
- @available_public_keys -= @available_project_keys
+ @key = DeployKey.new
+ set_index_vars
end
def new
- @key = @project.deploy_keys.new
-
- respond_with(@key)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace,
+ @project)
end
def create
@key = DeployKey.new(deploy_key_params)
+ set_index_vars
if @key.valid? && @project.deploy_keys << @key
redirect_to namespace_project_deploy_keys_path(@project.namespace,
@project)
else
- render "new"
+ render "index"
end
end
@@ -51,6 +44,18 @@ class Projects::DeployKeysController < Projects::ApplicationController
protected
+ def set_index_vars
+ @enabled_keys ||= @project.deploy_keys
+
+ @available_keys ||= accessible_keys - @enabled_keys
+ @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
+ @available_public_keys ||= DeployKey.are_public - @enabled_keys
+
+ # Public keys that are already used by another accessible project are already
+ # in @available_project_keys.
+ @available_public_keys -= @available_project_keys
+ end
+
def accessible_keys
@accessible_keys ||= current_user.accessible_deploy_keys
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index d13ea9f34b6..092ef32e6e3 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -17,7 +17,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
- @commits = @project.repository.commits(@ref, nil, 2000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 2000, skip_merges: true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@@ -55,7 +55,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
- @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
+ @commits = @project.repository.commits(@ref, limit: 6000, skip_merges: true)
@log = []
@commits.each do |commit|
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 4159e53bfa9..606552fa853 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
end
def create
- link = project.project_group_links.new
- link.group_id = params[:link_group_id]
- link.group_access = params[:link_group_access]
- link.save
+ group = Group.find(params[:link_group_id])
+ return render_404 unless can?(current_user, :read_group, group)
+
+ project.project_group_links.create(
+ group: group, group_access: params[:link_group_access]
+ )
redirect_to namespace_project_group_links_path(project.namespace, project)
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 5fd4f855dec..dfa9bd259e8 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -52,8 +52,16 @@ class Projects::HooksController < Projects::ApplicationController
end
def hook_params
- params.require(:hook).permit(:url, :push_events, :issues_events,
- :merge_requests_events, :tag_push_events, :note_events,
- :build_events, :enable_ssl_verification)
+ params.require(:hook).permit(
+ :build_events,
+ :enable_ssl_verification,
+ :issues_events,
+ :merge_requests_events,
+ :note_events,
+ :push_events,
+ :tag_push_events,
+ :token,
+ :url
+ )
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 86ba40facc5..5f3745d94b9 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -4,8 +4,8 @@ class Projects::IssuesController < Projects::ApplicationController
include ToggleAwardEmoji
before_action :module_enabled
- before_action :issue,
- only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
+ before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
+ :related_branches, :can_create_branch]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -34,14 +34,15 @@ class Projects::IssuesController < Projects::ApplicationController
end
@issues = @issues.page(params[:page])
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.atom { render layout: false }
format.json do
render json: {
- html: view_to_html_string("projects/issues/_issues")
+ html: view_to_html_string("projects/issues/_issues"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -96,12 +97,13 @@ class Projects::IssuesController < Projects::ApplicationController
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
+ return render_404 unless issue.can_move?(current_user, new_project)
+
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
respond_to do |format|
- format.js
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
@@ -110,7 +112,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
- render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
end
@@ -129,10 +131,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def related_branches
- merge_requests = @issue.referenced_merge_requests(current_user)
-
- @related_branches = @issue.related_branches -
- merge_requests.map(&:source_branch)
+ @related_branches = @issue.related_branches(current_user)
respond_to do |format|
format.json do
@@ -143,6 +142,18 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def can_create_branch
+ can_create = current_user &&
+ can?(current_user, :push_code, @project) &&
+ @issue.can_be_worked_on?(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: { can_create_branch: can_create }
+ end
+ end
+ end
+
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
@@ -196,7 +207,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description, :confidential,
- :milestone_id, :state_event, :task_num, label_ids: []
+ :milestone_id, :due_date, :state_event, :task_num, label_ids: []
)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9117f9242cd..164c035e8bc 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -39,13 +39,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
- @label = @project.labels.find_by(title: params[:label_name])
+ @labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.json do
render json: {
- html: view_to_html_string("projects/merge_requests/_merge_requests")
+ html: view_to_html_string("projects/merge_requests/_merge_requests"),
+ labels: @labels.as_json(methods: :text_color)
}
end
end
@@ -149,13 +150,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid?
respond_to do |format|
- format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
- render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
+ render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
@@ -322,6 +322,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
+ @ci_commits = [@ci_commit].compact
closes_issues
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index e457db2f0b7..33b2625c0ac 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,6 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
- before_action :authorize_admin_project_member!, except: :leave
+ before_action :authorize_admin_project_member!, except: [:leave, :index]
def index
@project_members = @project.project_members
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 8b2577aebe1..739681f4085 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
- :note_events, :build_events,
+ :note_events, :build_events, :wiki_page_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 9f3a4a69721..0d6c32fabd2 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -40,11 +40,11 @@ class Projects::WikisController < Projects::ApplicationController
end
def update
- @page = @project_wiki.find_page(params[:id])
-
return render('empty') unless can?(current_user, :create_wiki, @project)
- if @page.update(content, format, message)
+ @page = @project_wiki.find_page(params[:id])
+
+ if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController
end
def create
- @page = WikiPage.new(@project_wiki)
+ @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
- if @page.create(wiki_params)
+ if @page.persisted?
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, @page),
notice: 'Wiki was successfully updated.'
@@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController
params[:wiki].slice(:title, :content, :format, :message)
end
- def content
- params[:wiki][:content]
- end
-
- def format
- params[:wiki][:format]
- end
-
- def message
- params[:wiki][:message]
- end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c48175a4c5a..352bff19383 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,6 +8,13 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+ # To avoid duplicate form fields on the login page, the registration form
+ # names fields using `new_user`, but Devise still wants the params in
+ # `user`.
+ if params["new_#{resource_name}"].present? && params[resource_name].blank?
+ params[resource_name] = params.delete(:"new_#{resource_name}")
+ end
+
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
@@ -31,11 +38,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
- new_user_session_path
+ users_almost_there_path
end
private
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e42d2d73947..69c92d2bed2 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -8,8 +8,6 @@ class SearchController < ApplicationController
def show
return if params[:search].nil? || params[:search].blank?
- @search_term = params[:search]
-
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
@@ -20,6 +18,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group)
end
+ @search_term = params[:search]
+
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
Search::GlobalService.new(current_user, params).execute
end
- @objects = @search_results.objects(@scope, params[:page])
+ @search_objects = @search_results.objects(@scope, params[:page])
end
def autocomplete
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 8e7956da48f..2ae180c8a12 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,6 +1,7 @@
class UsersController < ApplicationController
skip_before_action :authenticate_user!
- before_action :set_user
+ before_action :user
+ before_action :authorize_read_user!, only: [:show]
def show
respond_to do |format|
@@ -75,22 +76,26 @@ class UsersController < ApplicationController
private
- def set_user
- @user = User.find_by_username!(params[:username])
+ def authorize_read_user!
+ render_404 unless can?(current_user, :read_user, user)
+ end
+
+ def user
+ @user ||= User.find_by_username!(params[:username])
end
def contributed_projects
- ContributedProjectsFinder.new(@user).execute(current_user)
+ ContributedProjectsFinder.new(user).execute(current_user)
end
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.
- new(contributed_projects, @user)
+ new(contributed_projects, user)
end
def load_events
# Get user activity feed for projects common for both users
- @events = @user.recent_events.
+ @events = user.recent_events.
merge(projects_for_current_user).
references(:project).
with_associations.
@@ -99,16 +104,16 @@ class UsersController < ApplicationController
def load_projects
@projects =
- PersonalProjectsFinder.new(@user).execute(current_user)
+ PersonalProjectsFinder.new(user).execute(current_user)
.page(params[:page])
end
def load_contributed_projects
- @contributed_projects = contributed_projects.joined(@user)
+ @contributed_projects = contributed_projects.joined(user)
end
def load_groups
- @groups = JoinedGroupsFinder.new(@user).execute(current_user)
+ @groups = JoinedGroupsFinder.new(user).execute(current_user)
end
def projects_for_current_user
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f1df6832bf6..f00f3f709e9 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -39,6 +39,7 @@ class IssuableFinder
items = by_assignee(items)
items = by_author(items)
items = by_label(items)
+ items = by_due_date(items)
sort(items)
end
@@ -117,7 +118,7 @@ class IssuableFinder
end
def filter_by_no_label?
- labels? && params[:label_name] == Label::None.title
+ labels? && params[:label_name].include?(Label::None.title)
end
def labels
@@ -271,7 +272,6 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names)
-
if projects
items = items.where(labels: { project_id: projects })
end
@@ -281,8 +281,44 @@ class IssuableFinder
items
end
+ def by_due_date(items)
+ if due_date?
+ if filter_by_no_due_date?
+ items = items.without_due_date
+ elsif filter_by_overdue?
+ items = items.due_before(Date.today)
+ elsif filter_by_due_this_week?
+ items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
+ elsif filter_by_due_this_month?
+ items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
+ end
+ end
+
+ items
+ end
+
+ def filter_by_no_due_date?
+ due_date? && params[:due_date] == Issue::NoDueDate.name
+ end
+
+ def filter_by_overdue?
+ due_date? && params[:due_date] == Issue::Overdue.name
+ end
+
+ def filter_by_due_this_week?
+ due_date? && params[:due_date] == Issue::DueThisWeek.name
+ end
+
+ def filter_by_due_this_month?
+ due_date? && params[:due_date] == Issue::DueThisMonth.name
+ end
+
+ def due_date?
+ params[:due_date].present? && klass.column_names.include?('due_date')
+ end
+
def label_names
- params[:label_name].split(',')
+ params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end
def current_user_related?
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index a41172816b8..01cbf91c658 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -51,7 +51,7 @@ class SnippetsFinder
snippets = project.snippets.fresh
if current_user
- if project.team.member?(current_user.id)
+ if project.team.member?(current_user.id) || current_user.admin?
snippets
else
snippets.public_and_internal
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 16e5b8ac223..3e0074da394 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -254,11 +254,11 @@ module ApplicationHelper
def page_filter_path(options = {})
without = options.delete(:without)
+ add_label = options.delete(:label)
exist_opts = {
state: params[:state],
scope: params[:scope],
- label_name: params[:label_name],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
@@ -275,6 +275,13 @@ module ApplicationHelper
path = request.path
path << "?#{options.to_param}"
+ if add_label
+ if params[:label_name].present? and params[:label_name].respond_to?('any?')
+ params[:label_name].each do |label|
+ path << "&label_name[]=#{label}"
+ end
+ end
+ end
path
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 60a0ff32c9c..914b0ef6042 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -15,6 +15,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text
end
+ def shared_runners_text
+ current_application_settings.shared_runners_text
+ end
+
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 9e59a295fc4..93241b3afb7 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -3,8 +3,8 @@ module BlobHelper
Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
end
- def highlight(blob_name, blob_content, nowrap: false)
- Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap)
+ def highlight(blob_name, blob_content, nowrap: false, plain: false)
+ Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap, plain: plain)
end
def no_highlight_files
@@ -131,7 +131,7 @@ module BlobHelper
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
def sanitize_svg(blob)
- blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
+ blob.data = Gitlab::Sanitizers::SVG.clean(blob.data)
blob
end
@@ -173,4 +173,15 @@ module BlobHelper
response.etag = @blob.id
!stale
end
+
+ def licenses_for_select
+ return @licenses_for_select if defined?(@licenses_for_select)
+
+ licenses = Licensee::License.all
+
+ @licenses_for_select = {
+ Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
+ Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
+ }
+ end
end
diff --git a/app/helpers/ci_badge_helper.rb b/app/helpers/ci_badge_helper.rb
deleted file mode 100644
index 27386133e36..00000000000
--- a/app/helpers/ci_badge_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module CiBadgeHelper
- def markdown_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- link = namespace_project_commits_path(project.namespace, project, ref)
- "[![build status](#{url})](#{link})"
- end
-
- def html_badge_code(project, ref)
- url = status_ci_project_url(project, ref: ref, format: 'png')
- link = namespace_project_commits_path(project.namespace, project, ref)
- "<a href='#{link}'><img src='#{url}' /></a>"
- end
-end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8b1575d5e0c..417050b4132 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
- def ci_status_icon(ci_commit)
- ci_icon_for_status(ci_commit.status)
- end
-
- def ci_status_label(ci_commit)
- ci_label_for_status(ci_commit.status)
- end
-
def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}"
@@ -47,10 +39,13 @@ module CiStatusHelper
end
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
- link_to ci_status_icon(ci_commit),
+ # TODO: split this method into
+ # - render_commit_status
+ # - render_pipeline_status
+ link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
- title: "Build #{ci_status_label(ci_commit)}",
+ title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 35ba543cef1..b59c3982edd 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -126,12 +126,10 @@ module CommitsHelper
def revert_commit_link(commit, continue_to_path, btn_class: nil)
return unless current_user
- tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request"
+ tooltip = "Revert this #{commit.change_type_title} in a new merge request"
if can_collaborate_with_project?
- content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do
- link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
- end
+ link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
@@ -146,11 +144,24 @@ module CommitsHelper
end
end
- def revert_commit_type(commit)
- if commit.merged_merge_request
- 'merge request'
- else
- 'commit'
+ def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil)
+ return unless current_user
+
+ tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
+
+ if can_collaborate_with_project?
+ link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
+ elsif can?(current_user, :fork_project, @project)
+ continue_params = {
+ to: continue_to_path,
+ notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.',
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = namespace_project_forks_path(@project.namespace, @project,
+ namespace_key: current_user.namespace.id,
+ continue: continue_params)
+
+ link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
end
end
@@ -183,7 +194,7 @@ module CommitsHelper
options = {
class: "commit-#{options[:source]}-link has-tooltip",
- data: { 'original-title'.to_sym => sanitize(source_email) }
+ title: source_email
}
if user.nil?
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 6a3ec83b8c0..9f73edb4553 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -9,7 +9,13 @@ module DiffHelper
end
def diff_view
- params[:view] == 'parallel' ? 'parallel' : 'inline'
+ diff_views = %w(inline parallel)
+
+ if diff_views.include?(cookies[:diff_view])
+ cookies[:diff_view]
+ else
+ diff_views.first
+ end
end
def diff_hard_limit_enabled?
@@ -17,7 +23,7 @@ module DiffHelper
end
def diff_options
- options = { ignore_whitespace_change: params[:w] == '1' }
+ options = { ignore_whitespace_change: hide_whitespace? }
if diff_hard_limit_enabled?
options.merge!(Commit.max_diff_options)
end
@@ -122,4 +128,31 @@ module DiffHelper
title
end
end
+
+ def commit_diff_whitespace_link(project, commit, options)
+ url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+ toggle_whitespace_link(url, options)
+ end
+
+ def diff_merge_request_whitespace_link(project, merge_request, options)
+ url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+ toggle_whitespace_link(url, options)
+ end
+
+ private
+
+ def hide_whitespace?
+ params[:w] == '1'
+ end
+
+ def params_with_whitespace
+ hide_whitespace? ? request.query_parameters.except(:w) : request.query_parameters.merge(w: 1)
+ end
+
+ def toggle_whitespace_link(url, options)
+ options[:class] ||= ''
+ options[:class] << ' btn btn-default'
+
+ link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]
+ end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f3fddef01cb..f07eff3fb57 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
+ def project_pipelines_path(project, *args)
+ namespace_project_pipelines_path(project.namespace, project, *args)
+ end
+
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
new file mode 100644
index 00000000000..109bc1a02d1
--- /dev/null
+++ b/app/helpers/import_helper.rb
@@ -0,0 +1,18 @@
+module ImportHelper
+ def github_project_link(path_with_namespace)
+ link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
+ end
+
+ private
+
+ def github_project_url(path_with_namespace)
+ "#{github_root_url}/#{path_with_namespace}"
+ end
+
+ def github_root_url
+ return @github_url if defined?(@github_url)
+
+ provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
+ @github_url = provider.fetch('url', 'https://github.com') if provider
+ end
+end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b14b8218d02..39474217286 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -16,6 +16,25 @@ module IssuablesHelper
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
+ def multi_label_name(current_labels, default_label)
+ # current_labels may be a string from before
+ if current_labels.is_a?(Array)
+ if current_labels.count > 1
+ "#{current_labels[0]} +#{current_labels.count - 1} more"
+ else
+ current_labels[0]
+ end
+ elsif current_labels.is_a?(String)
+ if current_labels.nil? || current_labels.empty?
+ default_label
+ else
+ current_labels
+ end
+ else
+ default_label
+ end
+ end
+
def issuable_json_path(issuable)
project = issuable.project
@@ -55,6 +74,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label)
end
+ def issuable_meta(issuable, project, text)
+ output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
+ output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
+ output << content_tag(:strong) do
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
+ author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index ac6c6fb25bb..0ea712c8266 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,31 +16,49 @@ module IssuesHelper
def url_for_project_issues(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.project_path
- else
- project.issues_tracker.project_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.project_path
+ else
+ project.issues_tracker.project_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_new_issue(project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.new_issue_path
- else
- project.issues_tracker.new_issue_url
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.new_issue_path
+ else
+ project.issues_tracker.new_issue_url
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
- else
- project.issues_tracker.issue_url(issue_iid)
- end
+ url =
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+
+ # Ensure we return a valid URL to prevent possible XSS.
+ URI.parse(url).to_s
+ rescue URI::InvalidURIError
+ ''
end
def bulk_update_milestone_options
@@ -170,6 +188,18 @@ module IssuesHelper
end.to_h
end
+ def due_date_options
+ options = [
+ Issue::AnyDueDate,
+ Issue::NoDueDate,
+ Issue::DueThisWeek,
+ Issue::DueThisMonth,
+ Issue::Overdue
+ ]
+
+ options_from_collection_for_select(options, 'name', 'title', params[:due_date])
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 3dded7c2f23..c99b137cdaa 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -37,7 +37,7 @@ module LabelsHelper
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
- label_name: label.name)
+ label_name: [label.name])
if block_given?
link_to link, &block
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 5d86bd490a8..3aa41030453 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -34,10 +34,13 @@ module NavHelper
end
def nav_header_class
- if nav_menu_collapsed?
- "header-collapsed"
- else
- "header-expanded"
- end
+ class_name =
+ if nav_menu_collapsed?
+ "header-collapsed"
+ else
+ "header-expanded"
+ end
+ class_name += " with-horizontal-nav" if defined?(nav) && nav
+ class_name
end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 82f805fa444..e4e8b934bc8 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
+ def nav(name = nil)
+ if name
+ @nav = name
+ else
+ @nav
+ end
+ end
+
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7e00aacceaa..85f8854d2eb 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -52,7 +52,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
end
end
@@ -123,6 +123,18 @@ module ProjectsHelper
end
end
+ def license_short_name(project)
+ no_license_key = project.repository.license_key.nil? ||
+ # Back-compat if cache contains 'no-license', can be removed in a few weeks
+ project.repository.license_key == 'no-license'
+
+ return 'LICENSE' if no_license_key
+
+ license = Licensee::License.new(project.repository.license_key)
+
+ license.nickname || license.name
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -144,6 +156,10 @@ module ProjectsHelper
nav_tabs << :settings
end
+ if can?(current_user, :read_project_member, project)
+ nav_tabs << :team
+ end
+
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
@@ -184,16 +200,13 @@ module ProjectsHelper
end
def repository_size(project = @project)
- "#{project.repository_size} MB"
- rescue
- # In order to prevent 500 error
- # when application cannot allocate memory
- # to calculate repo size - just show 'Unknown'
- 'unknown'
+ size_in_bytes = project.repository_size * 1.megabyte
+ number_to_human_size(size_in_bytes, delimiter: ',', precision: 2)
end
def default_url_to_repo(project = @project)
- if default_clone_protocol == "ssh"
+ case default_clone_protocol
+ when 'ssh'
project.ssh_url_to_repo
else
project.http_url_to_repo
@@ -216,40 +229,14 @@ module ProjectsHelper
end
end
- def add_contribution_guide_path(project)
- if project && !project.repository.contribution_guide
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CONTRIBUTING.md",
- commit_message: "Add contribution guide"
- )
- end
- end
-
- def add_changelog_path(project)
- if project && !project.repository.changelog
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "CHANGELOG",
- commit_message: "Add changelog"
- )
- end
- end
-
- def add_license_path(project)
- if project && !project.repository.license
- namespace_project_new_blob_path(
- project.namespace,
- project,
- project.default_branch,
- file_name: "LICENSE",
- commit_message: "Add license"
- )
- end
+ def add_special_file_path(project, file_name:, commit_message: nil)
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ project.default_branch || 'master',
+ file_name: file_name,
+ commit_message: commit_message || "Add #{file_name.downcase}"
+ )
end
def contribution_guide_path(project)
@@ -272,7 +259,7 @@ module ProjectsHelper
end
def license_path(project)
- filename_path(project, :license)
+ filename_path(project, :license_blob)
end
def version_path(project)
@@ -306,6 +293,13 @@ module ProjectsHelper
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
end
+ def new_license_path
+ ref = @repository.root_ref if @repository
+ ref ||= 'master'
+
+ namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
+ end
+
def last_push_event
if current_user
current_user.recent_push(@project.id)
@@ -335,8 +329,6 @@ module ProjectsHelper
@ref || @repository.try(:root_ref)
end
- private
-
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
@@ -346,4 +338,10 @@ module ProjectsHelper
)
end
end
+
+ def sanitize_repo_path(message)
+ return '' unless message.present?
+
+ message.strip.gsub(Gitlab.config.gitlab_shell.repos_path.chomp('/'), "[REPOS PATH]")
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8a97a74ad73..24c4c098c65 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -19,6 +19,16 @@ module SearchHelper
end
end
+ def search_entries_info(collection, scope, term)
+ return unless collection.count > 0
+
+ from = collection.offset_value + 1
+ to = collection.offset_value + collection.length
+ count = collection.total_count
+
+ "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
+ end
+
private
# Autocomplete results for various settings pages
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 4fc6de59a8b..e951a87a212 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -2,32 +2,29 @@ module SelectsHelper
def users_select_tag(id, opts = {})
css_class = "ajax-users-select "
css_class << "multiselect " if opts[:multiple]
+ css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
- placeholder = opts[:placeholder] || 'Search for a user'
- null_user = opts[:null_user] || false
- any_user = opts[:any_user] || false
- email_user = opts[:email_user] || false
- first_user = opts[:first_user] && current_user ? current_user.username : false
- current_user = opts[:current_user] || false
- author_id = opts[:author_id] || ''
- project = opts[:project] || @project
+ first_user = opts[:first_user] && current_user ? current_user.username : false
html = {
class: css_class,
data: {
- placeholder: placeholder,
- null_user: null_user,
- any_user: any_user,
- email_user: email_user,
+ placeholder: opts[:placeholder] || 'Search for a user',
+ null_user: opts[:null_user] || false,
+ any_user: opts[:any_user] || false,
+ email_user: opts[:email_user] || false,
first_user: first_user,
- current_user: current_user,
- author_id: author_id
+ current_user: opts[:current_user] || false,
+ "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+ author_id: opts[:author_id] || ''
}
}
unless opts[:scope] == :all
+ project = opts[:project] || @project
+
if project
html['data-project-id'] = project.id
elsif @group
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 2f2d2721d6d..630e10ea892 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -8,6 +8,8 @@ module SortingHelper
sort_value_oldest_created => sort_title_oldest_created,
sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_due_date_later => sort_title_due_date_later,
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
@@ -50,6 +52,14 @@ module SortingHelper
'Milestone due later'
end
+ def sort_title_due_date_soon
+ 'Due soon'
+ end
+
+ def sort_title_due_date_later
+ 'Due later'
+ end
+
def sort_title_name
'Name'
end
@@ -98,6 +108,14 @@ module SortingHelper
'milestone_due_desc'
end
+ def sort_value_due_date_soon
+ 'due_date_asc'
+ end
+
+ def sort_value_due_date_later
+ 'due_date_desc'
+ end
+
def sort_value_name
'name_asc'
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 04e53fe7c61..96a83671009 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
+
+ def profile_tab_class
+ if controller.controller_path =~ /\Aprofiles/
+ return 'active'
+ end
+
+ 'active' if current_controller?('oauth/applications')
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 4920ca5af6e..dbedf417fa5 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -66,7 +66,7 @@ module TreeHelper
ref
else
project = tree_edit_project(project)
- project.repository.next_patch_branch
+ project.repository.next_branch('patch')
end
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 55bb4f65270..9dd11d20ea6 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -56,7 +56,7 @@ module Emails
{
from: sender(sender_id),
to: recipient(recipient_id),
- subject: subject("#{@merge_request.title} (##{@merge_request.iid})")
+ subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})")
}
end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index f9650df9a74..96116e916dd 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -28,6 +28,14 @@ module Emails
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
+ def note_snippet_email(recipient_id, note_id)
+ setup_note_mail(note_id, recipient_id)
+
+ @snippet = @note.noteable
+ @target_url = namespace_project_snippet_url(*note_target_url_options)
+ mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ end
+
private
def note_target_url_options
@@ -38,7 +46,7 @@ module Emails
{
from: sender(@note.author_id),
to: recipient(recipient_id),
- subject: subject("#{@note.noteable.title} (##{@note.noteable.iid})")
+ subject: subject("#{@note.noteable.title} (#{@note.noteable.to_reference})")
}
end
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 2bff5b63cc4..21db2fe04a0 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer
mail(
to: User.admins.pluck(:email),
- subject: @message
+ subject: "GitLab Admin | #{@message}"
)
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index c0bf6def7c5..6103a2947e2 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -18,6 +18,7 @@ class Ability
when Namespace then namespace_abilities(user, subject)
when GroupMember then group_member_abilities(user, subject)
when ProjectMember then project_member_abilities(user, subject)
+ when User then user_abilities
else []
end.concat(global_abilities(user))
end
@@ -35,6 +36,8 @@ class Ability
anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group)
anonymous_group_abilities(subject)
+ when subject.is_a?(User)
+ anonymous_user_abilities
else
[]
end
@@ -81,17 +84,17 @@ class Ability
end
def anonymous_group_abilities(subject)
+ rules = []
+
group = if subject.is_a?(Group)
subject
else
subject.group
end
- if group && group.public?
- [:read_group]
- else
- []
- end
+ rules << :read_group if group.public?
+
+ rules
end
def anonymous_personal_snippet_abilities(snippet)
@@ -110,9 +113,14 @@ class Ability
end
end
+ def anonymous_user_abilities
+ [:read_user] unless restricted_public_level?
+ end
+
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
+ rules << :read_users_list
rules
end
@@ -163,7 +171,7 @@ class Ability
@public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project,
- :read_commit_status,
+ :read_commit_status
]
end
@@ -284,7 +292,6 @@ class Ability
def group_abilities(user, group)
rules = []
-
rules << :read_group if can_read_group?(user, group)
# Only group masters and group owners can create new projects
@@ -456,6 +463,10 @@ class Ability
rules
end
+ def user_abilities
+ [:read_user]
+ end
+
def abilities
@abilities ||= begin
abilities = Six.new
@@ -470,6 +481,10 @@ class Ability
private
+ def restricted_public_level?
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+ end
+
def named_abilities(name)
[
:"read_#{name}",
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index b61f5123127..b01a244032d 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class AbuseReport < ActiveRecord::Base
belongs_to :reporter, class_name: 'User'
belongs_to :user
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 36f88154232..7039db2d41e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -1,51 +1,3 @@
-# == Schema Information
-#
-# Table name: application_settings
-#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# default_group_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string
-# require_two_factor_authentication :boolean default(FALSE)
-# two_factor_grace_period :integer default(48)
-# metrics_enabled :boolean default(FALSE)
-# metrics_host :string default("localhost")
-# metrics_username :string
-# metrics_password :string
-# metrics_pool_size :integer default(16)
-# metrics_timeout :integer default(10)
-# metrics_method_call_threshold :integer default(10)
-# recaptcha_enabled :boolean default(FALSE)
-# recaptcha_site_key :string
-# recaptcha_private_key :string
-# metrics_port :integer default(8089)
-# sentry_enabled :boolean default(FALSE)
-# sentry_dsn :string
-# email_author_in_body :boolean default(FALSE)
-#
-
class ApplicationSetting < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :runners_registration_token
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 0ed0dd98a59..967ffd46db0 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: audit_events
-#
-# id :integer not null, primary key
-# author_id :integer not null
-# type :string(255) not null
-# entity_id :integer not null
-# entity_type :string(255) not null
-# details :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 72e6c5fa3fd..0fea6b7f576 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -19,6 +19,14 @@ class Blob < SimpleDelegator
new(blob)
end
+ def no_highlighting?
+ size && size > 1.megabyte
+ end
+
+ def only_display_raw?
+ size && size > 5.megabytes
+ end
+
def svg?
text? && language && language.name == 'SVG'
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 8a0a8a4c2a9..61498140f27 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string(255)
-# font :string(255)
-#
-
class BroadcastMessage < ActiveRecord::Base
include Sortable
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7d33838044b..c9506dd3057 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1,44 +1,5 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-# artifacts_metadata :text
-# erased_by_id :integer
-# erased_at :datetime
-#
-
module Ci
class Build < CommitStatus
- LAZY_ATTRIBUTES = ['trace']
-
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User'
@@ -50,25 +11,17 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
acts_as_taggable
- # To prevent db load megabytes of data from trace
- default_scope -> { select(Ci::Build.columns_without_lazy) }
-
before_destroy { project }
- class << self
- def columns_without_lazy
- (column_names - LAZY_ATTRIBUTES).map do |column_name|
- "#{table_name}.#{column_name}"
- end
- end
+ after_create :execute_hooks
+ class << self
def last_month
where('created_at > ?', Date.today - 1.month)
end
@@ -126,12 +79,16 @@ module Ci
end
def retried?
- !self.commit.latest_statuses_for_ref(self.ref).include?(self)
+ !self.commit.statuses.latest.include?(self)
+ end
+
+ def retry
+ Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
- latest_builds = self.commit.builds.similar(self).latest
+ latest_builds = self.commit.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
@@ -230,12 +187,33 @@ module Ci
end
end
+ def trace_length
+ if raw_trace
+ raw_trace.length
+ else
+ 0
+ end
+ end
+
def trace=(trace)
+ recreate_trace_dir
+ File.write(path_to_trace, trace)
+ end
+
+ def recreate_trace_dir
unless Dir.exists?(dir_to_trace)
FileUtils.mkdir_p(dir_to_trace)
end
+ end
+ private :recreate_trace_dir
- File.write(path_to_trace, trace)
+ def append_trace(trace_part, offset)
+ recreate_trace_dir
+
+ File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
+ File.open(path_to_trace, 'a') do |f|
+ f.write(trace_part)
+ end
end
def dir_to_trace
@@ -365,11 +343,23 @@ module Ci
self.update(erased_by: user, erased_at: Time.now)
end
- private
-
def yaml_variables
+ global_yaml_variables + job_yaml_variables
+ end
+
+ def global_yaml_variables
+ if commit.config_processor
+ commit.config_processor.global_variables.map do |key, value|
+ { key: key, value: value, public: true }
+ end
+ else
+ []
+ end
+ end
+
+ def job_yaml_variables
if commit.config_processor
- commit.config_processor.variables.map do |key, value|
+ commit.config_processor.job_variables(name).map do |key, value|
{ key: key, value: value, public: true }
end
else
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f4cf7034b14..f4b61c75ab6 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -1,39 +1,28 @@
-# == Schema Information
-#
-# Table name: ci_commits
-#
-# id :integer not null, primary key
-# project_id :integer
-# ref :string(255)
-# sha :string(255)
-# before_sha :string(255)
-# push_data :text
-# created_at :datetime
-# updated_at :datetime
-# tag :boolean default(FALSE)
-# yaml_errors :text
-# committed_at :datetime
-# gl_project_id :integer
-#
-
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
+ include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+ delegate :stages, to: :statuses
+
validates_presence_of :sha
+ validates_presence_of :status
validate :valid_commit_sha
+ # Invalidate object and save if when touched
+ after_touch :update_state
+
def self.truncate_sha(sha)
sha[0...8]
end
- def to_param
- sha
+ def self.stages
+ CommitStatus.where(commit: all).stages
end
def project_id
@@ -68,15 +57,20 @@ module Ci
nil
end
- def stage
- running_or_pending = statuses.latest.running_or_pending.ordered
- running_or_pending.first.try(:stage)
+ def branch?
+ !tag?
end
- def create_builds(ref, tag, user, trigger_request = nil)
+ def retryable?
+ builds.latest.any? do |build|
+ build.failed? && build.retryable?
+ end
+ end
+
+ def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
+ CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
end
end
@@ -84,7 +78,7 @@ module Ci
return unless config_processor
# don't create other builds if this one is retried
- latest_builds = builds.similar(build).latest
+ latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
@@ -92,88 +86,21 @@ module Ci
next_stages.delete(build.stage)
# get status for all prior builds
- prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
- status = Ci::Status.get_status(prior_builds)
+ prior_builds = latest_builds.where.not(stage: next_stages)
+ prior_status = prior_builds.status
# create builds for next stages based
next_stages.any? do |stage|
- CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
+ CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
end
end
- def refs
- statuses.order(:ref).pluck(:ref).uniq
- end
-
- def latest_statuses
- @latest_statuses ||= statuses.latest.to_a
- end
-
- def latest_statuses_for_ref(ref)
- latest_statuses.select { |status| status.ref == ref }
- end
-
- def matrix_builds(build = nil)
- matrix_builds = builds.latest.ordered
- matrix_builds = matrix_builds.similar(build) if build
- matrix_builds.to_a
- end
-
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
- def status
- if yaml_errors.present?
- return 'failed'
- end
-
- @status ||= Ci::Status.get_status(latest_statuses)
- end
-
- def pending?
- status == 'pending'
- end
-
- def running?
- status == 'running'
- end
-
- def success?
- status == 'success'
- end
-
- def failed?
- status == 'failed'
- end
-
- def canceled?
- status == 'canceled'
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def duration
- duration_array = statuses.map(&:duration).compact
- duration_array.reduce(:+).to_i
- end
-
- def started_at
- @started_at ||= statuses.order('started_at ASC').first.try(:started_at)
- end
-
- def finished_at
- @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
- end
-
def coverage
- coverage_array = latest_statuses.map(&:coverage).compact
+ coverage_array = statuses.latest.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
@@ -181,23 +108,29 @@ module Ci
def config_processor
return nil unless ci_yaml_file
- @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
- rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
- save_yaml_error(e.message)
- nil
- rescue
- save_yaml_error("Undefined error")
- nil
+ return @config_processor if defined?(@config_processor)
+
+ @config_processor ||= begin
+ Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
+ save_yaml_error(e.message)
+ nil
+ rescue
+ save_yaml_error("Undefined error")
+ nil
+ end
end
def ci_yaml_file
+ return @ci_yaml_file if defined?(@ci_yaml_file)
+
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
+ rescue
+ nil
end
- rescue
- nil
end
def skip_ci?
@@ -206,10 +139,23 @@ module Ci
private
+ def update_state
+ statuses.reload
+ self.status = if yaml_errors.blank?
+ statuses.latest.status || 'skipped'
+ else
+ 'failed'
+ end
+ self.started_at = statuses.started_at
+ self.finished_at = statuses.finished_at
+ self.duration = statuses.latest.duration
+ save
+ end
+
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
- save
+ update_state
end
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 90349a07594..0cdb64d6c33 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_runners
-#
-# id :integer not null, primary key
-# token :string(255)
-# created_at :datetime
-# updated_at :datetime
-# description :string(255)
-# contacted_at :datetime
-# active :boolean default(TRUE), not null
-# is_shared :boolean default(FALSE)
-# name :string(255)
-# version :string(255)
-# revision :string(255)
-# platform :string(255)
-# architecture :string(255)
-#
-
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 7b16f207a26..4b44ffa886e 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_runner_projects
-#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
module Ci
class RunnerProject < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 2b9a457c8ab..a0b19b51a12 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_triggers
-#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
module Ci
class Trigger < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 9973d2e5ade..872d5fb31de 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_trigger_requests
-#
-# id :integer not null, primary key
-# trigger_id :integer not null
-# variables :text
-# created_at :datetime
-# updated_at :datetime
-# commit_id :integer
-#
-
module Ci
class TriggerRequest < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index e786bd7dd93..10802f64813 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-# id :integer not null, primary key
-# project_id :integer
-# key :string(255)
-# value :text
-# encrypted_value :text
-# encrypted_value_salt :string(255)
-# encrypted_value_iv :string(255)
-# gl_project_id :integer
-#
-
module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d1f07ccd55c..562c3ed15b2 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -15,8 +15,8 @@ class Commit
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Commits above this size will not be rendered in HTML
- DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
- DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
+ DIFF_HARD_LIMIT_FILES = 1000
+ DIFF_HARD_LIMIT_LINES = 50000
class << self
def decorate(commits, project)
@@ -207,17 +207,22 @@ class Commit
@raw.short_id(7)
end
- def ci_commit
- project.ci_commit(sha)
+ def ci_commits
+ @ci_commits ||= project.ci_commits.where(sha: sha)
end
def status
- ci_commit.try(:status) || :not_found
+ return @status if defined?(@status)
+ @status ||= ci_commits.status
end
def revert_branch_name
"revert-#{short_id}"
end
+
+ def cherry_pick_branch_name
+ project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
+ end
def revert_description
if merged_merge_request
@@ -253,6 +258,10 @@ class Commit
end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
end
+ def change_type_title
+ merged_merge_request ? 'merge request' : 'commit'
+ end
+
private
def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 3377a85a55a..cacbc13b391 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,62 +1,21 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
class CommitStatus < ActiveRecord::Base
+ include Statuseable
+
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :commit, class_name: 'Ci::Commit', touch: true
belongs_to :user
validates :commit, presence: true
- validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name
alias_attribute :author, :user
- scope :running, -> { where(status: 'running') }
- scope :pending, -> { where(status: 'pending') }
- scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :running_or_pending, -> { where(status: [:running, :pending]) }
- scope :finished, -> { where(status: [:success, :failed, :canceled]) }
- scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
+ scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
- scope :for_ref, ->(ref) { where(ref: ref) }
-
- AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
+ scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :run do
@@ -86,31 +45,24 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, to: :commit, prefix: false
+ delegate :sha, :short_sha, to: :commit
- # TODO: this should be removed with all references
def before_sha
- Gitlab::Git::BLANK_SHA
+ commit.before_sha || Gitlab::Git::BLANK_SHA
end
- def started?
- !pending? && !canceled? && started_at
+ def self.stages
+ order_by = 'max(stage_idx)'
+ group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
end
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
+ def self.stages_status
+ all.stages.inject({}) do |h, stage|
+ h[stage] = all.where(stage: stage).status
+ h
+ end
end
def ignored?
@@ -118,11 +70,13 @@ class CommitStatus < ActiveRecord::Base
end
def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
+ duration =
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ duration
end
def stuck?
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 51288094ef1..5382dde6765 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -7,11 +7,13 @@ module InternalId
end
def set_iid
- records = project.send(self.class.name.tableize)
- records = records.with_deleted if self.paranoid?
- max_iid = records.maximum(:iid)
+ if iid.blank?
+ records = project.send(self.class.name.tableize)
+ records = records.with_deleted if self.paranoid?
+ max_iid = records.maximum(:iid)
- self.iid = max_iid.to_i + 1
+ self.iid = max_iid.to_i + 1
+ end
end
def to_param
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 6af76c97cd3..9b77b88ca80 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -36,14 +36,14 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
- scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
- scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
- scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
+ scope :order_milestone_due_desc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date DESC, milestones.id DESC') }
+ scope :order_milestone_due_asc, -> { outer_join_milestone.reorder('milestones.due_date IS NULL ASC, milestones.due_date ASC, milestones.id ASC') }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
+ scope :outer_join_milestone, -> { joins("LEFT OUTER JOIN milestones ON milestones.id = #{table_name}.milestone_id") }
delegate :name,
:email,
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 98f71ae8cb0..b381d225485 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -43,8 +43,8 @@ module Mentionable
self
end
- def all_references(current_user = self.author, text = nil)
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author)
+ def all_references(current_user = nil, text = nil)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user || self.author, self.author)
if text
ext.analyze(text)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 5b8e3f654ea..7bcc78247ba 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -8,7 +8,7 @@ module Milestoneish
end
def complete?(user = nil)
- total_items_count(user) == closed_items_count(user)
+ total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
def percent_complete(user = nil)
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
new file mode 100644
index 00000000000..3ef91caad47
--- /dev/null
+++ b/app/models/concerns/statuseable.rb
@@ -0,0 +1,81 @@
+module Statuseable
+ extend ActiveSupport::Concern
+
+ AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
+
+ class_methods do
+ def status_sql
+ builds = all.select('count(*)').to_sql
+ success = all.success.select('count(*)').to_sql
+ ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
+ ignored ||= '0'
+ pending = all.pending.select('count(*)').to_sql
+ running = all.running.select('count(*)').to_sql
+ canceled = all.canceled.select('count(*)').to_sql
+ skipped = all.skipped.select('count(*)').to_sql
+
+ deduce_status = "(CASE
+ WHEN (#{builds})=0 THEN NULL
+ WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
+ WHEN (#{builds})=(#{pending}) THEN 'pending'
+ WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{running})+(#{pending})>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+
+ deduce_status
+ end
+
+ def status
+ all.pluck(self.status_sql).first
+ end
+
+ def duration
+ duration_array = all.map(&:duration).compact
+ duration_array.reduce(:+)
+ end
+
+ def started_at
+ all.minimum(:started_at)
+ end
+
+ def finished_at
+ all.maximum(:finished_at)
+ end
+ end
+
+ included do
+ validates :status, inclusion: { in: AVAILABLE_STATUSES }
+
+ state_machine :status, initial: :pending do
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ state :skipped, value: 'skipped'
+ end
+
+ scope :running, -> { where(status: 'running') }
+ scope :pending, -> { where(status: 'pending') }
+ scope :success, -> { where(status: 'success') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :canceled, -> { where(status: 'canceled') }
+ scope :skipped, -> { where(status: 'skipped') }
+ scope :running_or_pending, -> { where(status: [:running, :pending]) }
+ scope :finished, -> { where(status: [:success, :failed, :canceled]) }
+ end
+
+ def started?
+ !pending? && !canceled? && started_at
+ end
+
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 9ab663c04ad..2c525d4cd7a 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
class DeployKey < Key
has_many :deploy_keys_projects, dependent: :destroy
has_many :projects, through: :deploy_keys_projects
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 18db521741f..ae8486bd9ac 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: deploy_keys_projects
-#
-# id :integer not null, primary key
-# deploy_key_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class DeployKeysProject < ActiveRecord::Base
belongs_to :project
belongs_to :deploy_key
diff --git a/app/models/email.rb b/app/models/email.rb
index b323d1edd10..32a412ab878 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: emails
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# email :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class Email < ActiveRecord::Base
include Sortable
diff --git a/app/models/event.rb b/app/models/event.rb
index 12183524b79..17ee48b91a8 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: events
-#
-# id :integer not null, primary key
-# target_type :string(255)
-# target_id :integer
-# title :string(255)
-# data :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# action :integer
-# author_id :integer
-#
-
class Event < ActiveRecord::Base
include Sortable
default_scope { where.not(author_id: nil) }
@@ -345,7 +329,7 @@ class Event < ActiveRecord::Base
end
def reset_project_activity
- if project
+ if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain
project.update_column(:last_activity_at, self.created_at)
end
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b8585d4e577..b7894c99846 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil)
id
end
+
+ def reference_link_text(from_project = nil)
+ return "##{id}" if /^\d+$/.match(id)
+
+ id
+ end
end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index 9b0c6263a96..9803bae0bee 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class ForkedProjectLink < ActiveRecord::Base
belongs_to :forked_to_project, class_name: Project
belongs_to :forked_from_project, class_name: Project
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 97f4f03a9a5..fa54e3540d0 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -1,37 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
class GenericCommitStatus < CommitStatus
before_validation :set_default_values
diff --git a/app/models/group.rb b/app/models/group.rb
index 9a04ac70d35..aec92e335e6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -1,21 +1,4 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# visibility_level :integer default(20), not null
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index fe923fafbe0..ba42a8eeb70 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,30 +1,9 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# note_events :boolean default(FALSE), not null
-# enable_ssl_verification :boolean default(TRUE)
-# build_events :boolean default(FALSE), not null
-#
-
class ProjectHook < WebHook
belongs_to :project
- 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) }
scope :build_hooks, -> { where(build_events: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 80962264ba2..eef24052a06 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# note_events :boolean default(FALSE), not null
-# enable_ssl_verification :boolean default(TRUE)
-# build_events :boolean default(FALSE), not null
-#
-
class ServiceHook < WebHook
belongs_to :service
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index c147d8762a9..777bad1e724 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,22 +1,5 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# note_events :boolean default(FALSE), not null
-# enable_ssl_verification :boolean default(TRUE)
-# build_events :boolean default(FALSE), not null
-#
-
class SystemHook < WebHook
+ def async_execute(data, hook_name)
+ Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
+ end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7a13c3f0a39..fde05f729dc 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: web_hooks
-#
-# id :integer not null, primary key
-# url :string(2000)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string default("ProjectHook")
-# service_id :integer
-# push_events :boolean default(TRUE), not null
-# issues_events :boolean default(FALSE), not null
-# merge_requests_events :boolean default(FALSE), not null
-# tag_push_events :boolean default(FALSE)
-# note_events :boolean default(FALSE), not null
-# enable_ssl_verification :boolean default(TRUE)
-# build_events :boolean default(FALSE), not null
-#
-
class WebHook < ActiveRecord::Base
include Sortable
include HTTParty
@@ -30,6 +10,9 @@ class WebHook < ActiveRecord::Base
default_value_for :build_events, false
default_value_for :enable_ssl_verification, true
+ scope :push_hooks, -> { where(push_events: true) }
+ scope :tag_push_hooks, -> { where(tag_push_events: true) }
+
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
@@ -40,23 +23,17 @@ class WebHook < ActiveRecord::Base
if parsed_url.userinfo.blank?
response = WebHook.post(url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification)
else
- post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ post_url = url.gsub("#{parsed_url.userinfo}@", '')
auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password),
}
response = WebHook.post(post_url,
body: data.to_json,
- headers: {
- "Content-Type" => "application/json",
- "X-Gitlab-Event" => hook_name.singularize.titleize
- },
+ headers: build_headers(hook_name),
verify: enable_ssl_verification,
basic_auth: auth)
end
@@ -70,4 +47,15 @@ class WebHook < ActiveRecord::Base
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
end
+
+ private
+
+ def build_headers(hook_name)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => hook_name.singularize.titleize
+ }
+ headers['X-Gitlab-Token'] = token if token.present?
+ headers
+ end
end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index e1915b079d4..3bacc450e6e 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: identities
-#
-# id :integer not null, primary key
-# extern_uid :string(255)
-# provider :string(255)
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
class Identity < ActiveRecord::Base
include Sortable
include CaseSensitivity
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 3f188e04770..2d4a9b9f19a 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -1,26 +1,4 @@
-# == Schema Information
-#
-# Table name: issues
-#
-# id :integer not null, primary key
-# title :string(255)
-# assignee_id :integer
-# author_id :integer
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# position :integer default(0)
-# branch_name :string(255)
-# description :text
-# milestone_id :integer
-# state :string(255)
-# iid :integer
-# updated_by_id :integer
-# moved_to_id :integer
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Issue < ActiveRecord::Base
include InternalId
@@ -29,6 +7,13 @@ class Issue < ActiveRecord::Base
include Sortable
include Taskable
+ DueDateStruct = Struct.new(:title, :name).freeze
+ NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
+ AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
+ Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
+ DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
+ DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
+
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
@@ -40,6 +25,13 @@ class Issue < ActiveRecord::Base
scope :open_for, ->(user) { opened.assigned_to(user) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
+ scope :without_due_date, -> { where(due_date: nil) }
+ scope :due_before, ->(date) { where('issues.due_date < ?', date) }
+ scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
+
+ scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
+ scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -83,6 +75,15 @@ class Issue < ActiveRecord::Base
@link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
+ def self.sort(method)
+ case method.to_s
+ when 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ else
+ super
+ end
+ end
+
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -104,10 +105,16 @@ class Issue < ActiveRecord::Base
end
end
- def related_branches
- project.repository.branch_names.select do |branch|
+ # All branches containing the current issue's ID, except for
+ # those with a merge request open referencing the current issue.
+ def related_branches(current_user)
+ branches_with_iid = project.repository.branch_names.select do |branch|
branch =~ /\A#{iid}-(?!\d+-stable)/i
end
+
+ branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
+
+ branches_with_iid - branches_with_merge_request
end
# Reset issue events cache
@@ -151,13 +158,21 @@ class Issue < ActiveRecord::Base
end
def to_branch_name
- "#{iid}-#{title.parameterize}"
+ if self.confidential?
+ "#{iid}-confidential-issue"
+ else
+ "#{iid}-#{title.parameterize}"
+ end
end
def can_be_worked_on?(current_user)
!self.closed? &&
!self.project.forked? &&
- self.related_branches.empty? &&
+ self.related_branches(current_user).empty? &&
self.closed_by_merge_requests(current_user).empty?
end
+
+ def overdue?
+ due_date.try(:past?) || false
+ end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 0282ad18139..d52afda67d1 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
require 'digest/md5'
class Key < ActiveRecord::Base
diff --git a/app/models/label.rb b/app/models/label.rb
index 55c01cae762..e5ad11983be 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: labels
-#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
-# description :string(255)
-#
-
class Label < ActiveRecord::Base
include Referable
include Subscribable
@@ -113,6 +99,14 @@ class Label < ActiveRecord::Base
template
end
+ def text_color
+ LabelsHelper::text_color_for_bg(self.color)
+ end
+
+ def title=(value)
+ write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ end
+
private
def label_format_reference(format = :id)
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index b94c9c777af..47bd6eaf35f 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string(255)
-# created_at :datetime
-# updated_at :datetime
-#
-
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 86b1b7e2f99..18657c3e1c8 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects
-#
-# id :integer not null, primary key
-# oid :string(255) not null
-# size :integer not null
-# created_at :datetime
-# updated_at :datetime
-# file :string(255)
-#
-
class LfsObject < ActiveRecord::Base
has_many :lfs_objects_projects, dependent: :destroy
has_many :projects, through: :lfs_objects_projects
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 890736bfc80..0fd5f089db9 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects_projects
-#
-# id :integer not null, primary key
-# lfs_object_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class LfsObjectsProject < ActiveRecord::Base
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/member.rb b/app/models/member.rb
index 60efafef211..d3060f07fc0 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string(255) not null
-# user_id :integer
-# notification_level :integer not null
-# type :string(255)
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
-# invite_accepted_at :datetime
-#
-
class Member < ActiveRecord::Base
include Sortable
include Gitlab::Access
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 9fb474a1a93..f63a0debf1a 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string(255) not null
-# user_id :integer
-# notification_level :integer not null
-# type :string(255)
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
-# invite_accepted_at :datetime
-#
-
class GroupMember < Member
SOURCE_TYPE = 'Namespace'
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 07ddb02ae9d..8dae3bb2ef2 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string(255) not null
-# user_id :integer
-# notification_level :integer not null
-# type :string(255)
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
-# invite_accepted_at :datetime
-#
-
class ProjectMember < Member
SOURCE_TYPE = 'Project'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index e410febdfff..5460935366c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1,35 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
-# merge_params :text
-# merge_when_build_succeeds :boolean default(FALSE), not null
-# merge_user_id :integer
-# merge_commit_sha :string
-#
-
-require Rails.root.join("app/models/commit")
-require Rails.root.join("lib/static_model")
-
class MergeRequest < ActiveRecord::Base
include InternalId
include Issuable
@@ -589,7 +557,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
- @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
+ @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
@@ -605,4 +573,8 @@ class MergeRequest < ActiveRecord::Base
def can_be_reverted?(current_user = nil)
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end
+
+ def can_be_cherry_picked?
+ merge_commit
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 33884118595..eb42c07b9b9 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_request_diffs
-#
-# id :integer not null, primary key
-# state :string(255)
-# st_commits :text
-# st_diffs :text
-# merge_request_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
-require Rails.root.join("app/models/commit")
-
class MergeRequestDiff < ActiveRecord::Base
include Sortable
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 986184dd301..e4fdd23badb 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: milestones
-#
-# id :integer not null, primary key
-# title :string(255) not null
-# project_id :integer not null
-# description :text
-# due_date :date
-# created_at :datetime
-# updated_at :datetime
-# state :string(255)
-# iid :integer
-#
-
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
@@ -129,6 +114,10 @@ class Milestone < ActiveRecord::Base
nil
end
+ def title=(value)
+ write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ end
+
# Sorts the issues for the given IDs.
#
# This method runs a single SQL query using a CASE statement to update the
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 55842df1e2d..9c942a8f4e3 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
-#
-
class Namespace < ActiveRecord::Base
include Sortable
include Gitlab::ShellAdapter
diff --git a/app/models/note.rb b/app/models/note.rb
index b992b2e76f0..0b038edb47b 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -1,25 +1,4 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string(255)
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Note < ActiveRecord::Base
include Gitlab::CurrentSettings
@@ -345,7 +324,7 @@ class Note < ActiveRecord::Base
end
def create_award_emoji
- self.noteable.award_emoji(award_emoji_name, author)
+ self.noteable.award_emoji(award_emoji_name, author)
end
def clear_blank_line_code!
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
index c78c7f4aa0e..116fb71ac08 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: oauth_access_tokens
-#
-# id :integer not null, primary key
-# resource_owner_id :integer
-# application_id :integer
-# token :string not null
-# refresh_token :string
-# expires_in :integer
-# revoked_at :datetime
-# created_at :datetime not null
-# scopes :string
-#
-
class OauthAccessToken < ActiveRecord::Base
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 452f3913eef..82c1c4de3a0 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -1,18 +1,2 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
class PersonalSnippet < Snippet
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8f20922e3c5..418b85e028a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1,46 +1,4 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# description :text
-# created_at :datetime
-# updated_at :datetime
-# creator_id :integer
-# issues_enabled :boolean default(TRUE), not null
-# wall_enabled :boolean default(TRUE), not null
-# merge_requests_enabled :boolean default(TRUE), not null
-# wiki_enabled :boolean default(TRUE), not null
-# namespace_id :integer
-# issues_tracker :string(255) default("gitlab"), not null
-# issues_tracker_id :string(255)
-# snippets_enabled :boolean default(TRUE), not null
-# last_activity_at :datetime
-# import_url :string(255)
-# visibility_level :integer default(0), not null
-# archived :boolean default(FALSE), not null
-# avatar :string(255)
-# import_status :string(255)
-# repository_size :float default(0.0)
-# star_count :integer default(0), not null
-# import_type :string(255)
-# import_source :string(255)
-# commit_count :integer default(0)
-# import_error :text
-# ci_id :integer
-# builds_enabled :boolean default(TRUE), not null
-# shared_runners_enabled :boolean default(TRUE), not null
-# runners_token :string
-# build_coverage_regex :string
-# build_allow_git_fetch :boolean default(TRUE), not null
-# build_timeout :integer default(3600), not null
-# pending_delete :boolean
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
@@ -63,7 +21,6 @@ class Project < ActiveRecord::Base
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
- default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
@@ -736,19 +693,17 @@ class Project < ActiveRecord::Base
end
def open_branches
- all_branches = repository.branches
+ # We're using a Set here as checking values in a large Set is faster than
+ # checking values in a large Array.
+ protected_set = Set.new(protected_branch_names)
- if protected_branches.present?
- all_branches.reject! do |branch|
- protected_branches_names.include?(branch.name)
- end
+ repository.branches.reject do |branch|
+ protected_set.include?(branch.name)
end
-
- all_branches
end
- def protected_branches_names
- @protected_branches_names ||= protected_branches.map(&:name)
+ def protected_branch_names
+ @protected_branch_names ||= protected_branches.pluck(:name)
end
def root_ref?(branch)
@@ -765,7 +720,7 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
- protected_branches_names.include?(branch_name)
+ protected_branch_names.include?(branch_name)
end
def developers_can_push_to_protected_branch?(branch_name)
@@ -821,18 +776,16 @@ class Project < ActiveRecord::Base
wiki = Repository.new("#{old_path}.wiki", self)
if repo.exists?
- repo.expire_cache
- repo.expire_emptiness_caches
+ repo.before_delete
end
if wiki.exists?
- wiki.expire_cache
- wiki.expire_emptiness_caches
+ wiki.before_delete
end
end
- def hook_attrs
- {
+ def hook_attrs(backward: true)
+ attrs = {
name: name,
description: description,
web_url: web_url,
@@ -843,12 +796,19 @@ class Project < ActiveRecord::Base
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch,
- # Backward compatibility
- homepage: web_url,
- url: url_to_repo,
- ssh_url: ssh_url_to_repo,
- http_url: http_url_to_repo
}
+
+ # Backward compatibility
+ if backward
+ attrs.merge!({
+ homepage: web_url,
+ url: url_to_repo,
+ ssh_url: ssh_url_to_repo,
+ http_url: http_url_to_repo
+ })
+ end
+
+ attrs
end
# Reset events cache related to this project
@@ -897,6 +857,7 @@ class Project < ActiveRecord::Base
repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
+ repository.copy_gitattributes(branch)
reload_default_branch
end
@@ -957,12 +918,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def ci_commit(sha)
- ci_commits.find_by(sha: sha)
+ def ci_commit(sha, ref)
+ ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_ci_commit(sha)
- ci_commit(sha) || ci_commits.create(sha: sha)
+ def ensure_ci_commit(sha, ref)
+ ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end
def enable_ci
@@ -1031,4 +992,11 @@ class Project < ActiveRecord::Base
def wiki
@wiki ||= ProjectWiki.new(self, self.owner)
end
+
+ def schedule_delete!(user_id, params)
+ # Queue this task for after the commit, so once we mark pending_delete it will run
+ run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }
+
+ update_attribute(:pending_delete, true)
+ end
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 79efb403058..e2f9ffb69ac 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -1,14 +1,4 @@
-# == Schema Information
-#
-# Table name: project_import_data
-#
-# id :integer not null, primary key
-# project_id :integer
-# data :text
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 792ad804575..7c23b766763 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require 'asana'
class AsanaService < Service
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 29d841faed8..d839221d315 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class AssemblaService < Service
include HTTParty
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 060062aaf7a..1d1780dcfbf 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class BambooService < CiService
include HTTParty
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 3efbfd2eec3..86a06321e21 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require "addressable/uri"
class BuildkiteService < CiService
@@ -26,7 +5,7 @@ class BuildkiteService < CiService
prop_accessor :project_url, :token, :enable_ssl_verification
- validates :project_url, presence: true, if: :activated?
+ validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -91,7 +70,7 @@ class BuildkiteService < CiService
{ type: 'text',
name: 'project_url',
placeholder: "#{ENDPOINT}/example/project" },
-
+
{ type: 'checkbox',
name: 'enable_ssl_verification',
title: "Enable SSL verification" }
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 6ab6d7417b7..54da4d74fc5 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 6e8f0842524..511b2eac792 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class CampfireService < Service
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index d9f0849d147..596c00705ad 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
# Base class for CI services
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 88a3e9218cb..6b2b1daa724 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class CustomIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index b4724bb647e..966dbc41d73 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class DroneCiService < CiService
prop_accessor :drone_url, :token, :enable_ssl_verification
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b831577cd97..e0083c43adb 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class EmailsOnPushService < Service
prop_accessor :send_from_committer_email
prop_accessor :disable_diffs
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index b402b68665a..f042bfc5210 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class ExternalWikiService < Service
include HTTParty
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 8605ce66e48..dd00275187f 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require "flowdock-git-hook"
class FlowdockService < Service
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 61babe9cfe5..598aca5e06d 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require "gemnasium/gitlab_service"
class GemnasiumService < Service
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 33f0d7ea01a..bbc312f5215 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
class GitlabCiService < CiService
# We override the active accessor to always make GitLabCiService disabled
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index eaa5654b9c6..5d17c358330 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing.url_helpers
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 0e3fa4a40fe..0ff4f4c8dd2 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class HipchatService < Service
MAX_COMMITS = 3
@@ -183,7 +162,7 @@ class HipchatService < Service
title = obj_attr[:title]
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
- merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>"
+ merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
message = "#{user_name} #{state} #{merge_request_link} in " \
"#{project_link}: <b>#{title}</b>"
@@ -224,7 +203,7 @@ class HipchatService < Service
when "MergeRequest"
subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
subject_id = subj_attr[:iid]
- subject_desc = "##{subject_id}"
+ subject_desc = "!#{subject_id}"
subject_type = "merge request"
title = format_title(subj_attr[:title])
when "Snippet"
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 04c714bfaad..91015e6c9b1 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require 'uri'
class IrkerService < Service
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 25045224ce5..6ae9b16d3ce 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -1,27 +1,6 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class IssueTrackerService < Service
- validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+ validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
default_value_for :category, 'issue_tracker'
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1ed42c4f3e7..beda89d3963 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class JiraService < IssueTrackerService
include HTTParty
include Gitlab::Routing.url_helpers
@@ -28,6 +7,8 @@ class JiraService < IssueTrackerService
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
+ validates :api_url, presence: true, url: true, if: :activated?
+
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index c9a890c7e3f..ad19b7795da 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class PivotaltrackerService < Service
include HTTParty
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index e76d9eca2ab..3dd878e4c7d 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class PushoverService < Service
include HTTParty
base_uri 'https://api.pushover.net/1'
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index de974354c77..11cce3e0561 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class RedmineService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index d89cf6d17b2..cf9e4d5a8b6 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -1,28 +1,7 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class SlackService < Service
prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
- validates :webhook, presence: true, if: :activated?
+ validates :webhook, presence: true, url: true, if: :activated?
def initialize_properties
if properties.nil?
@@ -60,7 +39,7 @@ class SlackService < Service
end
def supported_events
- %w(push issue merge_request note tag_push build)
+ %w(push issue merge_request note tag_push build wiki_page)
end
def execute(data)
@@ -90,6 +69,8 @@ class SlackService < Service
NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
+ when "wiki_page"
+ WikiPageMessage.new(data)
end
opt = {}
@@ -133,3 +114,4 @@ require "slack_service/push_message"
require "slack_service/merge_message"
require "slack_service/note_message"
require "slack_service/build_message"
+require "slack_service/wiki_page_message"
diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb
index e792c258f73..11fc691022b 100644
--- a/app/models/project_services/slack_service/merge_message.rb
+++ b/app/models/project_services/slack_service/merge_message.rb
@@ -50,7 +50,7 @@ class SlackService
end
def merge_request_link
- "[merge request ##{merge_request_id}](#{merge_request_url})"
+ "[merge request !#{merge_request_id}](#{merge_request_url})"
end
def merge_request_url
diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb
index b15d9a14677..89ba51cb662 100644
--- a/app/models/project_services/slack_service/note_message.rb
+++ b/app/models/project_services/slack_service/note_message.rb
@@ -58,7 +58,7 @@ class SlackService
def create_merge_note(merge_request)
commented_on_message(
- "[merge request ##{merge_request[:iid]}](#{@note_url})",
+ "[merge request !#{merge_request[:iid]}](#{@note_url})",
format_title(merge_request[:title]))
end
diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb
new file mode 100644
index 00000000000..f336d9e7691
--- /dev/null
+++ b/app/models/project_services/slack_service/wiki_page_message.rb
@@ -0,0 +1,53 @@
+class SlackService
+ class WikiPageMessage < BaseMessage
+ attr_reader :user_name
+ attr_reader :title
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :wiki_page_url
+ attr_reader :action
+ attr_reader :description
+
+ def initialize(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @title = obj_attr[:title]
+ @wiki_page_url = obj_attr[:url]
+ @description = obj_attr[:content]
+
+ @action =
+ case obj_attr[:action]
+ when "create"
+ "created"
+ when "update"
+ "edited"
+ end
+ end
+
+ def attachments
+ description_message
+ end
+
+ private
+
+ def message
+ "#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
+ end
+
+ def description_message
+ [{ text: format(@description), color: attachment_color }]
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def wiki_page_link
+ "[wiki page](#{wiki_page_url})"
+ end
+ end
+end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 8dceee5e2c5..b0dcb52eba1 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
class TeamcityService < CiService
include HTTParty
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 1f7d85a5f3d..5fba6baa204 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
class ProjectSnippet < Snippet
belongs_to :project
belongs_to :author, class_name: "User"
@@ -22,4 +6,6 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
+
+ participant :author, :notes
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 8ebd790a89e..33cf046fa75 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: protected_branches
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-# developers_can_push :boolean default(FALSE), not null
-#
-
class ProtectedBranch < ActiveRecord::Base
include Gitlab::ShellAdapter
diff --git a/app/models/release.rb b/app/models/release.rb
index 89f70278af5..e196b84eb18 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string(255)
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
class Release < ActiveRecord::Base
belongs_to :project
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 308c590e3f8..7aebfe279fb 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -12,11 +12,13 @@ class Repository
attr_accessor :path_with_namespace, :project
def self.clean_old_archives
- repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
+ Gitlab::Metrics.measure(:clean_old_archives) do
+ repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
- return unless File.directory?(repository_downloads_path)
+ return unless File.directory?(repository_downloads_path)
- Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ end
end
def initialize(path_with_namespace, project)
@@ -85,13 +87,15 @@ class Repository
nil
end
- def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
+ def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil)
options = {
repo: raw_repository,
ref: ref,
path: path,
limit: limit,
offset: offset,
+ after: after,
+ before: before,
# --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false,
@@ -144,10 +148,20 @@ class Repository
find_branch(branch_name)
end
- def add_tag(tag_name, ref, message = nil)
- before_push_tag
+ def add_tag(user, tag_name, target, message = nil)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ options = { message: message, tagger: user_to_committer(user) } if message
- gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.tags.create(tag_name, target, options)
+ end
+
+ find_tag(tag_name)
end
def rm_branch(user, branch_name)
@@ -226,7 +240,8 @@ class Repository
def cache_keys
%i(size branch_names tag_names commit_count
- readme version contribution_guide changelog license)
+ readme version contribution_guide changelog
+ license_blob license_key)
end
def build_cache
@@ -454,32 +469,26 @@ class Repository
def changelog
cache.fetch(:changelog) do
tree(:head).blobs.find do |file|
- file.name =~ /\A(changelog|history)/i
+ file.name =~ /\A(changelog|history|changes|news)/i
end
end
end
- def license
- cache.fetch(:license) do
- licenses = tree(:head).blobs.find_all do |file|
- file.name =~ /\A(copying|license|licence)/i
- end
-
- preferences = [
- /\Alicen[sc]e\z/i, # LICENSE, LICENCE
- /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
- /\Acopying\z/i, # COPYING
- /\Acopying\.(?!lesser)/i, # COPYING.txt
- /Acopying.lesser/i # COPYING.LESSER
- ]
+ def license_blob
+ return nil if !exists? || empty?
- license = nil
- preferences.each do |r|
- license = licenses.find { |l| l.name =~ r }
- break if license
+ cache.fetch(:license_blob) do
+ tree(:head).blobs.find do |file|
+ file.name =~ /\A(licen[sc]e|copying)(\..+|\z)/i
end
+ end
+ end
+
+ def license_key
+ return nil if !exists? || empty?
- license
+ cache.fetch(:license_key) do
+ Licensee.license(path).try(:key)
end
end
@@ -547,15 +556,18 @@ class Repository
commit(sha)
end
- def next_patch_branch
- patch_branch_ids = self.branch_names.map do |n|
- result = n.match(/\Apatch-([0-9]+)\z/)
+ def next_branch(name, opts={})
+ branch_ids = self.branch_names.map do |n|
+ next 1 if n == name
+ result = n.match(/\A#{name}-([0-9]+)\z/)
result[1].to_i if result
end.compact
- highest_patch_branch_id = patch_branch_ids.max || 0
+ highest_branch_id = branch_ids.max || 0
+
+ return name if opts[:mild] && 0 == highest_branch_id
- "patch-#{highest_patch_branch_id + 1}"
+ "#{name}-#{highest_branch_id + 1}"
end
# Remove archives older than 2 hours
@@ -575,7 +587,7 @@ class Repository
end
def contributors
- commits = self.commits(nil, nil, 2000, 0, true)
+ commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
@@ -758,6 +770,28 @@ class Repository
end
end
+ def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
+ source_sha = find_branch(base_branch).target
+ cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
+
+ return false unless cherry_pick_tree_id
+
+ commit_with_hooks(user, base_branch) do |ref|
+ committer = user_to_committer(user)
+ source_sha = Rugged::Commit.create(rugged,
+ message: commit.message,
+ author: {
+ email: commit.author_email,
+ name: commit.author_name,
+ time: commit.authored_date
+ },
+ committer: committer,
+ tree: cherry_pick_tree_id,
+ parents: [rugged.lookup(source_sha)],
+ update_ref: ref)
+ end
+ end
+
def check_revert_content(commit, base_branch)
source_sha = find_branch(base_branch).target
args = [commit.id, source_sha]
@@ -772,6 +806,20 @@ class Repository
tree_id
end
+ def check_cherry_pick_content(commit, base_branch)
+ source_sha = find_branch(base_branch).target
+ args = [commit.id, source_sha]
+ args << 1 if commit.merge_commit?
+
+ cherry_pick_index = rugged.cherrypick_commit(*args)
+ return false if cherry_pick_index.conflicts?
+
+ tree_id = cherry_pick_index.write_tree(rugged)
+ return false unless diff_exists?(source_sha, tree_id)
+
+ tree_id
+ end
+
def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0
end
@@ -902,6 +950,16 @@ class Repository
raw_repository.ls_files(actual_ref)
end
+ def copy_gitattributes(ref)
+ actual_ref = ref || root_ref
+ begin
+ raw_repository.copy_gitattributes(actual_ref)
+ true
+ rescue Gitlab::Git::Repository::InvalidRef
+ false
+ end
+ end
+
def main_language
return if empty? || rugged.head_unborn?
diff --git a/app/models/security_event.rb b/app/models/security_event.rb
index 68c00adad59..d131c11cb6c 100644
--- a/app/models/security_event.rb
+++ b/app/models/security_event.rb
@@ -1,16 +1,2 @@
-# == Schema Information
-#
-# Table name: audit_events
-#
-# id :integer not null, primary key
-# author_id :integer not null
-# type :string(255) not null
-# entity_id :integer not null
-# entity_type :string(255) not null
-# details :text
-# created_at :datetime
-# updated_at :datetime
-#
-
class SecurityEvent < AuditEvent
end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 77115597d71..375f195dba7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: sent_notifications
-#
-# id :integer not null, primary key
-# project_id :integer
-# noteable_id :integer
-# noteable_type :string(255)
-# recipient_id :integer
-# commit_id :string(255)
-# line_code :string(255)
-# reply_key :string(255) not null
-#
-
class SentNotification < ActiveRecord::Base
belongs_to :project
belongs_to :noteable, polymorphic: true
diff --git a/app/models/service.rb b/app/models/service.rb
index 721273250ea..de3fd24584a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
@@ -32,6 +11,7 @@ class Service < ActiveRecord::Base
default_value_for :tag_push_events, true
default_value_for :note_events, true
default_value_for :build_events, true
+ default_value_for :wiki_page_events, true
after_initialize :initialize_properties
@@ -53,6 +33,7 @@ class Service < ActiveRecord::Base
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
+ scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
default_value_for :category, 'common'
@@ -94,7 +75,7 @@ class Service < ActiveRecord::Base
end
def supported_events
- %w(push tag_push issue merge_request)
+ %w(push tag_push issue merge_request wiki_page)
end
def execute(data)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b96e3937281..0a3c3b57669 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Linguist::BlobHelper
@@ -112,6 +96,10 @@ class Snippet < ActiveRecord::Base
visibility_level
end
+ def no_highlighting?
+ content.lines.count > 1000
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index dd800ce110f..3b8aa1eb866 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: subscriptions
-#
-# id :integer not null, primary key
-# user_id :integer
-# subscribable_id :integer
-# subscribable_type :string(255)
-# subscribed :boolean
-# created_at :datetime
-# updated_at :datetime
-#
-
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :subscribable, polymorphic: true
diff --git a/app/models/todo.rb b/app/models/todo.rb
index d85f7bfdf57..f8b59fe4126 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
class Todo < ActiveRecord::Base
ASSIGNED = 1
MENTIONED = 2
diff --git a/app/models/user.rb b/app/models/user.rb
index 52f2904f450..37179c10788 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,69 +1,4 @@
-# == Schema Information
-#
-# Table name: users
-#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
-# unlock_token :string
-# otp_grace_period_started_at :datetime
-# external :boolean default(FALSE)
-#
-
require 'carrierwave/orm/activerecord'
-require 'file_size_validator'
class User < ActiveRecord::Base
extend Gitlab::ConfigHelper
@@ -86,7 +21,7 @@ class User < ActiveRecord::Base
default_value_for :theme_id, gitlab_config.default_theme
devise :two_factor_authenticatable,
- otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
+ otp_secret_encryption_key: Gitlab::Application.config.secret_key_base
alias_attribute :two_factor_enabled, :otp_required_for_login
devise :two_factor_backupable, otp_number_of_backup_codes: 10
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 413f3f485a8..0dfe597317e 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: users_star_projects
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# user_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 526760779a4..3d5fd9d3ee9 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -29,6 +29,10 @@ class WikiPage
# new Page values before writing to the Gollum repository.
attr_accessor :attributes
+ def hook_attrs
+ attributes
+ end
+
def initialize(wiki, page = nil, persisted = false)
@wiki = wiki
@page = page
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 2cd51a7610f..18274ce24e2 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,7 +1,11 @@
module Ci
class CreateBuildsService
- def execute(commit, stage, ref, tag, user, trigger_request, status)
- builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
+ def initialize(commit)
+ @commit = commit
+ end
+
+ def execute(stage, user, status, trigger_request = nil)
+ builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
- unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+ unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
+ trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
@@ -26,17 +31,21 @@ module Ci
:stage,
:stage_idx)
- build_attrs.merge!(ref: ref,
- tag: tag,
+ build_attrs.merge!(ref: @commit.ref,
+ tag: @commit.tag,
trigger_request: trigger_request,
user: user,
- project: commit.project)
+ project: @commit.project)
- build = commit.builds.create!(build_attrs)
- build.execute_hooks
- build
+ @commit.builds.create!(build_attrs)
end
end
end
+
+ private
+
+ def config_processor
+ @config_processor ||= @commit.config_processor
+ end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index b3dfc707221..993acf11db9 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -7,14 +7,14 @@ module Ci
# check if ref is tag
tag = project.repository.find_tag(ref).present?
- ci_commit = project.ensure_ci_commit(commit.sha)
+ ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
commit: ci_commit,
)
- if ci_commit.create_builds(ref, tag, nil, trigger_request)
+ if ci_commit.create_builds(nil, trigger_request)
trigger_request
end
end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index 50c95ced8a7..3018f27ec05 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -3,8 +3,9 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
- commit = project.ci_commits.find_by(sha: sha)
- image_name = image_for_commit(commit)
+ ci_commits = project.ci_commits.where(sha: sha)
+ ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref]
+ image_name = image_for_status(ci_commits.status)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
@@ -16,9 +17,9 @@ module Ci
project.commit(ref).try(:sha) if ref
end
- def image_for_commit(commit)
- return 'build-unknown.svg' unless commit
- 'build-' + commit.status + ".svg"
+ def image_for_status(status)
+ status ||= 'unknown'
+ 'build-' + status + ".svg"
end
end
end
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
new file mode 100644
index 00000000000..6b69cb53b2c
--- /dev/null
+++ b/app/services/commits/change_service.rb
@@ -0,0 +1,47 @@
+module Commits
+ class ChangeService < ::BaseService
+ class ValidationError < StandardError; end
+ class ChangeError < StandardError; end
+
+ def execute
+ @source_project = params[:source_project] || @project
+ @target_branch = params[:target_branch]
+ @commit = params[:commit]
+ @create_merge_request = params[:create_merge_request].present?
+
+ check_push_permissions unless @create_merge_request
+ commit
+ rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
+ ValidationError, ChangeError => ex
+ error(ex.message)
+ end
+
+ def commit
+ raise NotImplementedError
+ end
+
+ private
+
+ def check_push_permissions
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise ValidationError.new('You are not allowed to push into this branch')
+ end
+
+ true
+ end
+
+ def create_target_branch(new_branch)
+ # Temporary branch exists and contains the change commit
+ return success if repository.find_branch(new_branch)
+
+ result = CreateBranchService.new(@project, current_user)
+ .execute(new_branch, @target_branch, source_project: @source_project)
+
+ if result[:status] == :error
+ raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
+ end
+ end
+ end
+end
diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb
new file mode 100644
index 00000000000..f9a4efa7182
--- /dev/null
+++ b/app/services/commits/cherry_pick_service.rb
@@ -0,0 +1,19 @@
+module Commits
+ class CherryPickService < ChangeService
+ def commit
+ cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch
+ cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
+
+ if cherry_pick_tree_id
+ create_target_branch(cherry_pick_into) if @create_merge_request
+
+ repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
+ success
+ else
+ error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
+ It may have already been cherry-picked, or a more recent commit may have updated some of its content."
+ raise ChangeError, error_msg
+ end
+ end
+ end
+end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
index a3c950ede1f..c7de9f6f35e 100644
--- a/app/services/commits/revert_service.rb
+++ b/app/services/commits/revert_service.rb
@@ -1,21 +1,5 @@
module Commits
- class RevertService < ::BaseService
- class ValidationError < StandardError; end
- class ReversionError < StandardError; end
-
- def execute
- @source_project = params[:source_project] || @project
- @target_branch = params[:target_branch]
- @commit = params[:commit]
- @create_merge_request = params[:create_merge_request].present?
-
- check_push_permissions unless @create_merge_request
- commit
- rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
- ValidationError, ReversionError => ex
- error(ex.message)
- end
-
+ class RevertService < ChangeService
def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
@@ -26,34 +10,10 @@ module Commits
repository.revert(current_user, @commit, revert_into, revert_tree_id)
success
else
- error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
+ error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content."
- raise ReversionError, error_msg
+ raise ChangeError, error_msg
end
end
-
- private
-
- def create_target_branch(new_branch)
- # Temporary branch exists and contains the revert commit
- return success if repository.find_branch(new_branch)
-
- result = CreateBranchService.new(@project, current_user)
- .execute(new_branch, @target_branch, source_project: @source_project)
-
- if result[:status] == :error
- raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
- end
- end
-
- def check_push_permissions
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
-
- unless allowed
- raise ValidationError.new('You are not allowed to push into this branch')
- end
-
- true
- end
end
end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 707c2f7ff85..9f4481a8153 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -43,9 +43,4 @@ class CreateBranchService < BaseService
out[:branch] = branch
out
end
-
- def build_push_data(project, user, branch)
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
- end
end
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 69d5c42a877..0d2aa1ff03d 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
+ before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref = Gitlab::Git.ref_name(origin_ref)
+ tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
- commit = project.ci_commit(sha)
+ commit = project.ci_commit(sha, ref)
unless commit
- commit = project.ci_commits.new(sha: sha)
+ commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file
@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
- tag = Gitlab::Git.tag_ref?(origin_ref)
- commit.create_builds(ref, tag, user)
+ commit.create_builds(user)
end
+ commit.touch
commit
end
end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 55985380d31..91ed0e354d0 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -1,50 +1,30 @@
require_relative 'base_service'
class CreateTagService < BaseService
- def execute(tag_name, ref, message, release_description = nil)
+ def execute(tag_name, target, message, release_description = nil)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
- if valid_tag == false
- return error('Tag name invalid')
- end
+ return error('Tag name invalid') unless valid_tag
repository = project.repository
- existing_tag = repository.find_tag(tag_name)
- if existing_tag
- return error('Tag already exists')
- end
-
message.strip! if message
- repository.add_tag(tag_name, ref, message)
- new_tag = repository.find_tag(tag_name)
+ new_tag = nil
+ begin
+ new_tag = repository.add_tag(current_user, tag_name, target, message)
+ rescue Rugged::TagError
+ return error("Tag #{tag_name} already exists")
+ rescue GitHooksService::PreReceiveError
+ return error('Tag creation was rejected by Git hook')
+ end
if new_tag
- push_data = create_push_data(project, current_user, new_tag)
- EventCreateService.new.push(project, current_user, push_data)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, current_user, push_data)
-
if release_description
CreateReleaseService.new(@project, @current_user).
execute(tag_name, release_description)
end
-
- success(new_tag)
+ success.merge(tag: new_tag)
else
- error('Invalid reference name')
+ error("Target #{target} is invalid")
end
end
-
- def success(branch)
- out = super()
- out[:tag] = branch
- out
- end
-
- def create_push_data(project, user, tag)
- commits = [project.commit(tag.target)].compact
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message)
- end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index dc74c02760b..66136b62617 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -17,6 +17,7 @@ class GitPushService < BaseService
# 6. Checks if the project's main language has changed
#
def execute
+ @project.repository.after_create if @project.empty_repo?
@project.repository.after_push_commit(branch_name, params[:newrev])
if push_remove_branch?
@@ -42,7 +43,12 @@ class GitPushService < BaseService
# Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages
+
+ # Update the bare repositories info/attributes file using the contents of the default branches
+ # .gitattributes file
+ update_gitattributes if is_default_branch?
end
+
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
update_merge_requests
@@ -54,6 +60,10 @@ class GitPushService < BaseService
perform_housekeeping
end
+ def update_gitattributes
+ @project.repository.copy_gitattributes(params[:ref])
+ end
+
def update_main_language
# Performance can be bad so for now only check main_language once
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
@@ -73,6 +83,7 @@ class GitPushService < BaseService
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
EventCreateService.new.push(@project, current_user, build_push_data)
+ SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
@@ -138,6 +149,11 @@ class GitPushService < BaseService
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end
+ def build_push_data_system_hook
+ @push_data_system ||= Gitlab::PushDataBuilder.
+ build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
+ end
+
def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index c88c7672805..7410442609d 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -1,16 +1,17 @@
-class GitTagPushService
- attr_accessor :project, :user, :push_data
+class GitTagPushService < BaseService
+ attr_accessor :push_data
- def execute(project, user, oldrev, newrev, ref)
+ def execute
+ project.repository.after_create if project.empty_repo?
project.repository.before_push_tag
- @project, @user = project, user
- @push_data = build_push_data(oldrev, newrev, ref)
+ @push_data = build_push_data
- EventCreateService.new.push(project, user, @push_data)
+ EventCreateService.new.push(project, current_user, @push_data)
+ SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, @user, @push_data)
+ CreateCommitBuildsService.new.execute(project, current_user, @push_data)
ProjectCacheWorker.perform_async(project.id)
true
@@ -18,14 +19,14 @@ class GitTagPushService
private
- def build_push_data(oldrev, newrev, ref)
+ def build_push_data
commits = []
message = nil
- if !Gitlab::Git.blank_ref?(newrev)
- tag_name = Gitlab::Git.ref_name(ref)
+ if !Gitlab::Git.blank_ref?(params[:newrev])
+ tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
- if tag && tag.target == newrev
+ if tag && tag.target == params[:newrev]
commit = project.commit(tag.target)
commits = [commit].compact
message = tag.message
@@ -33,6 +34,11 @@ class GitTagPushService
end
Gitlab::PushDataBuilder.
- build(project, user, oldrev, newrev, ref, commits, message)
+ build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
+ end
+
+ def build_system_push_data
+ Gitlab::PushDataBuilder.
+ build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 18f76d3f650..2b16089df1b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -37,8 +37,9 @@ class IssuableBaseService < BaseService
end
def filter_params(issuable_ability_name = :issue)
- params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
- params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_assignee
+ filter_milestone
+ filter_labels
ability = :"admin_#{issuable_ability_name}"
@@ -49,6 +50,29 @@ class IssuableBaseService < BaseService
end
end
+ def filter_assignee
+ if params[:assignee_id] == IssuableFinder::NONE
+ params[:assignee_id] = ''
+ end
+ end
+
+ def filter_milestone
+ milestone_id = params[:milestone_id]
+ return unless milestone_id
+
+ if milestone_id == IssuableFinder::NONE ||
+ project.milestones.find_by(id: milestone_id).nil?
+ params[:milestone_id] = ''
+ end
+ end
+
+ def filter_labels
+ return if params[:label_ids].to_a.empty?
+
+ params[:label_ids] =
+ project.labels.where(id: params[:label_ids]).pluck(:id)
+ end
+
def update(issuable)
change_state(issuable)
filter_params
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 82e7090f1ea..e61628086f0 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -41,14 +41,25 @@ module Issues
private
def create_new_issue
- new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
+ new_params = { id: nil, iid: nil, label_ids: cloneable_label_ids,
+ milestone_id: cloneable_milestone_id,
project: @new_project, author: @old_issue.author,
description: rewrite_content(@old_issue.description) }
- new_params = @old_issue.serializable_hash.merge(new_params)
+ new_params = @old_issue.serializable_hash.symbolize_keys.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute
end
+ def cloneable_label_ids
+ @new_project.labels
+ .where(title: @old_issue.labels.pluck(:title)).pluck(:id)
+ end
+
+ def cloneable_milestone_id
+ @new_project.milestones
+ .find_by(title: @old_issue.milestone.try(:title)).try(:id)
+ end
+
def rewrite_notes
@old_issue.notes.find_each do |note|
new_note = note.dup
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd..cd4230aa5e4 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -7,6 +7,9 @@ module MergeRequests
merge_request.can_be_created = false
merge_request.compare_commits = []
merge_request.source_project = project unless merge_request.source_project
+
+ merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
@@ -38,21 +41,45 @@ module MergeRequests
merge_request.can_be_created = false
end
+ set_title_and_description(merge_request)
+ end
+
+ private
+
+ # When your branch name starts with an iid followed by a dash this pattern will be
+ # interpreted as the user wants to close that issue on this project.
+ #
+ # For example:
+ # - Issue 112 exists, title: Emoji don't show up in commit title
+ # - Source branch is: 112-fix-mep-mep
+ #
+ # Will lead to:
+ # - Appending `Closes #112` to the description
+ # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
+ # more than one commit in the MR
+ #
+ def set_title_and_description(merge_request)
+ if match = merge_request.source_branch.match(/\A(\d+)-/)
+ iid = match[1]
+ end
+
commits = merge_request.compare_commits
if commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
merge_request.description ||= commit.description.try(:strip)
+ elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?)
+ case issue
+ when Issue
+ merge_request.title = "Resolve \"#{issue.title}\""
+ when ExternalIssue
+ merge_request.title = "Resolve #{issue.title}"
+ end
else
merge_request.title = merge_request.source_branch.titleize.humanize
end
- # When your branch name starts with an iid followed by a dash this pattern will
- # be interpreted as the use wants to close that issue on this project
- # Pattern example: 112-fix-mep-mep
- # Will lead to appending `Closes #112` to the description
- if match = merge_request.source_branch.match(/\A(\d+)-/)
- iid = match[1]
+ if iid
closes_issue = "Closes ##{iid}"
if merge_request.description.present?
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index c5be21ba897..da2a774b70d 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -10,6 +10,8 @@ module Notes
execute(note.noteable, note.note)
end
+ return unless valid_project?(note)
+
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
@@ -18,5 +20,14 @@ module Notes
note
end
+
+ private
+
+ def valid_project?(note)
+ return false unless project
+ return true if note.for_commit?
+
+ note.noteable.try(:project) == project
+ end
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index df5054f08d7..19aab999e00 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -7,9 +7,7 @@ module Projects
DELETED_FLAG = '+deleted'
def pending_delete!
- project.update_attribute(:pending_delete, true)
-
- ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
+ project.schedule_delete!(current_user.id, params)
end
def execute
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 79a27f4af7e..111b3ec05ea 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,6 +34,8 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
+ project.expire_caches_before_rename(old_path)
+
# Apply new namespace id and visibility level
project.namespace = new_namespace
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index f0615ec7420..e43b5b51e5b 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -3,17 +3,13 @@ class SystemHooksService
execute_hooks(build_event_data(model, event))
end
- private
-
- def execute_hooks(data)
- SystemHook.all.each do |sh|
- async_execute_hook(sh, data, 'system_hooks')
+ def execute_hooks(data, hooks_scope = :all)
+ SystemHook.send(hooks_scope).each do |hook|
+ hook.async_execute(data, 'system_hooks')
end
end
- def async_execute_hook(hook, data, hook_name)
- Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
- end
+ private
def build_event_data(model, event)
data = {
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 82a0e2fd1f5..4bdb1b0c074 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -351,7 +351,7 @@ class SystemNoteService
# Returns an Array of Strings
def self.new_commit_summary(new_commits)
new_commits.collect do |commit|
- "* #{commit.short_id} - #{commit.title}"
+ "* #{commit.short_id} - #{escape_html(commit.title)}"
end
end
@@ -433,4 +433,8 @@ class SystemNoteService
body = "Moved #{direction} #{cross_reference}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
+
+ def self.escape_html(text)
+ Rack::Utils.escape_html(text)
+ end
end
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
new file mode 100644
index 00000000000..9162f128602
--- /dev/null
+++ b/app/services/wiki_pages/base_service.rb
@@ -0,0 +1,27 @@
+module WikiPages
+ class BaseService < ::BaseService
+
+ def hook_data(page, action)
+ hook_data = {
+ object_kind: page.class.name.underscore,
+ user: current_user.hook_attrs,
+ project: @project.hook_attrs,
+ object_attributes: page.hook_attrs,
+ # DEPRECATED
+ repository: @project.hook_attrs.slice(:name, :url, :description, :homepage)
+ }
+
+ page_url = Gitlab::UrlBuilder.build(page)
+ hook_data[:object_attributes].merge!(url: page_url, action: action)
+ hook_data
+ end
+
+ private
+
+ def execute_hooks(page, action = 'create')
+ page_data = hook_data(page, action)
+ @project.execute_hooks(page_data, :wiki_page_hooks)
+ @project.execute_services(page_data, :wiki_page_hooks)
+ end
+ end
+end
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
new file mode 100644
index 00000000000..24a817c06c9
--- /dev/null
+++ b/app/services/wiki_pages/create_service.rb
@@ -0,0 +1,14 @@
+module WikiPages
+ class CreateService < WikiPages::BaseService
+ def execute
+ project_wiki = ProjectWiki.new(@project, current_user)
+ page = WikiPage.new(project_wiki)
+
+ if page.create(@params)
+ execute_hooks(page, 'create')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb
new file mode 100644
index 00000000000..8f6a50da838
--- /dev/null
+++ b/app/services/wiki_pages/update_service.rb
@@ -0,0 +1,11 @@
+module WikiPages
+ class UpdateService < WikiPages::BaseService
+ def execute(page)
+ if page.update(@params[:content], @params[:format], @params[:message])
+ execute_hooks(page, 'update')
+ end
+
+ page
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 555aea554f0..e0d8d16a954 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -26,7 +26,9 @@
.btn-group{ data: data_attrs }
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
= level
- %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
+ %span.help-block#restricted-visibility-help
+ Selected levels cannot be used by non-admin users for projects or snippets.
+ If the public level is restricted, user profiles are only visible to logged in users.
.form-group
= f.label :import_sources, class: 'control-label col-sm-2'
.col-sm-10
@@ -153,7 +155,11 @@
= f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled
Enable shared runners for new projects
-
+ .form-group
+ = f.label :shared_runners_text, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_area :shared_runners_text, class: 'form-control', rows: 4
+ .help-block Markdown enabled
.form-group
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
.col-sm-10
@@ -212,6 +218,13 @@
.help-block
The sampling interval in seconds. Sampled data includes memory usage,
retained Ruby objects, file descriptors and so on.
+ .form-group
+ = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :metrics_packet_size, class: 'form-control'
+ .help-block
+ The amount of points to store in a single UDP packet. More points
+ results in fewer but larger UDP packets being sent.
%fieldset
%legend Spam and Anti-bot Protection
@@ -280,7 +293,7 @@
= f.check_box :repository_checks_enabled
Enable Repository Checks
.help-block
- GitLab will periodically run
+ GitLab will periodically run
%a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
in all project and wiki repositories to look for silent disk corruption issues.
.form-group
@@ -288,7 +301,7 @@
= link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
.help-block
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
-
+
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index 3571eefd570..967151bc33b 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -35,15 +35,15 @@
%td
#{build.stage} / #{build.name}
- .pull-right
- - if build.tags.any?
- - build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - if build.try(:trigger_request)
- %span.label.label-info triggered
- - if build.try(:allow_failure)
- %span.label.label-danger allowed to fail
+ %td
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(:allow_failure)
+ %span.label.label-danger allowed to fail
%td.duration
- if build.duration
@@ -61,12 +61,12 @@
%td
.pull-right
- if can?(current_user, :read_build, project) && build.artifacts?
- = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
+ = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
%i.fa.fa-download
- if can?(current_user, :update_build, build.project)
- if build.active?
- = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
+ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
- %i.fa.fa-repeat
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ %i.fa.fa-refresh
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 5931efdefe6..804d7851bdb 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -19,7 +19,7 @@
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-.gray-content-block.second-block
+.row-content-block.second-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
@@ -38,6 +38,7 @@
%th Ref
%th Runner
%th Name
+ %th Tags
%th Duration
%th Finished at
%th
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index ad952052f25..7b388cf7862 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -13,9 +13,36 @@
= form_errors(@hook)
.form-group
- = f.label :url, "URL:", class: 'control-label'
+ = f.label :url, 'URL', class: 'control-label'
.col-sm-10
- = f.text_field :url, class: "form-control"
+ = f.text_field :url, class: 'form-control'
+ .form-group
+ = f.label :token, 'Secret Token', class: 'control-label'
+ .col-sm-10
+ = f.text_field :token, class: 'form-control'
+ %p.help-block
+ Use this token to validate received payloads
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10.prepend-top-10
+ %div
+ System hook will be triggered on set of events like creating project
+ or adding ssh key. But you can also enable extra triggers like Push events.
+
+ %div.prepend-top-default
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
.form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10
@@ -31,13 +58,16 @@
.panel.panel-default
.panel-heading
System hooks (#{@hooks.count})
- %ul.well-list
+ %ul.content-list
- @hooks.each do |hook|
%li
- .list-item-name
- %strong= hook.url
- %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
-
- .pull-right
+ .controls
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ .monospace= hook.url
+ %div
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
+ - if hook.send(trigger)
+ %span.label.label-gray= trigger.titleize
+ %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index 4b475a4d8fa..698feb571ac 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -7,7 +7,7 @@
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
= link_to klass::file_name, "##{klass::file_name_noext}",
'data-toggle' => 'tab'
-.gray-content-block
+.row-content-block
To prevent performance issues admin logs output the last 2000 lines
.tab-content
- loggers.each do |klass|
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index b05fdbd5552..fe0b9d3a491 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -7,17 +7,17 @@
.form-group
= f.label :name, class: 'control-label'
.col-sm-10
- = f.text_field :name, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group
= f.label :username, class: 'control-label'
.col-sm-10
- = f.text_field :username, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group
= f.label :email, class: 'control-label'
.col-sm-10
- = f.text_field :email, required: true, autocomplete: "off", class: 'form-control'
+ = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
- if @user.new_record?
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 0ee8dc962b9..d6743081c8e 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
Without projects
%small.badge= number_with_delimiter(User.without_projects.count)
- .gray-content-block.second-block
+ .row-content-block.second-block
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f9ec3a89158..fc42e5dcc66 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -8,14 +8,14 @@
= link_to todos_filter_path(state: 'pending') do
%span
To do
- %span{class: 'badge'}
+ %span.badge
= todos_pending_count
- todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
- %span{class: 'badge'}
+ %span.badge
= todos_done_count
.nav-controls
@@ -25,7 +25,7 @@
= icon('spinner spin')
.todos-filters
- .gray-content-block.second-block
+ .row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
= select_tag('project_id', todo_projects_options,
@@ -45,6 +45,7 @@
.prepend-top-default
- if @todos.any?
+ .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small.js-todos-list
- project = group[0]
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
new file mode 100644
index 00000000000..3c3830a3f10
--- /dev/null
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -0,0 +1,10 @@
+.well-confirmation.text-center
+ %h1.prepend-top-0
+ Almost there...
+ %p.lead
+ Please check your email to confirm your account
+%p.confirmation-content.text-center
+ No confirmation email received? Please check your spam folder or
+.append-bottom-20.prepend-top-20.text-center
+ %a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
+ Request new confirmation email
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 22b2c1a186b..c9d1e454a5e 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -4,7 +4,7 @@
%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.
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-factor Authentication code', required: true, autofocus: true
+ %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
= f.submit "Verify code", class: "btn btn-save"
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index cb93ff2465e..510215bb8cd 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,18 +6,17 @@
.login-heading
%h3 Create an account
.login-body
- - user = params[:user].present? ? params[:user] : {}
- = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
+ = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
- = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
+ = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
%div
- = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
+ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
- = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
+ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 0aff79749ef..3998e66f40d 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Applications"
-- header_title page_title, applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -45,7 +44,7 @@
= icon('pencil')
= render 'delete_form', application: application, small: true
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
You don't have any applications
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
@@ -79,5 +78,5 @@
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
- .profile-settings-message.text-center
+ .settings-message.text-center
You don't have any authorized applications
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 4d20dd5830e..e4629bae0e6 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,7 +4,12 @@
#{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.2"] do
- = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - if event.author
+ = link_to user_path(event.author) do
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+ - else
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 5753158c24d..a1a282178e7 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -1,5 +1,5 @@
- if show_last_push_widget?(event)
- .gray-content-block.clear-block.last-push-widget
+ .row-content-block.clear-block.last-push-widget
.event-last-push
.event-last-push-text
%span You pushed to
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 8ffca96bb4e..57f6e7e0612 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -6,7 +6,7 @@
- else
= render 'explore/head'
-.gray-content-block.clearfix
+.row-content-block.clearfix
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 0f100c39ffb..9b838b9f3b7 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -6,7 +6,7 @@
- else
= render 'explore/head'
-.gray-content-block
+.row-content-block
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
index f73e1d9e865..aaad265b3ee 100644
--- a/app/views/groups/activity.html.haml
+++ b/app/views/groups/activity.html.haml
@@ -3,7 +3,6 @@
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
- page_title "Activity"
-- header_title group_title(@group, "Activity", activity_group_path(@group))
%section.activities
= render 'activities'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index a698cbbe9db..92cd4c553d0 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,3 @@
-- header_title group_title(@group, "Settings", edit_group_path(@group))
-
.panel.panel-default.prepend-top-default
.panel-heading
Group settings
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 6b7fd5746d6..0eb6bbd4420 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Members"
-- header_title group_title(@group, "Members", group_group_members_path(@group))
.group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group)
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index aea35c50862..4434f1cbd35 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,5 +1,4 @@
- page_title "Issues"
-- header_title group_title(@group, "Issues", issues_group_path(@group))
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
@@ -16,7 +15,7 @@
= render 'shared/issuable/filter', type: :issues
-.gray-content-block.second-block
+.row-content-block.second-block
Only issues from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index e1c9dd931ee..e6953d94531 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,5 +1,4 @@
- page_title "Merge Requests"
-- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.top-area
= render 'shared/issuable/nav', type: :merge_requests
@@ -8,7 +7,7 @@
= render 'shared/issuable/filter', type: :merge_requests
-.gray-content-block.second-block
+.row-content-block.second-block
Only merge requests from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index ab307708b75..121a7de3ad7 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Milestones"
-- header_title group_title(@group, "Milestones", group_milestones_path(@group))
.top-area
= render 'shared/milestones_filter'
@@ -10,7 +9,7 @@
= icon('plus')
New Milestone
-.gray-content-block
+.row-content-block
Only milestones from
%strong #{@group.name}
group are listed here.
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index dd75766121e..c2f2d9912f7 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,5 +1,4 @@
- page_title "Projects"
-- header_title group_title(@group, "Projects", projects_group_path(@group))
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 3d16ecb097a..77c297255b8 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -4,28 +4,20 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-.cover-block
- .cover-controls
- - if @group && can?(current_user, :admin_group, @group)
- = link_to icon('pencil'), edit_group_path(@group), class: 'btn'
- - if current_user
- = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn'
-
- .avatar-holder
+.cover-block.groups-cover-block
+ .container-fluid.container-limited
= link_to group_icon(@group), target: '_blank' do
- = image_tag group_icon(@group), class: "avatar group-avatar s90"
- .cover-title
- %h1
- = @group.name
- %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
- = visibility_level_icon(@group.visibility_level, fw: false)
-
- .cover-desc.username
- @#{@group.path}
-
- - if @group.description.present?
- .cover-desc.description
- = markdown(@group.description, pipeline: :description)
+ = image_tag group_icon(@group), class: "avatar group-avatar s70"
+ .group-info
+ .cover-title
+ %h1
+ @#{@group.path}
+ %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, fw: false)
+
+ - if @group.description.present?
+ .cover-desc.description
+ = markdown(@group.description, pipeline: :description)
%div{ class: container_class }
.top-area
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index da3c3711cdd..70e88da7aae 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -21,7 +21,7 @@
%tr
%td.shortcut
.key ?
- %td Show this dialog
+ %td Show/hide this dialog
%tr
%td.shortcut
- if browser.mac?
@@ -169,6 +169,10 @@
%td.shortcut
.key t
%td Go to finding file
+ %tr
+ %td.shortcut
+ .key i
+ %td New issue
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
@@ -241,6 +245,10 @@
%td.shortcut
.key e
%td Edit issue
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
@@ -261,3 +269,7 @@
%td.shortcut
.key e
%td Edit merge request
+ %tr
+ %td.shortcut
+ .key l
+ %td Change Label
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d084559abc3..d676bc28c89 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -48,14 +48,14 @@
.lead
Gray content block with side padding using
- %code .gray-content-block
+ %code .row-content-block
.example
- .gray-content-block
+ .row-content-block
%h4 Normal block inside content
= lorem
- .gray-content-block.second-block
+ .row-content-block.second-block
%h4 Second block
= lorem
@@ -345,11 +345,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.example
%div
@@ -372,11 +372,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.dropdown-page-two
.dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index d8af0295b2d..dfebf7768d9 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -20,10 +20,10 @@
job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target")
target_field.empty()
- target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>')
+ target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
$("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else
:plain
job = $("tr#repo_#{@repo_id}")
- job.find(".import-actions").html("<i class='fa fa-exclamation-circle'> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}</i>")
+ job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}")
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index aec2e836c9f..6e993e58f0d 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -10,13 +10,19 @@
%hr
%p
- if @incompatible_repos.any?
- = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all compatible projects
+ = icon("spinner spin", class: "loading-icon")
- else
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Bitbucket
@@ -28,7 +34,7 @@
%td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -47,7 +53,9 @@
%td.import-target
= "#{repo["owner"]}/#{repo["slug"]}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
%td
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 6ee16c8be4b..d3d3c595c17 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -13,10 +13,15 @@
how FogBugz email addresses and usernames are imported into GitLab.
%hr
%p
- = button_tag 'Import all projects', class: 'btn btn-success js-import-all'
+ = button_tag class: 'btn btn-import btn-success js-import-all' do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From FogBugz
@@ -28,7 +33,7 @@
%td
= project.import_source
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -47,7 +52,9 @@
%td.import-target
= "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 1416ee5bd5a..5b7f11440c1 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From GitHub
@@ -21,9 +26,9 @@
- @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td
- = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ = github_project_link(project.import_source)
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -38,11 +43,13 @@
- @repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
- = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ = github_project_link(repo.full_name)
%td.import-target
= repo.full_name
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index 911a55eb85d..e3a356b5379 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From GitLab.com
@@ -23,7 +28,7 @@
%td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -42,7 +47,9 @@
%td.import-target
= repo["path_with_namespace"]
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 6b0fa1edf8c..267eee4f262 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -8,10 +8,15 @@
Select projects you want to import.
%hr
%p
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Gitorious.org
@@ -23,7 +28,7 @@
%td
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -42,7 +47,9 @@
%td.import-target
= repo.full_name
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
:javascript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 175ef6921cd..5ada6b174eb 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -14,12 +14,19 @@
%hr
%p
- if @incompatible_repos.any?
- = button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all compatible projects
+ = icon("spinner spin", class: "loading-icon")
- else
- = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+ = button_tag class: "btn btn-import btn-success js-import-all" do
+ Import all projects
+ = icon("spinner spin", class: "loading-icon")
-.table-holder
+.table-responsive
%table.table.import-jobs
+ %colgroup.import-jobs-from-col
+ %colgroup.import-jobs-to-col
+ %colgroup.import-jobs-status-col
%thead
%tr
%th From Google Code
@@ -31,7 +38,7 @@
%td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
%td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span
@@ -50,7 +57,9 @@
%td.import-target
= "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ = button_tag class: "btn btn-import js-add-to-import" do
+ Import
+ = icon("spinner spin", class: "loading-icon")
- @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo.id}"}
%td
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c799e9c588d..3c3bc41bf0e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -22,7 +22,11 @@
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
- .content-wrapper
+ - if defined?(nav) && nav
+ .layout-nav
+ .container-fluid
+ = render "layouts/nav/#{nav}"
+ .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) }
= render "layouts/flash"
= yield :flash_message
%div{ class: (container_class unless @no_container) }
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index babfb032236..e4d1c773d03 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,6 @@
= yield :scripts_body_top
= render "layouts/header/default", title: header_title
- = render 'layouts/page', sidebar: sidebar
+ = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
new file mode 100644
index 00000000000..7c061dd531f
--- /dev/null
+++ b/app/views/layouts/devise_empty.html.haml
@@ -0,0 +1,17 @@
+!!! 5
+%html{ lang: "en"}
+ = render "layouts/head"
+ %body.ui_charcoal.login-page.application.navless
+ = render "layouts/header/empty"
+ = render "layouts/broadcast"
+ .container.navless-container
+ .content
+ = render "layouts/flash"
+ = yield
+
+ %hr
+ .container
+ .footer-links
+ = link_to "Explore", explore_root_path
+ = link_to "Help", help_path
+ = link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 2e483b7148d..f06acc98ca1 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,6 +1,6 @@
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
-- sidebar "group" unless sidebar
+- nav "group"
= render template: "layouts/application"
diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml
index a1a1fc2f858..66b115e36de 100644
--- a/app/views/layouts/group_settings.html.haml
+++ b/app/views/layouts/group_settings.html.haml
@@ -1,5 +1,4 @@
- page_title "Settings"
-- header_title group_title(@group, "Settings", edit_group_path(@group))
-- sidebar "group_settings"
+- nav "group"
= render template: "layouts/group"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3beb8ff7c0d..172579dafda 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,9 +1,12 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
- %button.navbar-toggle{type: 'button'}
+ %button.side-nav-toggle{type: 'button'}
%span.sr-only Toggle navigation
= icon('bars')
+ %button.navbar-toggle{type: 'button'}
+ %span.sr-only Toggle navigation
+ = icon('angle-left')
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -15,7 +18,7 @@
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
@@ -23,6 +26,7 @@
= icon('wrench fw')
%li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('bell fw')
%span.badge.todos-pending-count
= todos_pending_count
- if current_user.can_create_project?
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5cef652da14..fad4224e945 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -15,12 +15,12 @@
= icon('dashboard fw')
%span
Activity
- = nav_link(controller: :groups) do
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw')
%span
Groups
- = nav_link(controller: :milestones) do
+ = nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
%span
@@ -48,8 +48,7 @@
%span
Help
- %li.separate-item
- = nav_link(controller: :profile) do
+ = nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index f08c5edf99c..3b40006a0cc 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -4,7 +4,7 @@
= icon('bookmark fw')
%span
Projects
- = nav_link(controller: :groups) do
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
%span
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 55940741dc0..3438005863a 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,12 +1,6 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
+= render 'layouts/nav/group_settings'
+%ul.nav-links
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
= icon('group fw')
@@ -28,22 +22,16 @@
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.count= number_with_delimiter(issues.count)
+ %span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.count= number_with_delimiter(merge_requests.count)
+ %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
%span
Members
- - if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
- = link_to edit_group_path(@group), title: 'Settings' do
- = icon ('cogs fw')
- %span
- Settings
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index 56a92fe9103..e391ec7f2b7 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,20 +1,20 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to group_path(@group), title: 'Go to group', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to group
-
- %li.separate-item
-
- %ul.sidebar-subnav
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'Group Settings' do
- = icon ('pencil-square-o fw')
- %span
- Group Settings
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
- = icon('folder fw')
- %span
- Projects
+- if current_user
+ - if access = @group.users.find_by(id: current_user.id)
+ .controls
+ %span.dropdown.group-settings-dropdown
+ %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - if can?(current_user, :admin_group, @group)
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects' do
+ Projects
+ %li.divider
+ %li
+ = link_to edit_group_path(@group) do
+ Edit Group
+ %li
+ = link_to leave_group_group_members_path(@group),
+ data: { confirm: leave_group_message(@group.name) }, method: :delete, title: 'Leave group' do
+ Leave Group
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 3b9d31a6fc5..d730840d63a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,17 +1,9 @@
-%ul.nav.nav-sidebar
- = nav_link do
- = link_to root_path, title: 'Go to dashboard', class: 'back-link' do
- = icon('caret-square-o-left fw')
- %span
- Go to dashboard
-
- %li.separate-item
-
+%ul.nav-links
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
- Profile Settings
+ Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
@@ -27,7 +19,6 @@
= icon('envelope-o fw')
%span
Emails
- %span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
@@ -45,7 +36,6 @@
= icon('key fw')
%span
SSH Keys
- %span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 86b46e8c75e..479bde33719 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -77,7 +77,7 @@
Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- - if project_nav_tab? :settings
+ - if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
@@ -124,3 +124,8 @@
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
+
+ -# Shortcut to create a new issue
+ %li.hidden
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ Create a new issue
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index dfa6cc5702e..b77d3402a2e 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,6 @@
- page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title
-- sidebar "profile"
+- sidebar "dashboard"
+- nav "profile"
= render template: "layouts/application"
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 574e8bfef24..81c7c88fc96 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
+ = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index 59db86b08bc..b435067d5a6 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
+= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index c9bf04f514e..41a320d6bd8 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
+ = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index b96dd0fd8ab..7a5074a1dc3 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
+= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index 6762fae7f64..fbe506d4f4d 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request ##{@merge_request.iid} was merged"
+ = "Merge Request #{@merge_request.to_reference} was merged"
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 34dbc60e19b..bfbae01094f 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request ##{@merge_request.iid} was merged"
+= "Merge Request #{@merge_request.to_reference} was merged"
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index bdcca6e4ab7..d4aad8d1862 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-New Merge Request #<%= @merge_request.iid %>
+New Merge Request <%= @merge_request.to_reference %>
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
index 1d1411992a6..8cdab63829e 100644
--- a/app/views/notify/note_merge_request_email.text.erb
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -1,4 +1,4 @@
-New comment for Merge Request <%= @merge_request.iid %>
+New comment for Merge Request <%= @merge_request.to_reference %>
<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
diff --git a/app/views/notify/note_snippet_email.html.haml b/app/views/notify/note_snippet_email.html.haml
new file mode 100644
index 00000000000..2fa2f784661
--- /dev/null
+++ b/app/views/notify/note_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_snippet_email.text.erb b/app/views/notify/note_snippet_email.text.erb
new file mode 100644
index 00000000000..4d5a406f4b0
--- /dev/null
+++ b/app/views/notify/note_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(namespace_project_snippet_url(@snippet.project.namespace, @snippet.project, @snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 6efd119f260..afd3d79321f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Account"
-- header_title page_title, profile_account_path
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index f630c03e5f6..9c404b6935f 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,4 @@
- page_title "Audit Log"
-- header_title page_title, audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 3f328f96cea..6f7fefdb46d 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,5 +1,4 @@
- page_title "Emails"
-- header_title page_title, profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
@@ -46,4 +45,4 @@
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-warning prepend-left-10'
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 4dbaa662b66..3276db6692c 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,6 +1,6 @@
%li.key-list-item
.pull-left.append-right-10
- = icon 'key', class: "key-icon hidden-xs"
+ = icon 'key', class: "settings-list-icon hidden-xs"
.key-list-item-info
= link_to path_to_key(key, is_admin), class: "title" do
= key.title
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index 296cafa6e31..e78763bdcb2 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -4,7 +4,7 @@
%ul.well-list
= render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
- else
- %p.profile-settings-message.text-center
+ %p.settings-message.text-center
- if is_admin
There are no SSH keys associated with this account.
- else
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e0f8c9a5733..6a067a03535 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,5 +1,4 @@
- page_title "SSH Keys"
-- header_title page_title, profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index a2a505c082b..7696f112bb3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Notifications"
-- header_title page_title, profile_notifications_path
%div
- if @user.errors.any?
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 5ac8a8b9d09..243428b690e 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,4 @@
- page_title "Password"
-- header_title page_title, edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index f80211669fb..bfe53be6854 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,5 +1,4 @@
- page_title 'Preferences'
-- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index f59d27f7ed0..eef50d887c7 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -8,11 +8,11 @@
%p
- if @user.avatar?
You can change your avatar here
- - if Gitlab.config.gravatar.enabled
+ - if gravatar_enabled?
or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
- else
You can upload an avatar here
- - if Gitlab.config.gravatar.enabled
+ - if gravatar_enabled?
or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
.col-lg-9
.clearfix.avatar-image.append-bottom-default
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 961b61d2e76..48b0dd6b121 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -9,4 +9,7 @@
= spinner
:javascript
- new Activities();
+ var activity = new Activities();
+ $(document).on('page:restore', function (event) {
+ activity.reloadActivities()
+ })
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index b6074373e2b..0de019983ca 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -55,6 +55,9 @@
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
+ %li
+ tap --coverage-report=text-summary (Node.js) -
+ %code ^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
index 386d72e7787..66c30283c7a 100644
--- a/app/views/projects/_last_commit.html.haml
+++ b/app/views/projects/_last_commit.html.haml
@@ -1,9 +1,8 @@
.project-last-commit
- - ci_commit = project.ci_commit(commit.sha)
- - if ci_commit
- = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
- = ci_status_icon(ci_commit)
- = ci_status_label(ci_commit)
+ - if commit.status
+ = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
+ = ci_icon_for_status(commit.status)
+ = ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f0a3e416db7..7c2b8d01508 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,7 +1,7 @@
- if event = last_push_event
- if show_last_push_widget?(event)
- .gray-content-block.top-block.clear-block.hidden-xs
+ .row-content-block.top-block.clear-block.hidden-xs
.event-last-push
.event-last-push-text
%span You pushed to
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7a78d61a611..8de44a6c914 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,6 +1,6 @@
.md-area
.md-header
- %ul.nav-links
+ %ul.nav-links.clearfix
%li.active
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index d1191928d4f..369a847e7d4 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -7,9 +7,9 @@
= cache(readme_cache_key) do
= render_readme(readme)
- else
- .gray-content-block.second-block.center
+ .row-content-block.second-block.center
%h3.page-title
- This project does not have README yet
+ This project does not have a README yet
- if can?(current_user, :push_code, @project)
%p
A
@@ -18,5 +18,5 @@
distributed with computer software, forming part of its documentation.
%p
We recommend you to
- = link_to "add README", new_readme_path, class: 'underlined-link'
+ = link_to "add a README", new_readme_path, class: 'underlined-link'
file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 84034c8bf16..49f95ff37db 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,7 +1,7 @@
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
= render 'projects/builds/header_title'
-.top-block.gray-content-block.clearfix
+.top-block.row-content-block.clearfix
.pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default download' do
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index f8b6fa253c4..fefa652a3da 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -13,7 +13,11 @@
required: true, class: 'form-control new-file-name'
.pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
+ .license-selector.js-license-selector.hide
+ = select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name}
+
+ .encoding-selector
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-content.code
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index d09cd73558c..b1769759dce 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,10 +1,19 @@
-- blob.load_all_data!(@repository)
-- if markup?(blob.name)
- .file-content.wiki
- = render_markup(blob.name, blob.data)
+- if blob.only_display_raw?
+ .file-content.code
+ .nothing-here-block
+ File too large, you can
+ = succeed '.' do
+ = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank'
+
- else
- - unless blob.empty?
- = render 'shared/file_highlight', blob: blob
+ - blob.load_all_data!(@repository)
+
+ - if markup?(blob.name)
+ .file-content.wiki
+ = render_markup(blob.name, blob.data)
- else
- .file-content.code
- .nothing-here-block Empty file
+ - if blob.empty?
+ .file-content.code
+ .nothing-here-block Empty file
+ - else
+ = render 'shared/file_highlight', blob: blob
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 38e62c81fed..5926d181ba3 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,17 +1,17 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
- %tr.line_holder{ id: @form.since }
+ %tr.line_holder
= render "projects/diffs/match_line", { line: @match_line,
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
- line_old = line_new - @form.offset
- %tr.line_holder
+ %tr.line_holder{ id: line_old }
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "#"
+ = link_to raw(line_old), "##{line_old}"
%td.new_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_new) , "#"
+ = link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1dd2b5c0af7..0459699432e 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -14,5 +14,5 @@
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
:javascript
- blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
+ blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
new NewCommitForm($('.js-new-blob-form'))
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 88266e21230..ac7790421a4 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Branches"
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.pull-right
- if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index aa85f495e39..2e8015d119b 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -9,6 +9,7 @@
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
+
%li{class: ('active' if @scope == 'running')}
= link_to project_builds_path(@project, scope: :running) do
Running
@@ -34,7 +35,7 @@
= icon('wrench')
%span CI Lint
-.gray-content-block
+.row-content-block
#{(@scope || 'running').capitalize} builds from this project
%ul.content-list
@@ -52,12 +53,13 @@
%th Ref
%th Stage
%th Name
+ %th Tags
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
%th Coverage
%th
- = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
+ = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index b02aee3db21..c0f7a7686f0 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
.build-page
- .gray-content-block.top-block
+ .row-content-block.top-block
Build ##{@build.id} for commit
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
@@ -10,10 +10,10 @@
- merge_request = @build.merge_request
- if merge_request
via
- = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
+ = link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace
- - builds = @build.commit.matrix_builds(@build)
+ - builds = @build.commit.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
@@ -34,7 +34,7 @@
%i.fa.fa-warning
This build was retried.
- .gray-content-block.middle-block
+ .row-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
@@ -110,7 +110,7 @@
= icon('folder-open')
Browse
- .build-widget
+ .build-widget.build-controls
%h4.title
Build ##{@build.id}
- if can?(current_user, :update_build, @project)
@@ -127,6 +127,9 @@
data: { confirm: 'Are you sure you want to erase this build?' } do
= icon('eraser')
Erase
+ - if @build.has_trace?
+ = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build),
+ class: 'btn btn-sm btn-success', target: '_blank'
.clearfix
- if @build.duration
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 2cf9115e4dd..8e95f040273 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -19,11 +19,12 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
- %td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- - else
- .light none
+ - if defined?(ref) && ref
+ %td
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
@@ -39,6 +40,7 @@
%td
= build.name
+ %td
.label-container
- if build.tags.any?
- build.tags.each do |tag|
@@ -48,6 +50,8 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
%td.duration
- if build.duration
@@ -65,12 +69,12 @@
%td
.pull-right
- if can?(current_user, :read_build, build) && build.artifacts?
- = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts' do
+ = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
%i.fa.fa-download
- if can?(current_user, :update_build, build)
- if build.active?
- = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel' do
+ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry' do
- %i.fa.fa-repeat
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ %i.fa.fa-refresh
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 003b7c18d0e..5c9a319edeb 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,67 +1,2 @@
-.gray-content-block.middle-block
- .pull-right
- - if can?(current_user, :update_build, @ci_commit.project)
- - if @ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
-
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
- .oneline
- = pluralize @statuses.count(:id), "build"
- - if defined?(link_to_commit) && link_to_commit
- for commit
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
- - if @ci_commit.duration > 0
- in
- = time_interval_in_words @ci_commit.duration
-
-- if @ci_commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @ci_commit.yaml_errors.split(",").each do |error|
- %li= error
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
-
-- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-.table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- - @ci_commit.refs.each do |ref|
- - builds = @ci_commit.statuses.for_ref(ref).latest.ordered
- = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true
-
-- if @ci_commit.retried.any?
- .gray-content-block.second-block
- Retried builds
-
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Ref
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
+- @ci_commits.each do |ci_commit|
+ = render "ci_commit", ci_commit: ci_commit
diff --git a/app/views/projects/commit/_revert.html.haml b/app/views/projects/commit/_change.html.haml
index 52ca3ed5b14..44ef1fdbbe3 100644
--- a/app/views/projects/commit/_revert.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -1,13 +1,21 @@
-#modal-revert-commit.modal
+- case type.to_s
+- when 'revert'
+ - label = 'Revert'
+ - target_label = 'Revert in branch'
+- when 'cherry-pick'
+ - label = 'Cherry-pick'
+ - target_label = 'Pick into branch'
+
+.modal{id: "modal-#{type}-commit"}
.modal-dialog
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title== Revert this #{revert_commit_type(commit)}
+ %h3.page-title== #{label} this #{commit.change_type_title}
.modal-body
- = form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
+ = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do
.form-group.branch
- = label_tag 'target_branch', 'Revert in branch', class: 'control-label'
+ = label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10
= select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch"
- if can?(current_user, :push_code, @project)
@@ -20,7 +28,7 @@
- else
= hidden_field_tag 'create_merge_request', 1
.form-actions
- = submit_tag "Revert", class: 'btn btn-create'
+ = submit_tag label, class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
@@ -28,4 +36,4 @@
= commit_in_fork_help
:javascript
- new NewCommitForm($('.js-create-dir-form'))
+ new NewCommitForm($('.js-#{type}-form'))
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
new file mode 100644
index 00000000000..e849aefb188
--- /dev/null
+++ b/app/views/projects/commit/_ci_commit.html.haml
@@ -0,0 +1,71 @@
+.row-content-block.build-content.middle-block
+ .pull-right
+ - if can?(current_user, :update_build, @project)
+ - if ci_commit.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline
+ = pluralize ci_commit.statuses.count(:id), "build"
+ - if ci_commit.ref
+ for
+ %span.label.label-info
+ = ci_commit.ref
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
+ - if ci_commit.duration
+ in
+ = time_interval_in_words ci_commit.duration
+
+- if ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+ You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+
+- if @project.builds_enabled? && !ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Stage
+ %th Name
+ %th Tags
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ - builds = ci_commit.statuses.latest.ordered
+ = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
+
+- if ci_commit.retried.any?
+ .row-content-block.second-block
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Tags
+ %th Duration
+ %th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
+ %th
+ = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 71995fcc487..01163e526b2 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,4 +1,4 @@
-.pull-right
+.pull-right.commit-action-buttons
%div
- if @notes_count > 0
%span.btn.disabled.btn-grouped
@@ -18,13 +18,16 @@
Browse Files
- unless @commit.has_been_reverted?(current_user)
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
+ = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
%div
%p
- %span.light Commit
- = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
- = clipboard_button(clipboard_text: @commit.id)
.commit-info-row
+ - if @commit.status
+ = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
+ = ci_icon_for_status(@commit.status)
+ build:
+ = ci_label_for_status(@commit.status)
%span.light Authored by
%strong
= commit_author_link(@commit, avatar: true, size: 24)
@@ -38,19 +41,15 @@
#{time_ago_with_tooltip(@commit.committed_date)}
.commit-info-row
+ %span.light Commit
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = clipboard_button(clipboard_text: @commit.id)
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
-- if @ci_commit
- .pull-right
- = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
- = ci_status_icon(@ci_commit)
- build:
- = ci_status_label(@ci_commit)
-
-.commit-info-row.branches
- %i.fa.fa-spinner.fa-spin
+ %span.commit-info.branches
+ %i.fa.fa-spinner.fa-spin
.commit-box.content-block
%h3.commit-title
@@ -60,4 +59,4 @@
= preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:javascript
- $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
+ $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
index 82aac1fbd15..2b0c9a4b4de 100644
--- a/app/views/projects/commit/branches.html.haml
+++ b/app/views/projects/commit/branches.html.haml
@@ -3,7 +3,6 @@
- branch = commit_default_branch(@project, @branches)
= link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do
%span.label.label-gray
- %i.fa.fa-code-fork
= branch
- if @branches.any? || @tags.any?
= link_to("#", class: "js-details-expand") do
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 21e186120c3..e5e3d696035 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -5,7 +5,7 @@
.prepend-top-default
= render "commit_box"
-- if @ci_commit
+- if @commit.status
= render "ci_menu"
- else
%div.block-connector
@@ -13,4 +13,5 @@
diff_refs: @diff_refs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- = render "projects/commit/revert", commit: @commit, title: @commit.title
+ - %w(revert cherry-pick).each do |type|
+ = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 34f27f1e793..c7d8c9a0d15 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -4,9 +4,8 @@
- notes = commit.notes
- note_count = notes.user.count
-- ci_commit = project.ci_commit(commit.sha)
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(commit.status) if commit.status
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
@@ -17,8 +16,8 @@
%a.text-expander.js-toggle-button ...
.pull-right
- - if ci_commit
- = render_ci_status(ci_commit)
+ - if commit.status
+ = render_ci_status(commit)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
@@ -31,5 +30,5 @@
by
= commit_author_link(commit, avatar: true, size: 24)
.committed_ago
- #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
+ #{time_ago_with_tooltip(commit.committed_date)} &nbsp;
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 7a5b0d993db..d1bd76ab529 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -2,7 +2,8 @@
= nav_link(controller: [:commit, :commits]) do
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits
- %span.badge= number_with_delimiter(@repository.commit_count)
+ %span.badge
+ = number_with_delimiter(@repository.commit_count)
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index c52cf25d40a..088eaa28013 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -6,7 +6,7 @@
= render "head"
-.gray-content-block.second-block
+.row-content-block.second-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
@@ -39,4 +39,4 @@
= spinner
:javascript
- CommitsList.init("#{@ref}", #{@limit});
+ CommitsList.init(#{@limit});
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 02be5a2d07f..5e188dd0f3c 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
Compare branches, tags or commit ranges.
%br
Fill input field with commit id like
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index da731f28bb6..62525168239 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -3,7 +3,7 @@
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
= render "form"
- if @commits.present?
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index 8d66bae8cdf..450aaeb367c 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -1,32 +1,27 @@
%li
- .pull-right
+ .pull-left.append-right-10.hidden-xs
+ = icon "key", class: "key-icon"
+ .deploy-key-content.key-list-item-info
+ %strong.title
+ = deploy_key.title
+ .description
+ = deploy_key.fingerprint
+ .deploy-key-content.prepend-left-default.deploy-key-projects
+ - deploy_key.projects.each do |project|
+ - if can?(current_user, :read_project, project)
+ = link_to namespace_project_path(project.namespace, project), class: "label deploy-project-label" do
+ = project.name_with_namespace
+ .deploy-key-content
+ %span.key-created-at
+ created #{time_ago_with_tooltip(deploy_key.created_at)}
+ .visible-xs-block.visible-sm-block
- 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
- = icon('plus')
+ = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do
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"
+ = link_to 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-warning btn-sm prepend-left-10" do
+ Remove
- else
- = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- = icon('power-off')
+ = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-warning btn-sm prepend-left-10", method: :put do
Disable
-
- = icon('key')
- %strong= deploy_key.title
- %br
- %code.key-fingerprint= deploy_key.fingerprint
-
- %p.light.prepend-top-10
- - if deploy_key.public?
- %span.label.label-info.deploy-project-label
- Public deploy key
-
- - deploy_key.projects.each do |project|
- - if can?(current_user, :read_project, project)
- %span.label.label-gray.deploy-project-label
- = link_to namespace_project_path(project.namespace, project) do
- = project.name_with_namespace
-
- %small.pull-right
- Created #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index f6565f85836..894c36a96df 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,18 +1,13 @@
-%div
- = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
- = form_errors(@key)
-
- .form-group
- = f.label :title, class: "control-label"
- .col-sm-10= f.text_field :title, class: 'form-control', autofocus: true, required: true
- .form-group
- = f.label :key, class: "control-label"
- .col-sm-10
- %p.light
- Paste a machine public key here. Read more about how to generate it
- = link_to "here", help_page_path("ssh", "README")
- = f.text_area :key, class: "form-control thin_area", rows: 5, required: true
-
- .form-actions
- = f.submit 'Create Deploy Key', class: "btn-create btn"
- = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
+= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
+ = form_errors(@key)
+ .form-group
+ = f.label :title, class: "label-light"
+ = f.text_field :title, class: 'form-control', autofocus: true, required: true
+ .form-group
+ = f.label :key, class: "label-light"
+ = f.text_area :key, class: "form-control", rows: 5, required: true
+ .form-group
+ %p.light.append-bottom-0
+ Paste a machine public key here. Read more about how to generate it
+ = link_to "here", help_page_path("ssh", "README")
+ = f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 8e24c778b7c..e230834e8ba 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -1,43 +1,36 @@
- page_title "Deploy Keys"
-%h3.page-title
- Deploy keys allow read-only access to the repository
-
- = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do
- %i.fa.fa-plus
- New Deploy Key
-
-%p.light
- Deploy keys can be used for CI, staging or production servers.
- You can create a deploy key or add an existing one
-
-%hr.clearfix
-
-.row
- .col-md-6.enabled-keys
- %h5
- %strong.cgreen Enabled deploy keys
- for this project
- %ul.bordered-list
- = render @enabled_keys
- - if @enabled_keys.blank?
- .light-well
- .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one
- .col-md-6.available-keys
- - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown.
- - if @available_project_keys.any? || @available_public_keys.blank?
- %h5
- %strong Deploy keys
- from projects you have access to
- %ul.bordered-list
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
+ .col-lg-9
+ %h5.prepend-top-0
+ Create a new deploy key for this project
+ = render "form"
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
+ %h5.prepend-top-0
+ Enabled deploy keys for this project (#{@enabled_keys.size})
+ - if @enabled_keys.any?
+ %ul.well-list
+ = render @enabled_keys
+ - else
+ .profile-settings-message.text-center
+ No deploy keys found. Create one with the form above or add existing one below.
+ %h5.prepend-top-default
+ Deploy keys from projects you have access to (#{@available_project_keys.size})
+ - if @available_project_keys.any?
+ %ul.well-list
= render @available_project_keys
- - if @available_project_keys.blank?
- .light-well
- .nothing-here-block Deploy keys from projects you have access to will be displayed here
-
+ - else
+ .profile-settings-message.text-center
+ No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- if @available_public_keys.any?
- %h5
- %strong Public deploy keys
- available to any project
- %ul.bordered-list
+ %h5.prepend-top-default
+ Public deploy keys available to any project (#{@available_public_keys.size})
+ %ul.well-list
= render @available_public_keys
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index eaab99973a4..d9c4b410d32 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,3 +1,4 @@
+- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- if diff_view == 'parallel'
- fluid_layout true
@@ -5,6 +6,11 @@
.content-block.oneline-block.files-changed
.inline-parallel-buttons
+ - if show_whitespace_toggle
+ - if current_controller?(:commit)
+ = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
+ - elsif current_controller?(:merge_requests)
+ = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs')
.btn-group
= inline_diff_btn
= parallel_diff_btn
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 83a8d7ae9bf..0f04fc5d33c 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -40,19 +40,19 @@
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
- -# Skipp all non non-supported blobs
+ - # Skip all non non-supported blobs
- return unless blob.respond_to?('text?')
- if diff_file.too_large?
- .nothing-here-block
- This diff could not be displayed because it is too large.
- - else
- - if blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
+ .nothing-here-block This diff was suppressed by a .gitattributes entry.
+ - elsif blob_text_viewable?(blob)
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
- .nothing-here-block No preview for this file type
+ = render "projects/diffs/text_file", diff_file: diff_file, index: i
+ - elsif blob.image?
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 6ad7b05155a..1a2e59752fe 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,15 +7,17 @@
= render "home_panel"
-.gray-content-block.second-block.center
+.row-content-block.second-block.center
%h3.page-title
The repository for this project is empty
- if can?(current_user, :push_code, @project)
%p
If you already have files you can push them using command line instructions below.
%p
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
+ Otherwise you can start with adding a
+ = link_to "README", new_readme_path, class: 'underlined-link'
+ or a
+ = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
file to this project.
- if can?(current_user, :push_code, @project)
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index c15386b4883..f21c864e35c 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
-
- %td
- - if generic_commit_status.ref
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- - else
- .light none
+
+ - if defined?(ref) && ref
+ %td
+ - if generic_commit_status.ref
+ = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ - else
+ .light none
- if defined?(runner) && runner
%td
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index 6fa77cc10c6..9f05be9982b 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -1,7 +1,7 @@
- page_title "Continuous Integration", "Graphs"
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.oneline
A collection of graphs for Continuous Integration
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index fc465ab273b..da9f648cc9c 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index a7fab5b6d72..ebecab1dbfc 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.oneline
Programming languages used in this repository
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 882e7d6b6ee..ad4a932d391 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'head'
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml
index 13f5fc141fa..2b904544f28 100644
--- a/app/views/projects/group_links/index.html.haml
+++ b/app/views/projects/group_links/index.html.haml
@@ -1,41 +1,44 @@
- page_title "Groups"
-%h3.page_title Share project with other groups
-%p.light
- Projects can be stored in only one group at once. However you can share a project with other groups here.
-%hr
-- if @group_links.present?
- .enabled-groups.panel.panel-default
- .panel-heading
- Already shared with
- %ul.well-list
- - @group_links.each do |group_link|
- - group = group_link.group
- %li
- .pull-right
- = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: 'btn btn-sm' do
- %i.icon-remove
- disable sharing
- = link_to group do
- %strong
- %i.icon-folder-open
- = group.name
- %br
- .light up to #{group_link.human_access}
-
-
-.available-groups
- %h4
- Can be shared with
- %div
- = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post, class: 'form-horizontal' do
+.row.prepend-top-default
+ .col-lg-3.settings-sidebar
+ %h4.prepend-top-0
+ Share project with other groups
+ %p
+ Projects can be stored in only one group at once. However you can share a project with other groups here.
+ .col-lg-9
+ %h5.prepend-top-0
+ Set a group to share
+ = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post do
.form-group
- = label_tag :link_group_id, 'Group', class: 'control-label'
- .col-sm-10
- = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
+ = label_tag :link_group_id, "Group", class: "label-light"
+ = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path))
.form-group
- = label_tag :link_group_access, 'Max access level', class: 'control-label'
- .col-sm-10
- = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control"
- .form-actions
- = submit_tag "Share", class: "btn btn-create"
-
+ = label_tag :link_group_access, "Max access level", class: "label-light"
+ .select-wrapper
+ = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
+ %span.caret
+ = submit_tag "Share", class: "btn btn-create"
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.enabled-groups
+ %h5.prepend-top-0
+ Groups you share with (#{@group_links.size})
+ - if @group_links.present?
+ %ul.well-list
+ - @group_links.each do |group_link|
+ - group = group_link.group
+ %li
+ .pull-left.append-right-10.hidden-xs
+ = icon("folder-open-o", class: "settings-list-icon")
+ .pull-left
+ = link_to group do
+ = group.name
+ %br
+ up to #{group_link.human_access}
+ .pull-right
+ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), method: :delete, class: "btn btn-transparent" do
+ %span.sr-only disable sharing
+ = icon("trash")
+ - else
+ .settings-message.text-center
+ There are no groups with access to your project, add one in the form above
diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml
new file mode 100644
index 00000000000..62eba5888a4
--- /dev/null
+++ b/app/views/projects/hooks/_project_hook.html.haml
@@ -0,0 +1,15 @@
+%li
+ .row
+ .col-md-8.col-lg-7
+ %strong.light-header= hook.url
+ %div
+ - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
+ - if hook.send(trigger)
+ %span.label.label-gray.deploy-project-label= trigger.titleize
+ .col-md-4.col-lg-5.text-right-lg.prepend-top-5
+ %span.append-right-10.inline
+ SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+ = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
+ = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+ %span.sr-only Remove
+ = icon('trash')
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index e39224d86c6..36c1d69f060 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,89 +1,84 @@
- page_title "Webhooks"
-%h3.page-title
- Webhooks
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be
+ used for binding events when something is happening within the project.
+ .col-lg-9.append-bottom-default
+ %h5.prepend-top-0
+ Add new webhook
+ = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project) do |f|
+ = form_errors(@hook)
-%p.light
- #{link_to "Webhooks ", help_page_path("web_hooks", "web_hooks"), class: "vlink"} can be
- used for binding events when something is happening within the project.
-
-%hr.clearfix
-
-= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- = form_errors(@hook)
-
- .form-group
- = f.label :url, "URL", class: 'control-label'
- .col-sm-10
- = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
- .form-group
- = f.label :url, "Trigger", class: 'control-label'
- .col-sm-10.prepend-top-10
- %div
- = f.check_box :push_events, class: 'pull-left'
- .prepend-left-20
- = f.label :push_events, class: 'list-label' do
- %strong Push events
- %p.light
- This url will be triggered by a push to the repository
- %div
- = f.check_box :tag_push_events, class: 'pull-left'
- .prepend-left-20
- = f.label :tag_push_events, class: 'list-label' do
- %strong Tag push events
- %p.light
- This url will be triggered when a new tag is pushed to the repository
- %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
- %strong Issues events
- %p.light
- This url will be triggered when an issue is created/updated/merged
- %div
- = f.check_box :merge_requests_events, class: 'pull-left'
- .prepend-left-20
- = f.label :merge_requests_events, class: 'list-label' do
- %strong Merge Request events
- %p.light
- This url will be triggered when a merge request is created/updated/merged
- %div
- = f.check_box :build_events, class: 'pull-left'
- .prepend-left-20
- = f.label :build_events, class: 'list-label' do
- %strong Build events
- %p.light
- This url will be triggered when the build status changes
- .form-group
- = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
- .col-sm-10
- .checkbox
- = f.label :enable_ssl_verification do
- = f.check_box :enable_ssl_verification
- %strong Enable SSL verification
- .form-actions
- = f.submit "Add Webhook", class: "btn btn-create"
-
--if @hooks.any?
- .panel.panel-default
- .panel-heading
+ .form-group
+ = f.label :url, "URL", class: "label-light"
+ = f.text_field :url, class: "form-control", placeholder: "http://example.com/trigger-ci.json"
+ .form-group
+ = f.label :token, "Secret Token", class: 'label-light'
+ = f.text_field :token, class: "form-control", placeholder: ''
+ %p.help-block
+ Use this token to validate received payloads
+ .form-group
+ = f.label :url, "Trigger", class: "label-light"
+ %div
+ = f.check_box :push_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :push_events, class: "label-light append-bottom-0" do
+ Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ %div
+ = f.check_box :tag_push_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :tag_push_events, class: "label-light append-bottom-0" do
+ Tag push events
+ %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: "label-light append-bottom-0" do
+ 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: "label-light append-bottom-0" do
+ Issues events
+ %p.light
+ This url will be triggered when an issue is created/updated/merged
+ %div
+ = f.check_box :merge_requests_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :merge_requests_events, class: "label-light append-bottom-0" do
+ Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created/updated/merged
+ %div
+ = f.check_box :build_events, class: "pull-left"
+ .prepend-left-20
+ = f.label :build_events, class: "label-light append-bottom-0" do
+ Build events
+ %p.light
+ This url will be triggered when the build status changes
+ .form-group
+ = f.label :enable_ssl_verification, "SSL verification", class: "label-light"
+ %div
+ = f.check_box :enable_ssl_verification, class: "pull-left"
+ .prepend-left-20
+ = f.label :enable_ssl_verification, class: "label-light append-bottom-0" do
+ Enable SSL verification
+ = f.submit "Add Webhook", class: "btn btn-create"
+ %hr
+ %h5.prepend-top-default
Webhooks (#{@hooks.count})
- %ul.well-list
- - @hooks.each do |hook|
- %li
- .pull-right
- = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- .clearfix
- %span.monospace= hook.url
- %p
- - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray= trigger.titleize
- SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
+ - if @hooks.any?
+ %ul.well-list
+ - @hooks.each do |hook|
+ = render "project_hook", hook: hook
+ - else
+ %p.profile-settings-message.text-center.append-bottom-0
+ No webhooks found, add one in the form above.
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 6027fb23360..a8a8caf7280 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -10,7 +10,7 @@
.panel-body
%pre
:preserve
- #{@project.import_error.try(:strip)}
+ #{sanitize_repo_path(@project.import_error)}
= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
= render "shared/import_form", f: f
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 4aa92d0b39e..57ad2ec1852 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -48,6 +48,11 @@
= link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
= icon('clock-o')
= issue.milestone.title
+ - if issue.due_date
+ %span{class: "#{'cred' if issue.overdue?}"}
+ &nbsp;
+ = icon('calendar')
+ = issue.due_date.to_s(:medium)
- if issue.labels.any?
&nbsp;
- issue.labels.each do |label|
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 6da8e4f33a9..469429ccf3c 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,5 +1,13 @@
-- if current_user && can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
+- if can?(current_user, :push_code, @project)
.pull-right
- = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name do
- = icon('code-fork')
- New Branch
+ #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
+ = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
+ .checking
+ %i.fa.fa-spinner.fa-spin
+ Checking branches
+ .available(style="display: none")
+ %i.fa.fa-code-fork
+ New branch
+ .unavailable(style="display: none")
+ %i.fa.fa-exclamation-triangle
+ New branch unavailable
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index b10cd03515f..bdfa0c7009e 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- sha = @project.repository.find_branch(branch).target
- - ci_commit = @project.ci_commit(sha) if sha
+ - ci_commit = @project.ci_commit(sha, branch) if sha
- if ci_commit
%span.related-branch-ci-status
= render_ci_status(ci_commit)
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index c4cdd4b3d43..6623028c02e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,82 +1,79 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
-= render "header_title"
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
+ = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs
+ Closed
+ .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
+ = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs Open
-.issue
- .detail-page-header.issuable-header
- .pull-left
- .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
- %span.hidden-xs
- Closed
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('check')
- .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
- %span.hidden-xs
- Open
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('circle-o')
-
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
- .issue-meta
+ .issuable-meta
= confidential_icon(@issue)
- %strong.identifier
- Issue ##{@issue.iid}
- %span.creator
- opened
- .editor-details
- .editor-details
- = time_ago_with_tooltip(@issue.created_at)
- by
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = issuable_meta(@issue, @project, "Issue")
- .pull-right.issue-btn-group
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
- = icon('plus')
- New issue
- - if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
+ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can?(current_user, :create_issue, @project)
+ %li
+ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ - if can?(current_user, :update_issue, @issue)
+ %li
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+ = icon('plus')
+ New issue
+ - if can?(current_user, :update_issue, @issue)
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
- .issue-details.issuable-details
- .detail-page-description.content-block
- %h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
- = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ - if @issue.description.present?
+ .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
- // This element is filled in using JavaScript.
+ #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
- #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)}
- // This element is filled in using JavaScript.
+ #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
.content-block.content-block-small
= render 'new_branch'
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .row
- %section.col-md-12
- .issuable-discussion
- = render 'projects/issues/discussion'
+ %section.issuable-discussion
+ = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
deleted file mode 100644
index 986d8c220db..00000000000
--- a/app/views/projects/issues/update.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
-$('aside.right-sidebar').effect('highlight');
-new IssuableContext();
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 2f14a91e64f..18b3f9e1549 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -42,7 +42,7 @@
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
- = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false
- if @ci_commit
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index e8cda51e759..23525318400 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,10 +1,9 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
+- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
-= render "header_title"
-
-- if params[:view] == 'parallel'
+- if diff_view == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
@@ -32,7 +31,8 @@
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
- = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
+ %span.label-branch
+ = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
@@ -86,8 +86,10 @@
= spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
-- if @merge_request.can_be_reverted?
- = render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_reverted?(current_user)
+ = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
+- if @merge_request.can_be_cherry_picked?
+ = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index fc62bb5bce9..b31ea5e5321 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,7 +1,7 @@
-- page_title "Edit", "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
%h3.page-title
- Edit Merge Request ##{@merge_request.iid}
+ Edit Merge Request #{@merge_request.to_reference}
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index fc03ee73a3d..f5bf16ef3ad 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
.merge-request
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
index 307a75d02ca..a116ffe2e15 100644
--- a/app/views/projects/merge_requests/show/_builds.html.haml
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -1 +1,2 @@
-= render "projects/commit/builds", link_to_commit: true
+= render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
+
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 ab4b1f14be5..36c275e8be1 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,35 +1,32 @@
-.detail-page-header
- .status-box{ class: status_box_class(@merge_request) }
- %span.hidden-xs
- = @merge_request.state_human_name
- %span.hidden-sm.hidden-md.hidden-lg
- = icon(@merge_request.state_icon_name)
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
- .issue-meta
- %strong.identifier
- %span.hidden-sm.hidden-md.hidden-lg
- MR
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
+ = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs
- Merge Request
- !#{@merge_request.iid}
- %span.creator
- opened
- .editor-details
- = time_ago_with_tooltip(@merge_request.created_at)
- by
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = @merge_request.state_human_name
- .issue-btn-group.pull-right
- - if can?(current_user, :update_merge_request, @merge_request)
- - if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issuable-meta
+ = issuable_meta(@merge_request, @project, "Merge Request")
+
+ - if can?(current_user, :update_merge_request, @merge_request)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ %li{ class: issue_button_visibility(@merge_request, true) }
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ %li{ class: issue_button_visibility(@merge_request, false) }
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
+ %li
+ = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit" do
= icon('pencil-square-o')
Edit
- - if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
deleted file mode 100644
index 9cce5660e1c..00000000000
--- a/app/views/projects/merge_requests/update.js.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
-$('aside.right-sidebar').effect('highlight');
-new IssuableContext();
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 2ec0d20a879..4d381754610 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -41,9 +41,4 @@
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
- Could not connect to the CI server. Please check your settings and try again.
-
- :javascript
- $(function() {
- merge_request_widget.getCIStatus(false);
- });
+ Could not connect to the CI server. Please check your settings and try again. \ No newline at end of file
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 3abae9f0bf6..ec4beae9727 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -44,3 +44,8 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
+ - else
+ %p
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ = render 'projects/merge_requests/widget/merged_buttons'
diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml
index 85a3a6ba9e2..56167509af9 100644
--- a/app/views/projects/merge_requests/widget/_merged_buttons.haml
+++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml
@@ -1,11 +1,14 @@
-- source_branch_exists = local_assigns.fetch(:source_branch_exists, false)
-- mr_can_be_reverted = @merge_request.can_be_reverted?
+- can_remove_source_branch = local_assigns.fetch(:source_branch_exists, false) && @merge_request.can_remove_source_branch?(current_user)
+- mr_can_be_reverted = @merge_request.can_be_reverted?(current_user)
+- mr_can_be_cherry_picked = @merge_request.can_be_cherry_picked?
-- if source_branch_exists || mr_can_be_reverted
+- if can_remove_source_branch || mr_can_be_reverted || mr_can_be_cherry_picked
.btn-group
- - if source_branch_exists
+ - if can_remove_source_branch
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do
= icon('trash-o')
Remove Source Branch
- if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
+ - if mr_can_be_cherry_picked
+ = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index be63875ab34..6ec84660157 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -24,15 +24,15 @@
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do
- = icon('trash-o')
- Delete
-
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
= icon('pencil-square-o')
Edit
-.detail-page-description.milestone-detail.second-block
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+ = icon('trash-o')
+ Delete
+
+.detail-page-description.milestone-detail
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
@@ -42,9 +42,12 @@
= preserve do
= markdown @milestone.description
-- if @milestone.complete?(current_user) && @milestone.active?
+- if @milestone.total_items_count(current_user).zero?
+ .alert.alert-success.prepend-top-default
+ %span Assign some issues to this milestone.
+- elsif @milestone.complete?(current_user) && @milestone.active?
.alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. You may close milestone now.
+ %span All issues for this milestone are closed. You may close this milestone now.
= render 'shared/milestones/summary', milestone: @milestone, project: @project
= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index 28a617538b5..c609c505def 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.append-bottom-default
+.row-content-block.append-bottom-default
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
index b8068835b3a..572b00a38c7 100644
--- a/app/views/projects/notes/_discussion.html.haml
+++ b/app/views/projects/notes/_discussion.html.haml
@@ -1,5 +1,5 @@
- note = discussion_notes.first
-.timeline-entry
+%li.note.note-discussion.timeline-entry
.timeline-entry-inner
.timeline-icon
= link_to user_path(note.author) do
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 03a44ca99c0..aeb7c1d5ee4 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,4 +1,5 @@
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
+- note_editable = note_editable?(note)
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
@@ -7,7 +8,9 @@
.note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
- = "#{note.author.to_reference} commented"
+ = "#{note.author.to_reference}"
+ - if !note.system
+ commented
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
.note-actions
@@ -15,16 +18,16 @@
- if access
%span.note-role
= access
- - if note_editable?(note)
+ - if note_editable
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil')
= 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: 'note-action-button js-note-delete danger' do
= icon('trash-o')
- .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
+ .note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- - if note_editable?(note)
+ - if note_editable
= render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index cc42aab5c52..1c39ce897a3 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,6 +1,6 @@
%ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes"
-%ul.notes.timeline
+%ul.notes.notes-form.timeline
%li.timeline-entry
- if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index cd8a5f0bd02..0ea8862a684 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -6,15 +6,11 @@
= "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 46f2ba4bbcf..2a2ead58eeb 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -8,21 +8,18 @@
= "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else
.panel.panel-default
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
- = render discussion_notes
+ %ul.notes.timeline
+ = render discussion_notes
.discussion-reply-holder
= link_to_reply_diff(discussion_notes.first)
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index f8e000b424f..45141bcd1df 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -5,14 +5,10 @@
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
on the outdated diff
+ = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down
Show/hide discussion
- .last-update.hide.js-toggle-content
- - last_note = discussion_notes.last
- last updated by
- = link_to_member(@project, last_note.author, avatar: false)
- #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 62888e41935..ae13f8428f0 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -8,7 +8,7 @@
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
- - if current_user.can?(:admin_group, shared_group)
+ - if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index f68449b1863..b9e9dd8aaea 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,35 +1,41 @@
-- unless @branches.empty?
- %br
- %h4 Already Protected:
- .table-holder
+%h5.prepend-top-0
+ Already Protected (#{@branches.size})
+- if @branches.empty?
+ %p.profile-settings-message.text-center
+ No branches are protected, protect a branch with the form above.
+- else
+ - can_admin_project = can?(current_user, :admin_project, @project)
+ .table-responsive
%table.table.protected-branches-list
+ %colgroup
+ %col{ width: "30%" }
+ %col{ width: "30%" }
+ %col{ width: "25%" }
+ - if can_admin_project
+ %col
%thead
- %tr.no-border
+ %tr
%th Branch
- %th Developers can push
%th Last commit
- %th
-
+ %th Developers can push
+ - if can_admin_project
+ %th
%tbody
- @branches.each do |branch|
- @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
%tr
%td
- = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
- %td
- = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
- %td
- - if commit = branch.commit
- = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- &middot;
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
+ = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if commit = branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
+ - if can_admin_project
%td
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 653b02da4db..c7d317dbaee 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,31 +1,33 @@
- page_title "Protected branches"
-%h3.page-title Protected branches
-%p.light Keep stable branches secure and force developers to use Merge Requests
-%hr
-.well
- %p Protected branches are designed to
- %ul
- %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevent anyone from force pushing to the branch
- %li prevent anyone from deleting the branch
- %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = page_title
+ %p Keep stable branches secure and force developers to use Merge Requests
+ .col-lg-9
+ %h5.prepend-top-0
+ Protect a branch
+ .account-well.append-bottom-default
+ %p.light-header.append-bottom-0 Protected branches are designed to
+ %ul
+ %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
+ %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+ - if can? current_user, :admin_project, @project
+ = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
+ = form_errors(@protected_branch)
-- if can? current_user, :admin_project, @project
- = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
- = form_errors(@protected_branch)
-
- .form-group
- = f.label :name, "Branch", class: 'control-label'
- .col-sm-10
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :developers_can_push do
- = f.check_box :developers_can_push
- %strong Developers can push
- .help-block Allow developers to push to this branch
- .form-actions
- = f.submit 'Protect', class: "btn-create btn"
-= render 'branches_list'
+ .form-group
+ = f.label :name, "Branch", class: "label-light"
+ = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
+ .form-group
+ = f.check_box :developers_can_push, class: "pull-left"
+ .prepend-left-20
+ = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
+ %p.light.append-bottom-0
+ Allow developers to push to this branch
+ = f.submit "Protect", class: "btn-create btn"
+ %hr
+ = render "branches_list"
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 6f0b32aa165..0d59cec322c 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.oneline
.title
Release notes for tag
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 6a37f444bb7..9fa4127c948 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -1,7 +1,10 @@
%h3 Shared runners
-.bs-callout.bs-callout-warning
- GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
+.bs-callout.bs-callout-warning.shared-runners-description
+ - if shared_runners_text.present?
+ = markdown(shared_runners_text, pipeline: 'plain_markdown')
+ - else
+ Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com).
%hr
- if @project.shared_runners_enabled?
= link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 4310f038fc9..74feb9e3282 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,7 +12,7 @@
= render 'projects/last_push'
= render "home_panel"
-.project-stats.gray-content-block.second-block
+.project-stats.row-content-block.second-block
%ul.nav
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
@@ -36,9 +36,9 @@
%li
= link_to 'Changelog', changelog_path(@project)
- - if @repository.license
+ - if @repository.license_blob
%li
- = link_to 'License', license_path(@project)
+ = link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
%li
@@ -47,15 +47,15 @@
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
- = link_to add_changelog_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- - unless @repository.license
+ - unless @repository.license_blob
%li.missing
- = link_to add_license_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
- = link_to add_contribution_guide_path(@project) do
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- if @repository.commit
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 4af963e14da..103ff447464 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Snippets"
= render "header_title"
-.gray-content-block.top-block
+.row-content-block.top-block
.pull-right
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
= icon('plus')
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 760347de0a9..dc6ece30dd2 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
- if can? current_user, :push_code, @project
.pull-right
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index b40a6e5cb2d..f9306453297 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -23,7 +23,7 @@
.form-group
= label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+ = text_area_tag :message, nil, required: false, tabindex: 3, class: 'form-control', rows: 5
.help-block Optionally, enter a message to create an annotated tag.
%hr
.form-group
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 1dc9b799a95..9f1424aecc7 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -2,7 +2,7 @@
= render "projects/commits/header_title"
= render "projects/commits/head"
-.gray-content-block
+.row-content-block
.pull-right
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
@@ -19,15 +19,13 @@
%i.fa.fa-trash-o
.title
%span.item-title= @tag.name
- - if @tag.message.present?
- %span.light
- &nbsp;
- = strip_gpg_signature(@tag.message)
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
-
+ - if @tag.message.present?
+ %pre.body
+ = strip_gpg_signature(@tag.message)
.append-bottom-default.prepend-top-default
- if @release.description.present?
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 48b3b5c9920..112b51712ef 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -1,7 +1,6 @@
%tr
%td
- .clearfix
- %span.monospace= trigger.token
+ %span.monospace= trigger.token
%td
- if trigger.last_trigger_request
@@ -9,6 +8,5 @@
- else
Never
- %td
- .pull-right
- = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
+ %td.text-right
+ = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-warning btn-sm"
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index bd346c4b8e6..f91885b216d 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -1,71 +1,70 @@
- page_title "Triggers"
-%h3.page-title
- Triggers
-%p.light
- Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ = page_title
+ %p
+ Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+ .col-lg-9
+ %h5.prepend-top-0
+ Your triggers
+ - if @triggers.any?
+ .table-responsive
+ %table.table
+ %thead
+ %th Token
+ %th Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
+ - else
+ %p.profile-settings-message.text-center.append-bottom-default
+ There are no triggers to use, add one by the button below.
-%hr.clearfix
+ = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f|
+ = f.submit "Add Trigger", class: 'btn btn-success'
--if @triggers.any?
- .table-holder
- %table.table
- %thead
- %th Token
- %th Last used
- %th
- = render partial: 'trigger', collection: @triggers, as: :trigger
-- else
- %h4 No triggers
+ %h5.prepend-top-default
+ Use CURL
-= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f|
- .clearfix
- = f.submit "Add Trigger", class: 'btn btn-success pull-right'
+ %p.light
+ Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
-%hr.clearfix
--if @triggers.any?
- %h3
- Use CURL
+ %pre
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F ref=REF_NAME \
+ #{builds_trigger_url(@project.id)}
+ %h5.prepend-top-default
+ Use .gitlab-ci.yml
- %p.light
- Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
+ %p.light
+ Copy the snippet to
+ %i .gitlab-ci.yml
+ of dependent project.
+ At the end of your build it will trigger this project to rebuilt.
+ %pre
+ :plain
+ trigger:
+ type: deploy
+ script:
+ - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
+ %h5.prepend-top-default
+ Pass build variables
- %pre
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F ref=REF_NAME \
- #{builds_trigger_url(@project.id)}
- %h3
- Use .gitlab-ci.yml
+ %p.light
+ Add
+ %strong variables[VARIABLE]=VALUE
+ to API request.
+ The value of variable could then be used to distinguish triggered build from normal one.
- %p.light
- Copy the snippet to
- %i .gitlab-ci.yml
- of dependent project.
- At the end of your build it will trigger this project to rebuilt.
-
- %pre
- :plain
- trigger:
- type: deploy
- script:
- - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
- %h3
- Pass build variables
-
- %p.light
- Add
- %strong variables[VARIABLE]=VALUE
- to API request.
- The value of variable could then be used to distinguish triggered build from normal one.
-
- %pre
- :plain
- curl -X POST \
- -F token=TOKEN \
- -F "ref=REF_NAME" \
- -F "variables[RUN_NIGHTLY_BUILD]=true" \
- #{builds_trigger_url(@project.id)}
+ %pre.append-bottom-0
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F "ref=REF_NAME" \
+ -F "variables[RUN_NIGHTLY_BUILD]=true" \
+ #{builds_trigger_url(@project.id)}
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index dd27ea2b11b..ba3f2cadc48 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -2,7 +2,7 @@
= render "header_title"
= render 'nav'
-.gray-content-block
+.row-content-block
%span.oneline
Git access for
%strong= @project_wiki.path_with_namespace
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
index df16f503570..a585147ddd1 100644
--- a/app/views/repository_check_mailer/notify.html.haml
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -3,3 +3,6 @@
%p
= link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
+
+%p
+ You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml
index 02f3f80288a..93db151329e 100644
--- a/app/views/repository_check_mailer/notify.text.haml
+++ b/app/views/repository_check_mailer/notify.text.haml
@@ -1,3 +1,6 @@
#{@message}.
\
View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
+
+You are receiving this message because you are a GitLab administrator
+for #{Gitlab.config.gitlab.url}.
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 2c3fca439f3..2c378231237 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -2,97 +2,70 @@
- if @project
%li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do
- = icon('code fw')
- %span
- Code
- %span.badge
- = @search_results.blobs_count
+ Code
+ %span.badge
+ = @search_results.blobs_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
%li{class: ("active" if @scope == 'notes')}
= link_to search_filter_path(scope: 'notes') do
- = icon('comments fw')
- %span
- Comments
- %span.badge
- = @search_results.notes_count
+ Comments
+ %span.badge
+ = @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
- = icon('book fw')
- %span
- Wiki
- %span.badge
- = @search_results.wiki_blobs_count
+ Wiki
+ %span.badge
+ = @search_results.wiki_blobs_count
%li{class: ("active" if @scope == 'commits')}
= link_to search_filter_path(scope: 'commits') do
- = icon('history fw')
- %span
- Commits
- %span.badge
- = @search_results.commits_count
+ Commits
+ %span.badge
+ = @search_results.commits_count
- elsif @show_snippets
%li{class: ("active" if @scope == 'snippet_blobs')}
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- = icon('code fw')
- %span
- Snippet Contents
- %span.badge
- = @search_results.snippet_blobs_count
+ Snippet Contents
+ %span.badge
+ = @search_results.snippet_blobs_count
%li{class: ("active" if @scope == 'snippet_titles')}
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- = icon('book fw')
- %span
- Titles and Filenames
- %span.badge
- = @search_results.snippet_titles_count
+ Titles and Filenames
+ %span.badge
+ = @search_results.snippet_titles_count
- else
%li{class: ("active" if @scope == 'projects')}
= link_to search_filter_path(scope: 'projects') do
- = icon('bookmark fw')
- %span
- Projects
- %span.badge
- = @search_results.projects_count
+ Projects
+ %span.badge
+ = @search_results.projects_count
%li{class: ("active" if @scope == 'issues')}
= link_to search_filter_path(scope: 'issues') do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.badge
- = @search_results.issues_count
+ Issues
+ %span.badge
+ = @search_results.issues_count
%li{class: ("active" if @scope == 'merge_requests')}
= link_to search_filter_path(scope: 'merge_requests') do
- = icon('tasks fw')
- %span
- Merge requests
- %span.badge
- = @search_results.merge_requests_count
+ Merge requests
+ %span.badge
+ = @search_results.merge_requests_count
%li{class: ("active" if @scope == 'milestones')}
= link_to search_filter_path(scope: 'milestones') do
- = icon('clock-o fw')
- %span
- Milestones
- %span.badge
- = @search_results.milestones_count
-
+ Milestones
+ %span.badge
+ = @search_results.milestones_count
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 4ef544136a8..ef1c0296d49 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,47 +1,33 @@
-.dropdown.inline
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Group:
- - if @group.present?
- %strong= @group.name
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by group
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(group_id: nil), class: ("is-active" if !params[:group_id].present?) do
- Any
- %li.divider
- - current_user.authorized_groups.sort_by(&:name).each do |group|
- %li
- = link_to search_filter_path(group_id: group.id, project_id: nil), class: ("is-active" if params[:group_id] == group.id.to_s) do
- = group.name
+- if params[:group_id].present?
+ = hidden_field_tag :group_id, params[:group_id]
+- if params[:project_id].present?
+ = hidden_field_tag :project_id, params[:project_id]
+.dropdown
+ %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } }
+ %span.dropdown-toggle-text
+ Group:
+ - if @group.present?
+ = @group.name
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by group")
+ = dropdown_filter("Search groups")
+ = dropdown_content
+ = dropdown_loading
-.dropdown.inline.prepend-left-10.project-filter
- %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light Project:
- - if @project.present?
- %strong= @project.name_with_namespace
- - else
- Any
- %b.caret
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Filter results by project
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}}
- = icon('times')
- .dropdown-content
- %ul
- %li
- = link_to search_filter_path(project_id: nil), class: ("is-active" if !params[:project_id].present?) do
- Any
- %li.divider
- - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project|
- %li
- = link_to search_filter_path(project_id: project.id, group_id: nil), class: ("is-active" if params[:project_id] == project.id.to_s) do
- = project.name_with_namespace
+.dropdown.project-filter
+ %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %span.dropdown-toggle-text
+ Project:
+ - if @project.present?
+ = @project.name_with_namespace
+ - else
+ Any
+ = icon("chevron-down")
+ .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right
+ = dropdown_title("Filter results by project")
+ = dropdown_filter("Search projects")
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a9dbc84da29..3139be1cd37 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,14 +1,15 @@
-= form_tag search_path, method: :get do |f|
- = hidden_field_tag :project_id, params[:project_id]
- = hidden_field_tag :group_id, params[:group_id]
+= form_tag search_path, method: :get, class: 'js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
- .search-holder.clearfix
- .input-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false
- %span.input-group-btn
- = button_tag 'Search', class: "btn btn-primary"
+ .search-holder
+ .search-field-holder
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = icon("search", class: "search-icon")
+ %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
+ = icon("times-circle")
+ %span.sr-only
+ Clear search
- unless params[:snippets].eql? 'true'
- %br
= render 'filter' if current_user
+ = button_tag "Search", class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 60df348891c..252c37532e1 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,10 +1,8 @@
-- if @search_results.empty?
+- if @search_objects.empty?
= render partial: "search/results/empty"
- else
- .gray-content-block
- Search results for
- %code
- = @search_term
+ .row-content-block
+ = search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
@@ -15,12 +13,9 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @objects
+ = render 'shared/projects/list', projects: @search_objects
- else
- = render partial: "search/results/#{@scope.singularize}", collection: @objects
+ = render partial: "search/results/#{@scope.singularize}", collection: @search_objects
- if @scope != 'projects'
- = paginate @objects, theme: 'gitlab'
-
-:javascript
- $(".search-results .term").highlight("#{escape_javascript(params[:search])}");
+ = paginate(@search_objects, theme: 'gitlab')
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 710f5613c81..640890fbe92 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -7,7 +7,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(issue.description, { project: issue.project }))
+ = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project }))
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index faeb2b55c6f..333f6533213 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -2,7 +2,7 @@
%h4
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
- .pull-right ##{merge_request.iid}
+ .pull-right #{merge_request.to_reference}
- if merge_request.description.present?
.description.term
= preserve do
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 34241cd8aad..b0fc60573f7 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -7,7 +7,7 @@
Confirmation required
.modal-body
- %p.cred.lead.js-confirm-text
+ %p.text-danger.js-confirm-text
%p
This action can lead to data loss.
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 57856031d6e..37dcf39c062 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,12 +1,13 @@
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
+ - link_icon = icon('link')
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
- %i.fa.fa-link
+ = link_icon
= i
.blob-content{data: {blob_id: blob.id}}
- = highlight(blob.name, blob.data)
+ = highlight(blob.name, blob.data, plain: blob.no_highlighting?)
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index b38c5e18efb..9ce5562e667 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,4 +2,4 @@
%span.label-name
= link_to_label(label, tooltip: false)
%span.prepend-left-10
- = markdown(label.description, pipeline: :single_line)
+ = markdown(label.description, pipeline: :single_line) \ No newline at end of file
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
new file mode 100644
index 00000000000..dc89e36419c
--- /dev/null
+++ b/app/views/shared/_labels_row.html.haml
@@ -0,0 +1,3 @@
+- labels.each do |label|
+ %span.label-row
+ = link_to_label(label, tooltip: false)
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index fc935166bf6..4eaf7c2a025 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -62,6 +62,14 @@
%strong Build events
%p.light
This url will be triggered when a build status changes
+ - if @service.supported_events.include?("wiki_page")
+ %div
+ = form.check_box :wiki_page_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :wiki_page_events, class: 'list-label' do
+ %strong Wiki Page events
+ %p.light
+ This url will be triggered when a wiki page is created/updated
- @service.fields.each do |field|
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index e3a6a5a68b6..d327bd0a96f 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -20,6 +20,11 @@
= sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do
= sort_title_milestone_later
+ - if controller.controller_name == 'issues' || controller.action_name == 'issues'
+ = link_to page_filter_path(sort: sort_value_due_date_soon) do
+ = sort_title_due_date_soon
+ = link_to page_filter_path(sort: sort_value_due_date_later) do
+ = sort_title_due_date_later
= link_to page_filter_path(sort: sort_value_upvotes) do
= sort_title_upvotes
= link_to page_filter_path(sort: sort_value_downvotes) do
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 921eaefd79a..9474462cbd1 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,5 +1,5 @@
.issues-filters
- .issues-details-filters.gray-content-block.second-block
+ .issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
@@ -23,6 +23,7 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown"
+
.pull-right
= render 'shared/sort_dropdown'
@@ -46,9 +47,10 @@
.filter-item.inline
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
-- if @label
- .gray-content-block.second-block
- = render "shared/label_row", label: @label
+ - if !@labels.nil?
+ .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
+ - if @labels.any?
+ = render "shared/labels_row", labels: @labels
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index aed2622a6da..5c52cc6d1da 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -4,7 +4,7 @@
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
- class: 'form-control pad js-gfm-input', required: true
+ class: 'form-control pad', required: true
- if issuable.is_a?(MergeRequest)
%p.help-block
@@ -75,8 +75,9 @@
= f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
- if has_labels
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
+ .issuable-form-select-holder
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
%span.light No labels yet.
&nbsp;
@@ -88,9 +89,10 @@
.form-group
= label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10
- - projects = project_options(issuable, current_user, ability: :admin_issue)
- = select_tag(:move_to_project_id, projects, include_blank: true,
- class: 'select2', data: { placeholder: 'Select project' })
+ .issuable-form-select-holder
+ - projects = project_options(issuable, current_user, ability: :admin_issue)
+ = select_tag(:move_to_project_id, projects, include_blank: true,
+ class: 'select2', data: { placeholder: 'Select project' })
&nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
@@ -102,17 +104,19 @@
.form-group
= f.label :source_branch, class: 'control-label'
.col-sm-10
- = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ .issuable-form-select-holder
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
.form-group
= f.label :target_branch, class: 'control-label'
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
+ .issuable-form-select-holder
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} })
- if @merge_request.new_record?
- %p.help-block
+ &nbsp;
= link_to 'Change branches', mr_change_branches_path(@merge_request)
- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?)
-.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")}
+.row-content-block{class: (is_footer ? "footer-block" : "middle-block")}
- if issuable.new_record?
= f.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index f722e61eeac..61fd1e9c335 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,44 +1,14 @@
- if params[:label_name].present?
- = hidden_field_tag(:label_name, params[:label_name])
+ - if params[:label_name].respond_to?('any?')
+ - params[:label_name].each do |label|
+ = hidden_field_tag "label_name[]", label, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
+ %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name[]", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
%span.dropdown-toggle-text
- = h(params[:label_name].presence || "Label")
+ = h(multi_label_name(params[:label_name], "Label"))
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Filter by label")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
+ = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- if can? current_user, :admin_label, @project and @project
- .dropdown-page-two.dropdown-new-label
- = dropdown_title("Create new label", back: true)
- = dropdown_content do
- .dropdown-labels-error.js-label-error
- %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
- .suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
- .dropdown-label-color-input
- .dropdown-label-color-preview.js-dropdown-label-color-preview
- %input#new_label_color.dropdown-input-field{ type: "text" }
- .clearfix
- %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
- Create
- %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
- Cancel
+ = render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
new file mode 100644
index 00000000000..3bc57d3d2ac
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -0,0 +1,17 @@
+.dropdown-page-two.dropdown-new-label
+ = dropdown_title("Create new label", back: true)
+ = dropdown_content do
+ .dropdown-labels-error.js-label-error
+ %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
+ .suggest-colors.suggest-colors-dropdown
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp
+ .dropdown-label-color-input
+ .dropdown-label-color-preview.js-dropdown-label-color-preview
+ %input#new_label_color.default-dropdown-input{ type: "text" }
+ .clearfix
+ %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" }
+ Create
+ %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" }
+ Cancel
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
new file mode 100644
index 00000000000..7f4867417f7
--- /dev/null
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -0,0 +1,20 @@
+- title = local_assigns.fetch(:title, 'Assign labels')
+- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
+.dropdown-page-one
+ = dropdown_title(title)
+ = dropdown_filter(filter_placeholder)
+ = dropdown_content
+ - if @project
+ = dropdown_footer do
+ %ul.dropdown-footer-list
+ - if can? current_user, :admin_label, @project
+ %li
+ %a.dropdown-toggle-page{href: "#"}
+ Create new
+ %li
+ = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+ - if can? current_user, :admin_label, @project
+ Manage labels
+ - else
+ View labels
+ = dropdown_loading \ No newline at end of file
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index a6970b7eebb..1d9b09a5ef1 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -4,22 +4,22 @@
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
- = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
+ = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
- = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
+ = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
+ = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
- = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
+ = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 08bfd93f4e6..ed1b8a8da2a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -10,17 +10,17 @@
= sidebar_gutter_toggle_icon
.issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if prev_issuable = prev_issuable_for(issuable)
- = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
+ = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Prev
- if next_issuable = next_issuable_for(issuable)
- = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
+ = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn issuable-pager'
- else
- %a.btn.btn-default.disabled{href: '#'}
+ %a.btn.btn-default.issuable-pager.disabled{href: '#'}
Next
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
@@ -58,7 +58,7 @@
- if issuable.milestone
= issuable.milestone.title
- else
- No
+ None
.title.hide-collapsed
Milestone
= icon('spinner spin', class: 'block-loading')
@@ -75,6 +75,34 @@
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
= dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
+ - if issuable.has_attribute?(:due_date)
+ .block.due_date
+ .sidebar-collapsed-icon
+ = icon('calendar')
+ %span.js-due-date-sidebar-value
+ = issuable.due_date.try(:to_s, :medium) || 'None'
+ .title.hide-collapsed
+ Due date
+ = icon('spinner spin', class: 'block-loading')
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.bold.hide-collapsed
+ - if issuable.due_date
+ = issuable.due_date.to_s(:medium)
+ - else
+ .light None
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .selectbox.hide-collapsed
+ = f.hidden_field :due_date, value: issuable.due_date
+ .dropdown
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
+ %span.dropdown-toggle-text Due date
+ = icon('chevron-down')
+ .dropdown-menu.dropdown-menu-due-date
+ = dropdown_title('Due date')
+ = dropdown_content do
+ .js-due-date-calendar
+
- if issuable.project.labels.any?
.block.labels
.sidebar-collapsed-icon
@@ -101,23 +129,9 @@
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- = dropdown_title("Assign labels")
- = dropdown_filter("Search labels")
- = dropdown_content
- - if @project
- = dropdown_footer do
- %ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
- %li
- %a.dropdown-toggle-page{href: "#"}
- Create new
- %li
- = link_to namespace_project_labels_path(@project.namespace, @project) do
- - if can? current_user, :admin_label, @project
- Manage labels
- - else
- View labels
+ = render partial: "shared/issuable/label_page_default"
+ - if can? current_user, :admin_label, @project and @project
+ = render partial: "shared/issuable/label_page_create"
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -150,6 +164,7 @@
:javascript
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
new LabelsSelect();
- new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
+ new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
new Subscription('.subscription')
- new Sidebar();
+ new DueDateSelect();
+ sidebar = new Sidebar();
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index e1127b2311c..47b66d44e43 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -23,5 +23,5 @@
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
- class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index cab8743a077..7ff947a51db 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -24,7 +24,7 @@
- else
= link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.milestone-detail
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index e7e04621ff4..1169bed0382 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,4 +1,5 @@
- @sort ||= sort_value_recently_updated
+- personal = params[:personal]
- archived = params[:archived]
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -10,7 +11,7 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
+ = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
@@ -20,3 +21,11 @@
%li
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
+ - if current_user
+ %li.divider
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
+ Owned by anyone
+ %li
+ = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
+ Owned by me
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 53ff8959bc8..ab8b022411d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -6,9 +6,8 @@
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
-- cache_key.push(ci_commit.status) if ci_commit
+- cache_key.push(project.commit.status) if project.commit.try(:status)
%li.project-row{ class: css_class }
= cache(cache_key) do
@@ -16,9 +15,9 @@
- if project.main_language
%span
= project.main_language
- - if ci_commit
+ - if project.commit.try(:status)
%span
- = render_ci_status(ci_commit)
+ = render_ci_status(project.commit)
- if forks
%span
= icon('code-fork')
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 3c445f67236..e65b1814872 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -20,6 +20,6 @@
- else
= render "snippets/actions"
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.row-content-block.second-block
%h2.title
= markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
index cfd11e45b6a..94d4dd4fa7d 100644
--- a/app/views/sherlock/file_samples/show.html.haml
+++ b/app/views/sherlock/file_samples/show.html.haml
@@ -3,7 +3,7 @@
- header_title t('sherlock.title'), sherlock_transactions_path
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml
index 83f61ce4b07..fc2863dca8e 100644
--- a/app/views/sherlock/queries/show.html.haml
+++ b/app/views/sherlock/queries/show.html.haml
@@ -9,7 +9,7 @@
%a(href="#tab-backtrace" data-toggle="tab")
= t('sherlock.backtrace')
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml
index 010e1a2a902..da969c02765 100644
--- a/app/views/sherlock/transactions/index.html.haml
+++ b/app/views/sherlock/transactions/index.html.haml
@@ -1,7 +1,7 @@
- page_title t('sherlock.title')
- header_title t('sherlock.title'), sherlock_transactions_path
-.gray-content-block
+.row-content-block
.pull-right
= link_to(destroy_all_sherlock_transactions_path,
class: 'btn btn-danger',
diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml
index 9d4b0b2724c..8aa6b437d95 100644
--- a/app/views/sherlock/transactions/show.html.haml
+++ b/app/views/sherlock/transactions/show.html.haml
@@ -16,7 +16,7 @@
%span.badge
#{@transaction.file_samples.length}
-.gray-content-block
+.row-content-block
.pull-right
= link_to(sherlock_transactions_path, class: 'btn') do
%i.fa.fa-arrow-left
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0c4b6a5618b..3c0b89c6741 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,8 +6,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
-= render 'shared/show_aside'
-
.user-profile
.cover-block
.cover-controls
@@ -71,13 +69,13 @@
= @user.location
%ul.nav-links.center.user-profile-nav
- %li.activity-tab
+ %li.js-activity-tab
= link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
Activity
- %li.groups-tab
+ %li.js-groups-tab
= link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do
Groups
- %li.contributed-tab
+ %li.js-contributed-tab
= link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do
Contributed projects
%li.projects-tab
@@ -87,7 +85,7 @@
%div{ class: container_class }
.tab-content
#activity.tab-pane
- .gray-content-block.calender-block.white.second-block.hidden-xs
+ .row-content-block.calender-block.white.second-block.hidden-xs
%div{ class: container_class }
.user-calendar{data: {href: user_calendar_path}}
%h4.center.light
@@ -100,7 +98,7 @@
#groups.tab-pane
- # This tab is always loaded via AJAX
- #contributed.contributed-projects.tab-pane
+ #contributed.tab-pane
- # This tab is always loaded via AJAX
#projects.tab-pane
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 8692c1cccee..ab8bb949862 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -15,16 +15,16 @@
- if current_user
:javascript
- var get_emojis_url = "#{emojis_path}";
- var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
- var noteable_type = "#{votable.class.name.underscore}";
- var noteable_id = "#{votable.id}";
- var aliases = #{AwardEmoji.aliases.to_json};
+ var getEmojisUrl = "#{emojis_path}";
+ var postEmojiUrl = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
+ var noteableType = "#{votable.class.name.underscore}";
+ var noteableId = "#{votable.id}";
+ var unicodes = #{AwardEmoji.unicode.to_json};
- window.awards_handler = new AwardsHandler(
- get_emojis_url,
- post_emoji_url,
- noteable_type,
- noteable_id,
- aliases
+ window.awardsHandler = new AwardsHandler(
+ getEmojisUrl,
+ postEmojiUrl,
+ noteableType,
+ noteableId,
+ unicodes
);
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 9e1215b21a6..f3327ca9e61 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -39,7 +39,7 @@ class PostReceive
end
if Gitlab::Git.tag_ref?(ref)
- GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
+ GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
@@ -47,7 +47,7 @@ class PostReceive
end
private
-
+
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
index 44b3145d50f..a3e16fa5212 100644
--- a/app/workers/repository_check/batch_worker.rb
+++ b/app/workers/repository_check/batch_worker.rb
@@ -33,8 +33,8 @@ module RepositoryCheck
# has to sit and wait for this query to finish.
def project_ids
limit = 10_000
- never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit).
- pluck(:id)
+ never_checked_projects = Project.where('last_repository_check_at IS NULL AND created_at < ?', 24.hours.ago).
+ limit(limit).pluck(:id)
old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
never_checked_projects + old_check_projects
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
index e54ae86d06c..f2d12ba5a7d 100644
--- a/app/workers/repository_check/single_repository_worker.rb
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -1,9 +1,9 @@
module RepositoryCheck
class SingleRepositoryWorker
include Sidekiq::Worker
-
+
sidekiq_options retry: false
-
+
def perform(project_id)
project = Project.find(project_id)
project.update_columns(
@@ -11,20 +11,32 @@ module RepositoryCheck
last_repository_check_at: Time.now,
)
end
-
+
private
-
+
def check(project)
- # Use 'map do', not 'all? do', to prevent short-circuiting
- [project.repository, project.wiki.repository].map do |repository|
- git_fsck(repository.path_to_repo)
- end.all?
+ if !git_fsck(project.repository)
+ false
+ elsif project.wiki_enabled?
+ # Historically some projects never had their wiki repos initialized;
+ # this happens on project creation now. Let's initialize an empty repo
+ # if it is not already there.
+ begin
+ project.create_wiki
+ rescue Rugged::RepositoryError
+ end
+
+ git_fsck(project.wiki.repository)
+ else
+ true
+ end
end
-
- def git_fsck(path)
+
+ def git_fsck(repository)
+ path = repository.path_to_repo
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
-
+
if status.zero?
true
else
diff --git a/bin/background_jobs b/bin/background_jobs
index 1f67d732949..25a578a1c49 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
+ exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
diff --git a/bin/rails b/bin/rails
index 5191e6927af..0138d79b751 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 17240489f64..d87d5f57810 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
require_relative '../config/boot'
require 'rake'
Rake.application.run
diff --git a/bin/rspec b/bin/rspec
index 20060ebd79c..6e6709219af 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')
diff --git a/bin/spinach b/bin/spinach
index a080e286cfe..474050e29d1 100755
--- a/bin/spinach
+++ b/bin/spinach
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
- load File.expand_path("../spring", __FILE__)
-rescue LoadError
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('spinach', 'spinach')
diff --git a/bin/spring b/bin/spring
index 7b45d374fcd..7fe232c3aae 100755
--- a/bin/spring
+++ b/bin/spring
@@ -4,12 +4,12 @@
# It gets overwritten when you run the `spring binstub` command.
unless defined?(Spring)
- require "rubygems"
- require "bundler"
+ require 'rubygems'
+ require 'bundler'
- 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"
+ if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
+ Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
+ gem 'spring', match[1]
+ require 'spring/binstub'
end
end
diff --git a/bin/teaspoon b/bin/teaspoon
new file mode 100755
index 00000000000..7c3b8dfc4ed
--- /dev/null
+++ b/bin/teaspoon
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require 'bundler/setup'
+load Gem.bin_path('teaspoon', 'teaspoon')
diff --git a/bin/web b/bin/web
index 03fe7a6354b..ecd0bbd10b0 100755
--- a/bin/web
+++ b/bin/web
@@ -19,12 +19,12 @@ get_unicorn_pid()
start()
{
- $unicorn_cmd -D
+ exec $unicorn_cmd -D
}
start_foreground()
{
- $unicorn_cmd
+ exec $unicorn_cmd
}
stop()
diff --git a/config/application.rb b/config/application.rb
index 2e2ed48db07..b602e2b6168 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -32,7 +32,30 @@ 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, :otp_attempt, :variables, :import_url)
+ #
+ # Parameters filtered:
+ # - Password (:password, :password_confirmation)
+ # - Private tokens (:private_token)
+ # - Two-factor tokens (:otp_attempt)
+ # - Repo/Project Import URLs (:import_url)
+ # - Build variables (:variables)
+ # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
+ # - Webhook URLs (:hook)
+ # - Sentry DSN (:sentry_dsn)
+ # - Deploy keys (:key)
+ config.filter_parameters += %i(
+ certificate
+ encrypted_key
+ hook
+ import_url
+ key
+ otp_attempt
+ password
+ password_confirmation
+ private_token
+ sentry_dsn
+ variables
+ )
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 689694a3480..4f39016bfa4 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -36,7 +36,7 @@ Rails.application.configure do
# For having correct urls in mails
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# Open sent mails in browser
- config.action_mailer.delivery_method = :letter_opener
+ config.action_mailer.delivery_method = :letter_opener_web
# Don't make a mess when bootstrapping a development environment
config.action_mailer.perform_deliveries = (ENV['BOOTSTRAP'] != '1')
diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml
index f0656400beb..01c8dc5ff98 100644
--- a/config/gitlab.teatro.yml
+++ b/config/gitlab.teatro.yml
@@ -15,7 +15,6 @@ production: &base
issues: true
merge_requests: true
wiki: true
- wall: false
snippets: false
visibility_level: "private" # can be "private" | "internal" | "public"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index d9c15f81404..e682bcb976d 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -152,7 +152,6 @@ production: &base
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
- enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
@@ -168,9 +167,9 @@ production: &base
# once per hour you will have concurrent 'git fsck' jobs.
repository_check_worker:
cron: "20 * * * *"
- # Send admin emails once a day
+ # Send admin emails once a week
admin_email_worker:
- cron: "0 0 * * *"
+ cron: "0 0 * * 0"
# Remove outdated repository archives
repository_archive_cache_worker:
@@ -350,6 +349,8 @@ production: &base
# - { name: 'github',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
+ # url: "https://github.com/",
+ # verify_ssl: true,
# args: { scope: 'user:email' } }
# - { name: 'bitbucket',
# app_id: 'YOUR_APP_ID',
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 10c25044b75..8db2c05fe45 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -140,6 +140,30 @@ Settings.omniauth.cas3['session_duration'] ||= 8.hours
Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
Settings.omniauth.session_tickets['cas3'] = 'ticket'
+# Fill out omniauth-gitlab settings. It is needed for easy set up GHE or GH by just specifying url.
+
+github_default_url = "https://github.com"
+github_settings = Settings.omniauth['providers'].find { |provider| provider["name"] == "github" }
+
+if github_settings
+ # For compatibility with old config files (before 7.8)
+ # where people dont have url in github settings
+ if github_settings['url'].blank?
+ github_settings['url'] = github_default_url
+ end
+
+ github_settings["args"] ||= Settingslogic.new({})
+
+ if github_settings["url"].include?(github_default_url)
+ github_settings["args"]["client_options"] = OmniAuth::Strategies::GitHub.default_options[:client_options]
+ else
+ github_settings["args"]["client_options"] = {
+ "site" => File.join(github_settings["url"], "api/v3"),
+ "authorize_url" => File.join(github_settings["url"], "login/oauth/authorize"),
+ "token_url" => File.join(github_settings["url"], "login/oauth/access_token")
+ }
+ end
+end
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
@@ -245,7 +269,7 @@ Settings.cron_jobs['repository_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_check_worker']['cron'] ||= '20 * * * *'
Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::BatchWorker'
Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 22fe51a4534..b2d08d87bac 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -61,12 +61,30 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const)
end
- Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
- const = File.basename(path, '.rb').camelize.constantize
-
- config.instrument_instance_methods(const)
+ # Path to search => prefix to strip from constant
+ paths_to_instrument = {
+ ['app', 'finders'] => ['app', 'finders'],
+ ['app', 'mailers', 'emails'] => ['app', 'mailers'],
+ ['app', 'services', '**'] => ['app', 'services'],
+ ['lib', 'gitlab', 'diff'] => ['lib'],
+ ['lib', 'gitlab', 'email', 'message'] => ['lib']
+ }
+
+ paths_to_instrument.each do |(path, prefix)|
+ prefix = Rails.root.join(*prefix)
+
+ Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
+ path = Pathname.new(file_path).relative_path_from(prefix)
+ const = path.to_s.sub('.rb', '').camelize.constantize
+
+ config.instrument_methods(const)
+ config.instrument_instance_methods(const)
+ end
end
+ config.instrument_methods(Premailer::Adapter::Nokogiri)
+ config.instrument_instance_methods(Premailer::Adapter::Nokogiri)
+
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
@@ -97,16 +115,9 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor)
- # Instrument all service classes
- services = Rails.root.join('app', 'services')
-
- Dir[services.join('**', '*.rb')].each do |file_path|
- path = Pathname.new(file_path).relative_path_from(services)
- const = path.to_s.sub('.rb', '').camelize.constantize
-
- config.instrument_methods(const)
- config.instrument_instance_methods(const)
- end
+ # Instrument the classes used for checking if somebody has push access.
+ config.instrument_instance_methods(Gitlab::GitAccess)
+ config.instrument_instance_methods(Gitlab::GitAccessWiki)
end
GC::Profiler.enable
diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example
index b1bbcca1d61..30d05f16153 100644
--- a/config/initializers/rack_attack.rb.example
+++ b/config/initializers/rack_attack.rb.example
@@ -17,8 +17,9 @@ paths_to_be_protected = [
# Create one big regular expression that matches strings starting with any of
# the paths_to_be_protected.
paths_regex = Regexp.union(paths_to_be_protected.map { |path| /\A#{Regexp.escape(path)}/ })
+rack_attack_enabled = Gitlab.config.rack_attack.git_basic_auth['enabled']
-unless Rails.env.test?
+unless Rails.env.test? || !rack_attack_enabled
Rack::Attack.throttle('protected paths', limit: 10, period: 60.seconds) do |req|
if req.post? && req.path =~ paths_regex
req.ip
diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb
index bbbfed68329..6a721826170 100644
--- a/config/initializers/rack_attack_git_basic_auth.rb
+++ b/config/initializers/rack_attack_git_basic_auth.rb
@@ -1,4 +1,6 @@
-unless Rails.env.test?
+rack_attack_enabled = Gitlab.config.rack_attack.git_basic_auth['enabled']
+
+unless Rails.env.test? || !rack_attack_enabled
# Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will
# update the blacklist from Grack::Auth#authenticate_user.
Rack::Attack.blacklist('Git HTTP Basic Auth') do |req|
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index e87899b2d5c..74fef7cadfe 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -15,6 +15,9 @@ if Rails.env.production?
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
config.release = Gitlab::REVISION
+
+ # Sanitize fields based on those sanitized from Rails.
+ config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
end
end
end
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 88cb859871c..599dabb9e50 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -22,7 +22,7 @@ else
key: '_gitlab_session',
secure: Gitlab.config.gitlab.https,
httponly: true,
- expire_after: Settings.gitlab['session_expire_delay'] * 60,
+ expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root
)
end
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index b8cc025bae2..d256a16d42b 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -1,2 +1,3 @@
-Rails.application.config.action_dispatch.trusted_proxies =
+Rails.application.config.action_dispatch.trusted_proxies = (
[ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies)
+).map { |proxy| IPAddr.new(proxy) }
diff --git a/config/routes.rb b/config/routes.rb
index ecde83d8547..8deb224cde9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,16 +16,18 @@ Rails.application.routes.draw do
end
end
- # Make the built-in Rails routes available in development, otherwise they'd
- # get swallowed by the `namespace/project` route matcher below.
- #
- # See https://git.io/va79N
if Rails.env.development?
+ # Make the built-in Rails routes available in development, otherwise they'd
+ # get swallowed by the `namespace/project` route matcher below.
+ #
+ # See https://git.io/va79N
get '/rails/mailers' => 'rails/mailers#index'
get '/rails/mailers/:path' => 'rails/mailers#preview'
get '/rails/info/properties' => 'rails/info#properties'
get '/rails/info/routes' => 'rails/info#routes'
get '/rails/info' => 'rails/info#index'
+
+ mount LetterOpenerWeb::Engine, at: '/rails/letter_opener'
end
namespace :ci do
@@ -212,8 +214,6 @@ Rails.application.routes.draw do
resources :keys, only: [:show, :destroy]
resources :identities, except: [:show]
- delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
-
member do
get :projects
get :keys
@@ -223,12 +223,14 @@ Rails.application.routes.draw do
put :unblock
put :unlock
put :confirm
- post 'impersonate' => 'impersonation#create'
+ post :impersonate
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
+ resource :impersonation, only: :destroy
+
resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
@@ -418,6 +420,7 @@ Rails.application.routes.draw do
devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
+ get '/users/almost_there' => 'confirmations#almost_there'
end
root to: "root#index"
@@ -549,6 +552,7 @@ Rails.application.routes.draw do
post :cancel_builds
post :retry_builds
post :revert
+ post :cherry_pick
end
end
@@ -669,6 +673,7 @@ Rails.application.routes.draw do
post :cancel
post :retry
post :erase
+ get :raw
end
resource :artifacts, only: [] do
@@ -707,6 +712,7 @@ Rails.application.routes.draw do
post :toggle_award_emoji
get :referenced_merge_requests
get :related_branches
+ get :can_create_branch
end
collection do
post :bulk_update
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 0825776ffaa..87fb8e3300d 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -1,6 +1,9 @@
Gitlab::Seeder.quiet do
+ # Limit the number of merge requests per project to avoid long seeds
+ MAX_NUM_MERGE_REQUESTS = 10
+
Project.all.reject(&:empty_repo?).each do |project|
- branches = project.repository.branch_names
+ branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
branches.each do |branch_name|
break if branches.size < 2
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index e3ca2b4eea3..b99d24a03c9 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds
commits = @project.repository.commits('master', nil, 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
- @project.ensure_ci_commit(sha)
+ @project.ensure_ci_commit(sha, 'master')
end
rescue
[]
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index eed6d366814..efdf53112fd 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -1,19 +1,26 @@
class MigrateRepoSize < ActiveRecord::Migration
def up
- Project.reset_column_information
- Project.find_each(batch_size: 500) do |project|
+ project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
+
+ project_data.each do |project|
+ id = project['id']
+ namespace_path = project['namespace_path'] || ''
+ path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
+
begin
- if project.empty_repo?
+ repo = Gitlab::Git::Repository.new(path)
+ if repo.empty?
print '-'
else
- project.update_repository_size
+ size = repo.size
print '.'
+ execute("UPDATE projects SET repository_size = #{size} WHERE id = #{id}")
end
- rescue
- print 'F'
+ rescue => e
+ puts "\nFailed to update project #{id}: #{e}"
end
end
- puts 'Done'
+ puts "\nDone"
end
def down
diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
index 003169c13c6..d7b00e3d6ed 100644
--- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
+++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
+ create_trigrams_extension
+
unless trigrams_enabled?
raise 'You must enable the pg_trgm extension. You can do so by running ' \
'"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \
@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
row && row['enabled'] == 't' ? true : false
end
+ def create_trigrams_extension
+ # This may not work if the user doesn't have permission. We attempt in
+ # case we do have permission, particularly for test/dev environments.
+ begin
+ enable_extension 'pg_trgm'
+ rescue
+ end
+ end
+
def to_index
{
ci_runners: [:token, :description],
diff --git a/db/migrate/20160227120001_add_event_field_for_web_hook.rb b/db/migrate/20160227120001_add_event_field_for_web_hook.rb
new file mode 100644
index 00000000000..65f2a47bb3c
--- /dev/null
+++ b/db/migrate/20160227120001_add_event_field_for_web_hook.rb
@@ -0,0 +1,5 @@
+class AddEventFieldForWebHook < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :wiki_page_events, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20160227120047_add_event_to_services.rb b/db/migrate/20160227120047_add_event_to_services.rb
new file mode 100644
index 00000000000..f5040d770de
--- /dev/null
+++ b/db/migrate/20160227120047_add_event_to_services.rb
@@ -0,0 +1,5 @@
+class AddEventToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :wiki_page_events, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20160310124959_add_due_date_to_issues.rb b/db/migrate/20160310124959_add_due_date_to_issues.rb
new file mode 100644
index 00000000000..ec08bd9fdfa
--- /dev/null
+++ b/db/migrate/20160310124959_add_due_date_to_issues.rb
@@ -0,0 +1,6 @@
+class AddDueDateToIssues < ActiveRecord::Migration
+ def change
+ add_column :issues, :due_date, :date
+ add_index :issues, :due_date
+ end
+end
diff --git a/db/migrate/20160412173416_add_fields_to_ci_commit.rb b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
new file mode 100644
index 00000000000..125956a3ddd
--- /dev/null
+++ b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
@@ -0,0 +1,8 @@
+class AddFieldsToCiCommit < ActiveRecord::Migration
+ def change
+ add_column :ci_commits, :status, :string
+ add_column :ci_commits, :started_at, :timestamp
+ add_column :ci_commits, :finished_at, :timestamp
+ add_column :ci_commits, :duration, :integer
+ end
+end
diff --git a/db/migrate/20160412173417_update_ci_commit.rb b/db/migrate/20160412173417_update_ci_commit.rb
new file mode 100644
index 00000000000..fd92444dbac
--- /dev/null
+++ b/db/migrate/20160412173417_update_ci_commit.rb
@@ -0,0 +1,35 @@
+class UpdateCiCommit < ActiveRecord::Migration
+ # This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers
+ # Otherwise Offline migration should be used.
+ def change
+ execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL")
+ end
+
+ private
+
+ def status
+ builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)'
+ success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')"
+ ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)"
+ pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')"
+ running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')"
+ canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')"
+
+ "(CASE
+ WHEN #{builds}=0 THEN 'skipped'
+ WHEN #{builds}=#{success}+#{ignored} THEN 'success'
+ WHEN #{builds}=#{pending} THEN 'pending'
+ WHEN #{builds}=#{canceled} THEN 'canceled'
+ WHEN #{running}+#{pending}>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+ end
+
+ def ref
+ '(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
+ end
+
+ def tag
+ '(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
+ end
+end
diff --git a/db/migrate/20160412173418_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb
new file mode 100644
index 00000000000..603d4a41610
--- /dev/null
+++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb
@@ -0,0 +1,19 @@
+class AddCiCommitIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def change
+ add_index :ci_commits, [:gl_project_id, :sha], index_options
+ add_index :ci_commits, [:gl_project_id, :status], index_options
+ add_index :ci_commits, [:status], index_options
+ end
+
+ private
+
+ def index_options
+ if Gitlab::Database.postgresql?
+ { algorithm: :concurrently }
+ else
+ { }
+ end
+ end
+end
diff --git a/db/migrate/20160413115152_add_token_to_web_hooks.rb b/db/migrate/20160413115152_add_token_to_web_hooks.rb
new file mode 100644
index 00000000000..f04225068cd
--- /dev/null
+++ b/db/migrate/20160413115152_add_token_to_web_hooks.rb
@@ -0,0 +1,5 @@
+class AddTokenToWebHooks < ActiveRecord::Migration
+ def change
+ add_column :web_hooks, :token, :string
+ end
+end
diff --git a/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb b/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb
new file mode 100644
index 00000000000..d493044c67b
--- /dev/null
+++ b/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :shared_runners_text, :text
+ end
+end
diff --git a/db/migrate/20160419120017_add_metrics_packet_size.rb b/db/migrate/20160419120017_add_metrics_packet_size.rb
new file mode 100644
index 00000000000..78c163d62ac
--- /dev/null
+++ b/db/migrate/20160419120017_add_metrics_packet_size.rb
@@ -0,0 +1,5 @@
+class AddMetricsPacketSize < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :metrics_packet_size, :integer, default: 1
+ end
+end
diff --git a/db/migrate/20160421130527_disable_repository_checks.rb b/db/migrate/20160421130527_disable_repository_checks.rb
new file mode 100644
index 00000000000..808a4b93c7c
--- /dev/null
+++ b/db/migrate/20160421130527_disable_repository_checks.rb
@@ -0,0 +1,11 @@
+class DisableRepositoryChecks < ActiveRecord::Migration
+ def up
+ change_column_default :application_settings, :repository_checks_enabled, false
+ execute 'UPDATE application_settings SET repository_checks_enabled = false'
+ end
+
+ def down
+ change_column_default :application_settings, :repository_checks_enabled, true
+ execute 'UPDATE application_settings SET repository_checks_enabled = true'
+ end
+end
diff --git a/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb
new file mode 100644
index 00000000000..aa560bc0f0c
--- /dev/null
+++ b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb
@@ -0,0 +1,5 @@
+class RemoveWallEnabledFromProjects < ActiveRecord::Migration
+ def change
+ remove_column :projects, :wall_enabled, :boolean, default: true, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7dc7b78ec6c..6db1c8acfa2 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: 20160421130527) do
+ActiveRecord::Schema.define(version: 20160508194200) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -70,16 +70,16 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "recaptcha_site_key"
t.string "recaptcha_private_key"
t.integer "metrics_port", default: 8089
+ t.boolean "akismet_enabled", default: false
+ t.string "akismet_api_key"
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
- t.boolean "akismet_enabled", default: false
- t.string "akismet_api_key"
t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility"
t.boolean "repository_checks_enabled", default: false
- t.integer "metrics_packet_size", default: 1
t.text "shared_runners_text"
+ t.integer "metrics_packet_size", default: 1
end
create_table "audit_events", force: :cascade do |t|
@@ -439,10 +439,10 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "state"
t.integer "iid"
t.integer "updated_by_id"
- t.integer "moved_to_id"
t.boolean "confidential", default: false
t.datetime "deleted_at"
t.date "due_date"
+ t.integer "moved_to_id"
end
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
@@ -646,14 +646,12 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
- t.boolean "is_award", default: false, null: false
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
- add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
@@ -729,8 +727,8 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.integer "project_id"
t.text "data"
t.text "encrypted_credentials"
- t.text "encrypted_credentials_iv"
- t.text "encrypted_credentials_salt"
+ t.string "encrypted_credentials_iv"
+ t.string "encrypted_credentials_salt"
end
create_table "projects", force: :cascade do |t|
@@ -741,7 +739,6 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.datetime "updated_at"
t.integer "creator_id"
t.boolean "issues_enabled", default: true, null: false
- t.boolean "wall_enabled", default: true, null: false
t.boolean "merge_requests_enabled", default: true, null: false
t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id"
@@ -829,9 +826,9 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.string "type"
t.string "title"
t.integer "project_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "active", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "active", default: false, null: false
t.text "properties"
t.boolean "template", default: false
t.boolean "push_events", default: true
@@ -1038,6 +1035,7 @@ ActiveRecord::Schema.define(version: 20160421130527) do
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
t.boolean "wiki_page_events", default: false, null: false
+ t.string "token"
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/doc/README.md b/doc/README.md
index e6ac4794827..e358da1c424 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -41,6 +41,8 @@
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
+- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs
+- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability
## Contributor documentation
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
new file mode 100644
index 00000000000..43d85ffb775
--- /dev/null
+++ b/doc/administration/high_availability/README.md
@@ -0,0 +1,35 @@
+# High Availability
+
+GitLab supports several different types of clustering and high-availability.
+The solution you choose will be based on the level of scalability and
+availability you require. The easiest solutions are scalable, but not necessarily
+highly available.
+
+## Architecture
+
+### Active/Passive
+
+For pure high-availability/failover with no scaling you can use an
+active/passive configuration. This utilizes DRBD (Distributed Replicated
+Block Device) to keep all data in sync. DRBD requires a low latency link to
+remain in sync. It is not advisable to attempt to run DRBD between data centers
+or in different cloud availability zones.
+
+Components/Servers Required:
+
+- 2 servers/virtual machines (one active/one passive)
+
+### Active/Active
+
+This architecture scales easily because all application servers handle
+user requests simultaneously. The database, Redis, and GitLab application are
+all deployed on separate servers. The configuration is **only** highly-available
+if the database, Redis and storage are also configured as such.
+
+**Steps to configure active/active:**
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
new file mode 100644
index 00000000000..538dada1bae
--- /dev/null
+++ b/doc/administration/high_availability/database.md
@@ -0,0 +1,116 @@
+# Configuring a Database for GitLab HA
+
+You can choose to install and manage a database server (PostgreSQL/MySQL)
+yourself, or you can use GitLab Omnibus packages to help. GitLab recommends
+PostgreSQL. This is the database that will be installed if you use the
+Omnibus package to manage your database.
+
+## Configure your own database server
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for PostgreSQL. For example, AWS offers a managed Relational
+Database Service (RDS) that runs PostgreSQL.
+
+If you use a cloud-managed service, or provide your own PostgreSQL:
+
+1. Set up a `gitlab` username with a password of your choice. The `gitlab` user
+ needs privileges to create the `gitlabhq_production` database.
+1. Configure the GitLab application servers with the appropriate details.
+ This step is covered in [Configuring GitLab for HA](gitlab.md)
+
+## Configure using Omnibus
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Disable all components except PostgreSQL
+ postgresql['enable'] = true
+ bootstrap['enable'] = false
+ nginx['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ redis['enable'] = false
+ gitlab_workhorse['enable'] = false
+ mailroom['enable'] = false
+
+ # PostgreSQL configuration
+ postgresql['sql_password'] = 'DB password'
+ postgresql['md5_auth_cidr_addresses'] = ['0.0.0.0/0']
+ postgresql['listen_address'] = '0.0.0.0'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
+
+ > **Note**: This `reconfigure` step will result in some errors.
+ That's OK - don't be alarmed.
+
+1. Open a database prompt:
+
+ ```
+ su - gitlab-psql
+ /bin/bash
+ psql -h /var/opt/gitlab/postgresql -d template1
+
+ # Output:
+
+ psql (9.2.15)
+ Type "help" for help.
+
+ template1=#
+ ```
+
+1. Run the following command at the database prompt and you will be asked to
+ enter the new password for the PostgreSQL superuser.
+
+ ```
+ \password
+
+ # Output:
+
+ Enter new password:
+ Enter it again:
+ ```
+
+1. Similarly, set the password for the `gitlab` database user. Use the same
+ password that you specified in the `/etc/gitlab/gitlab.rb` file for
+ `postgresql['sql_password']`.
+
+ ```
+ \password gitlab
+
+ # Output:
+
+ Enter new password:
+ Enter it again:
+ ```
+
+1. Enable the `pg_trgm` extension:
+ ```
+ CREATE EXTENSION pg_trgm;
+
+ # Output:
+
+ CREATE EXTENSION
+ ```
+1. Exit the database prompt by typing `\q` and Enter.
+1. Exit the `gitlab-psql` user by running `exit` twice.
+1. Run `sudo gitlab-ctl reconfigure` a final time.
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
new file mode 100644
index 00000000000..8a881ce8863
--- /dev/null
+++ b/doc/administration/high_availability/gitlab.md
@@ -0,0 +1,131 @@
+# Configuring GitLab for HA
+
+Assuming you have already configured a database, Redis, and NFS, you can
+configure the GitLab application server(s) now. Complete the steps below
+for each GitLab application server in your environment.
+
+> **Note:** There is some additional configuration near the bottom for
+ secondary GitLab application servers. It's important to read and understand
+ these additional steps before proceeding with GitLab installation.
+
+1. If necessary, install the NFS client utility packages using the following
+ commands:
+
+ ```
+ # Ubuntu/Debian
+ apt-get install nfs-common
+
+ # CentOS/Red Hat
+ yum install nfs-utils nfs-utils-lib
+ ```
+
+1. Specify the necessary NFS shares. Mounts are specified in
+ `/etc/fstab`. The exact contents of `/etc/fstab` will depend on how you chose
+ to configure your NFS server. See [NFS documentation](nfs.md) for the various
+ options. Here is an example snippet to add to `/etc/fstab`:
+
+ ```
+ 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ 10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+ ```
+
+1. Create the shared directories. These may be different depending on your NFS
+ mount locations.
+
+ ```
+ mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
+ ```
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL. Depending your the NFS configuration, you may need to change some GitLab
+ data locations. See [NFS documentation](nfs.md) for `/etc/gitlab/gitlab.rb`
+ configuration values for various scenarios. The example below assumes you've
+ added NFS mounts in the default data locations.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Prevent GitLab from starting if NFS data mounts are not available
+ high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
+
+ # Disable components that will not be on the GitLab application server
+ postgresql['enable'] = false
+ redis['enable'] = false
+
+ # PostgreSQL connection details
+ gitlab_rails['db_adapter'] = 'postgresql'
+ gitlab_rails['db_encoding'] = 'unicode'
+ gitlab_rails['db_host'] = '10.1.0.5' # IP/hostname of database server
+ gitlab_rails['db_password'] = 'DB password'
+
+ # Redis connection details
+ gitlab_rails['redis_port'] = '6379'
+ gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
+ gitlab_rails['redis_password'] = 'Redis Password'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+
+## Primary GitLab application server
+
+As a final step, run the setup rake task on the first GitLab application server.
+It is not necessary to run this on additional application servers.
+
+1. Initialize the database by running `sudo gitlab-rake gitlab:setup`.
+
+> **WARNING:** Only run this setup task on **NEW** GitLab instances because it
+ will wipe any existing data.
+
+> **Note:** When you specify `https` in the `external_url`, as in the example
+ above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
+ certificates are not present, Nginx will fail to start. See
+ [Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+ for more information.
+
+## Additional configuration for secondary GitLab application servers
+
+Secondary GitLab servers (servers configured **after** the first GitLab server)
+need some additional configuration.
+
+1. Configure shared secrets. These values can be obtained from the primary
+ GitLab server in `/etc/gitlab/gitlab-secrets.json`. Add these to
+ `/etc/gitlab/gitlab.rb` **prior to** running the first `reconfigure` in
+ the steps above.
+
+ ```ruby
+ gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
+ gitlab_rails['secret_token'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa'
+ gitlab_ci['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d'
+ gitlab_ci['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
+ ```
+
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+## Troubleshooting
+
+- `mount: wrong fs type, bad option, bad superblock on`
+
+You have not installed the necessary NFS client utilities. See step 1 above.
+
+- `mount: mount point /var/opt/gitlab/... does not exist`
+
+This particular directory does not exist on the NFS server. Ensure
+the share is exported and exists on the NFS server and try to remount.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
new file mode 100644
index 00000000000..b1fe34ed9a1
--- /dev/null
+++ b/doc/administration/high_availability/load_balancer.md
@@ -0,0 +1,63 @@
+# Load Balancer for GitLab HA
+
+In an active/active GitLab configuration, you will need a load balancer to route
+traffic to the application servers. The specifics on which load balancer to use
+or the exact configuration is beyond the scope of GitLab documentation. We hope
+that if you're managing HA systems like GitLab you have a load balancer of
+choice already. Some examples including HAProxy (open-source), F5 Big-IP LTM,
+and Citrix Net Scaler. This documentation will outline what ports and protocols
+you need to use with GitLab.
+
+## Basic ports
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 80 | 80 | HTTP |
+| 443 | 443 | HTTPS [^1] |
+| 22 | 22 | TCP |
+
+## GitLab Pages Ports
+
+If you're using GitLab Pages you will need some additional port configurations.
+GitLab Pages requires a separate VIP. Configure DNS to point the
+`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new VIP. See the
+[GitLab Pages documentation][gitlab-pages] for more information.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 80 | Varies [^2] | HTTP |
+| 443 | Varies [^2] | TCP [^3] |
+
+## Alternate SSH Port
+
+Some organizations have policies against opening SSH port 22. In this case,
+it may be helpful to configure an alternate SSH hostname that allows users
+to use SSH on port 443. An alternate SSH hostname will require a new VIP
+compared to the other GitLab HTTP configuration above.
+
+Configure DNS for an alternate SSH hostname such as altssh.gitlab.example.com.
+
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | -------- |
+| 443 | 22 | TCP |
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+
+[^1]: When using HTTPS protocol for port 443, you will need to add an SSL
+ certificate to the load balancers. If you wish to terminate SSL at the
+ GitLab application server instead, use TCP protocol.
+[^2]: The backend port for GitLab Pages depends on the
+ `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
+ setting. See [GitLab Pages documentation][gitlab-pages] for more details.
+[^3]: Port 443 for GitLab Pages should always use the TCP protocol. Users can
+ configure custom domains with custom SSL, which would not be possible
+ if SSL was terminated at the load balancer.
+
+[gitlab-pages]: http://doc.gitlab.com/ee/pages/administration.html
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
new file mode 100644
index 00000000000..e4e124e200a
--- /dev/null
+++ b/doc/administration/high_availability/nfs.md
@@ -0,0 +1,116 @@
+# NFS
+
+## Required NFS Server features
+
+**File locking**: GitLab **requires** file locking which is only supported
+natively in NFS version 4. NFSv3 also supports locking as long as
+Linux Kernel 2.6.5+ is used. We recommend using version 4 and do not
+specifically test NFSv3.
+
+**no_root_squash**: NFS normally changes the `root` user to `nobody`. This is
+a good security measure when NFS shares will be accessed by many different
+users. However, in this case only GitLab will use the NFS share so it
+is safe. GitLab requires the `no_root_squash` setting because we need to
+manage file permissions automatically. Without the setting you will receive
+errors when the Omnibus package tries to alter permissions. Note that GitLab
+and other bundled components do **not** run as `root` but as non-privileged
+users. The requirement for `no_root_squash` is to allow the Omnibus package to
+set ownership and permissions on files, as needed.
+
+### Recommended options
+
+When you define your NFS exports, we recommend you also add the following
+options:
+
+- `sync` - Force synchronous behavior. Default is asynchronous and under certain
+ circumstances it could lead to data loss if a failure occurs before data has
+ synced.
+
+## Client mount options
+
+Below is an example of an NFS mount point we use on GitLab.com:
+
+```
+10.1.1.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nobootwait,lookupcache=positive 0 2
+```
+
+Notice several options that you should consider using:
+
+| Setting | Description |
+| ------- | ----------- |
+| `nobootwait` | Don't halt boot process waiting for this mount to become available
+| `lookupcache=positive` | Tells the NFS client to honor `positive` cache results but invalidates any `negative` cache results. Negative cache results cause problems with Git. Specifically, a `git push` can fail to register uniformly across all NFS clients. The negative cache causes the clients to 'remember' that the files did not exist previously.
+
+## Mount locations
+
+When using default Omnibus configuration you will need to share 5 data locations
+between all GitLab cluster nodes. No other locations should be shared. The
+following are the 5 locations you need to mount:
+
+| Location | Description |
+| -------- | ----------- |
+| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data
+| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services
+| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments
+| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data
+| `/var/opt/gitlab/gitlab-ci/builds` | GitLab CI build traces
+
+Other GitLab directories should not be shared between nodes. They contain
+node-specific files and GitLab code that does not need to be shared. To ship
+logs to a central location consider using remote syslog. GitLab Omnibus packages
+provide configuration for [UDP log shipping][udp-log-shipping].
+
+### Consolidating mount points
+
+If you don't want to configure 5-6 different NFS mount points, you have a few
+alternative options.
+
+#### Change default file locations
+
+Omnibus allows you to configure the file locations. With custom configuration
+you can specify just one main mountpoint and have all of these locations
+as subdirectories. Mount `/gitlab-data` then use the following Omnibus
+configuration to move each data location to a subdirectory:
+
+```ruby
+user['home'] = '/gitlab-data/home'
+git_data_dir '/gitlab-data/git-data'
+gitlab_rails['shared_path'] = '/gitlab-data/shared'
+gitlab_rails['uploads_directory'] = "/gitlab-data/uploads"
+gitlab_ci['builds_directory'] = '/gitlab-data/builds'
+```
+
+To move the `git` home directory, all GitLab services must be stopped. Run
+`gitlab-ctl stop && initctl stop gitlab-runsvdir`. Then continue with the
+reconfigure.
+
+Run `sudo gitlab-ctl reconfigure` to start using the central location. Please
+be aware that if you had existing data you will need to manually copy/rsync it
+to these new locations and then restart GitLab.
+
+#### Bind mounts
+
+Bind mounts provide a way to specify just one NFS mount and then
+bind the default GitLab data locations to the NFS mount. Start by defining your
+single NFS mount point as you normally would in `/etc/fstab`. Let's assume your
+NFS mount point is `/gitlab-data`. Then, add the following bind mounts in
+`/etc/fstab`:
+
+```bash
+/gitlab-data/git-data /var/opt/gitlab/git-data none bind 0 0
+/gitlab-data/.ssh /var/opt/gitlab/.ssh none bind 0 0
+/gitlab-data/uploads /var/opt/gitlab/gitlab-rails/uploads none bind 0 0
+/gitlab-data/shared /var/opt/gitlab/gitlab-rails/shared none bind 0 0
+/gitlab-data/builds /var/opt/gitlab/gitlab-ci/builds none bind 0 0
+```
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure Redis](redis.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
+
+[udp-log-shipping]: http://doc.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
new file mode 100644
index 00000000000..d89a1e582ca
--- /dev/null
+++ b/doc/administration/high_availability/redis.md
@@ -0,0 +1,62 @@
+# Configuring Redis for GitLab HA
+
+You can choose to install and manage Redis yourself, or you can use GitLab
+Omnibus packages to help.
+
+## Configure your own Redis server
+
+If you're hosting GitLab on a cloud provider, you can optionally use a
+managed service for Redis. For example, AWS offers a managed ElastiCache service
+that runs Redis.
+
+> **Note:** Redis does not require authentication by default. See
+ [Redis Security](http://redis.io/topics/security) documentation for more
+ information. We recommend using a combination of a Redis password and tight
+ firewall rules to secure your Redis service.
+
+## Configure using Omnibus
+
+1. Download/install GitLab Omnibus using **steps 1 and 2** from
+ [GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
+ steps on the download page.
+1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
+ Be sure to change the `external_url` to match your eventual GitLab front-end
+ URL.
+
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Disable all components except PostgreSQL
+ redis['enable'] = true
+ bootstrap['enable'] = false
+ nginx['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ postgresql['enable'] = false
+ gitlab_workhorse['enable'] = false
+ mailroom['enable'] = false
+
+ # Redis configuration
+ redis['port'] = 6379
+ redis['bind'] = '0.0.0.0'
+
+ # If you wish to use Redis authentication (recommended)
+ redis['password'] = 'Redis Password'
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
+
+ > **Note**: This `reconfigure` step will result in some errors.
+ That's OK - don't be alarmed.
+1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
+ from running on upgrade. Only the primary GitLab application server should
+ handle migrations.
+
+---
+
+Read more on high-availability configuration:
+
+1. [Configure the database](database.md)
+1. [Configure NFS](nfs.md)
+1. [Configure the GitLab application servers](gitlab.md)
+1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 61bf8ce6161..3411e4af6a7 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -1,7 +1,8 @@
# Repository checks
>**Note:**
-This feature was [introduced][ce-3232] in GitLab 8.7.
+This feature was [introduced][ce-3232] in GitLab 8.7. It is OFF by
+default because it still causes too many false alarms.
Git has a built-in mechanism, [git fsck][git-fsck], to verify the
integrity of all data commited to a repository. GitLab administrators
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
new file mode 100644
index 00000000000..134a7583762
--- /dev/null
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -0,0 +1,162 @@
+# Troubleshooting Sidekiq
+
+Sidekiq is the background job processor GitLab uses to asynchronously run
+tasks. When things go wrong it can be difficult to troubleshoot. These
+situations also tend to be high-pressure because a production system job queue
+may be filling up. Users will notice when this happens because new branches
+may not show up and merge requests may not be updated. The following are some
+troubleshooting steps that will help you diagnose the bottleneck.
+
+> **Note:** GitLab administrators/users should consider working through these
+debug steps with GitLab Support so the backtraces can be analyzed by our team.
+It may reveal a bug or necessary improvement in GitLab.
+
+> **Note:** In any of the backtraces, be weary of suspecting cases where every
+ thread appears to be waiting in the database, Redis, or waiting to acquire
+ a mutex. This **may** mean there's contention in the database, for example,
+ but look for one thread that is different than the rest. This other thread
+ may be using all available CPU, or have a Ruby Global Interpreter Lock,
+ preventing other threads from continuing.
+
+## Thread dump
+
+Send the Sidekiq process ID the `TTIN` signal and it will output thread
+backtraces in the log file.
+
+```
+kill -TTIN <sidekiq_pid>
+```
+
+Check in `/var/log/gitlab/sidekiq/current` or `$GITLAB_HOME/log/sidekiq.log` for
+the backtrace output. The backtraces will be lengthy and generally start with
+several `WARN` level messages. Here's an example of a single thread's backtrace:
+
+```
+2016-04-13T06:21:20.022Z 31517 TID-orn4urby0 WARN: ActiveRecord::RecordNotFound: Couldn't find Note with 'id'=3375386
+2016-04-13T06:21:20.022Z 31517 TID-orn4urby0 WARN: /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/activerecord-4.2.5.2/lib/active_record/core.rb:155:in `find'
+/opt/gitlab/embedded/service/gitlab-rails/app/workers/new_note_worker.rb:7:in `perform'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/processor.rb:150:in `execute_job'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/processor.rb:132:in `block (2 levels) in process'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/middleware/chain.rb:127:in `block in invoke'
+/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/sidekiq_middleware/memory_killer.rb:17:in `call'
+/opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/sidekiq-4.0.1/lib/sidekiq/middleware/chain.rb:129:in `block in invoke'
+/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/sidekiq_middleware/arguments_logger.rb:6:in `call'
+...
+```
+
+In some cases Sidekiq may be hung and unable to respond to the `TTIN` signal.
+Move on to other troubleshooting methods if this happens.
+
+## Process profiling with `perf`
+
+Linux has a process profiling tool called `perf` that is helpful when a certain
+process is eating up a lot of CPU. If you see high CPU usage and Sidekiq won't
+respond to the `TTIN` signal, this is a good next step.
+
+If `perf` is not installed on your system, install it with `apt-get` or `yum`:
+
+```
+# Debian
+sudo apt-get install linux-tools
+
+# Ubuntu (may require these additional Kernel packages)
+sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`
+
+# Red Hat/CentOS
+sudo yum install perf
+```
+
+Run perf against the Sidekiq PID:
+
+```
+sudo perf record -p <sidekiq_pid>
+```
+
+Let this run for 30-60 seconds and then press Ctrl-C. Then view the perf report:
+
+```
+sudo perf report
+
+# Sample output
+Samples: 348K of event 'cycles', Event count (approx.): 280908431073
+ 97.69% ruby nokogiri.so [.] xmlXPathNodeSetMergeAndClear
+ 0.18% ruby libruby.so.2.1.0 [.] objspace_malloc_increase
+ 0.12% ruby libc-2.12.so [.] _int_malloc
+ 0.10% ruby libc-2.12.so [.] _int_free
+```
+
+Above you see sample output from a perf report. It shows that 97% of the CPU is
+being spent inside Nokogiri and `xmlXPathNodeSetMergeAndClear`. For something
+this obvious you should then go investigate what job in GitLab would use
+Nokogiri and XPath. Combine with `TTIN` or `gdb` output to show the
+corresponding Ruby code where this is happening.
+
+## The GNU Project Debugger (gdb)
+
+`gdb` can be another effective tool for debugging Sidekiq. It gives you a little
+more interactive way to look at each thread and see what's causing problems.
+
+> **Note:** Attaching to a process with `gdb` will suspends the normal operation
+ of the process (Sidekiq will not process jobs while `gdb` is attached).
+
+Start by attaching to the Sidekiq PID:
+
+```
+gdb -p <sidekiq_pid>
+```
+
+Then gather information on all the threads:
+
+```
+info threads
+
+# Example output
+30 Thread 0x7fe5fbd63700 (LWP 26060) 0x0000003f7cadf113 in poll () from /lib64/libc.so.6
+29 Thread 0x7fe5f2b3b700 (LWP 26533) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+28 Thread 0x7fe5f2a3a700 (LWP 26534) 0x0000003f7ce0ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+27 Thread 0x7fe5f2939700 (LWP 26535) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+26 Thread 0x7fe5f2838700 (LWP 26537) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+25 Thread 0x7fe5f2737700 (LWP 26538) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+24 Thread 0x7fe5f2535700 (LWP 26540) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+23 Thread 0x7fe5f2434700 (LWP 26541) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+22 Thread 0x7fe5f2232700 (LWP 26543) 0x0000003f7ce0b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
+21 Thread 0x7fe5f2131700 (LWP 26544) 0x00007fe5f7b570f0 in xmlXPathNodeSetMergeAndClear ()
+from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+...
+```
+
+If you see a suspicious thread, like the Nokogiri one above, you may want
+to get more information:
+
+```
+thread 21
+bt
+
+# Example output
+#0 0x00007ff0d6afe111 in xmlXPathNodeSetMergeAndClear () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#1 0x00007ff0d6b0b836 in xmlXPathNodeCollectAndTest () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#2 0x00007ff0d6b09037 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#3 0x00007ff0d6b09017 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#4 0x00007ff0d6b092e0 in xmlXPathCompOpEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#5 0x00007ff0d6b0bc37 in xmlXPathRunEval () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#6 0x00007ff0d6b0be5f in xmlXPathEvalExpression () from /opt/gitlab/embedded/service/gem/ruby/2.1.0/gems/nokogiri-1.6.7.2/lib/nokogiri/nokogiri.so
+#7 0x00007ff0d6a97dc3 in evaluate (argc=2, argv=0x1022d058, self=<value optimized out>) at xml_xpath_context.c:221
+#8 0x00007ff0daeab0ea in vm_call_cfunc_with_frame (th=0x1022a4f0, reg_cfp=0x1032b810, ci=<value optimized out>) at vm_insnhelper.c:1510
+```
+
+To output a backtrace from all threads at once:
+
+```
+apply all thread bt
+```
+
+## Check for blocking queries
+
+Sometimes the speed at which Sidekiq processes jobs can be so fast that it can
+cause database contention. Check for blocking queries when backtraces above
+show that many threads are stuck in the database adapter.
+
+The PostgreSQL wiki has details on the query you can run to see blocking
+queries. The query is different based on PostgreSQL version. See
+[Lock Monitoring](https://wiki.postgresql.org/wiki/Lock_Monitoring) for
+the query details.
diff --git a/doc/api/README.md b/doc/api/README.md
index 3a8fa6cebd1..ff039f1886f 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -33,6 +33,7 @@ following locations:
- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
- [Runners](runners.md)
+- [Licenses](licenses.md)
## Authentication
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 4a12e962b62..0881a7d7a90 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -101,8 +101,18 @@ DELETE /projects/:id/triggers/:token
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
-| `token` | string | yes | The `token` of a project |
+| `token` | string | yes | The `token` of a trigger |
```
curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
+
+```json
+{
+ "created_at": "2015-12-23T16:25:56.760Z",
+ "deleted_at": "2015-12-24T12:32:20.100Z",
+ "last_used": null,
+ "token": "7b9148c158980bbd9bcea92c17522d",
+ "updated_at": "2015-12-24T12:32:20.100Z"
+}
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 6341440c58b..57c2e1d9b87 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -12,6 +12,8 @@ GET /projects/:id/repository/commits
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
+| `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
+| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 3e78149f442..fc7a7ae0c0c 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -77,7 +77,8 @@ Example response:
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : [],
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -154,7 +155,8 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -216,7 +218,8 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed": false
+ "subscribed": false,
+ "user_notes_count": 1
}
```
@@ -271,7 +274,8 @@ Example response:
"description" : null,
"updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null,
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
@@ -329,7 +333,8 @@ Example response:
"id" : 85,
"assignee" : null,
"milestone" : null,
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
diff --git a/doc/api/licenses.md b/doc/api/licenses.md
new file mode 100644
index 00000000000..855b0eab56f
--- /dev/null
+++ b/doc/api/licenses.md
@@ -0,0 +1,147 @@
+# Licenses
+
+## List license templates
+
+Get all license templates.
+
+```
+GET /licenses
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `popular` | boolean | no | If passed, returns only popular licenses |
+
+```bash
+curl https://gitlab.example.com/api/v3/licenses?popular=1
+```
+
+Example response:
+
+```json
+[
+ {
+ "key": "apache-2.0",
+ "name": "Apache License 2.0",
+ "nickname": null,
+ "featured": true,
+ "html_url": "http://choosealicense.com/licenses/apache-2.0/",
+ "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html",
+ "description": "A permissive license that also provides an express grant of patent rights from contributors to users.",
+ "conditions": [
+ "include-copyright",
+ "document-changes"
+ ],
+ "permissions": [
+ "commercial-use",
+ "modifications",
+ "distribution",
+ "patent-use",
+ "private-use"
+ ],
+ "limitations": [
+ "trademark-use",
+ "no-liability"
+ ],
+ "content": " Apache License\n Version 2.0, January 2004\n [...]"
+ },
+ {
+ "key": "gpl-3.0",
+ "name": "GNU General Public License v3.0",
+ "nickname": "GNU GPLv3",
+ "featured": true,
+ "html_url": "http://choosealicense.com/licenses/gpl-3.0/",
+ "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt",
+ "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.",
+ "conditions": [
+ "include-copyright",
+ "document-changes",
+ "disclose-source",
+ "same-license"
+ ],
+ "permissions": [
+ "commercial-use",
+ "modifications",
+ "distribution",
+ "patent-use",
+ "private-use"
+ ],
+ "limitations": [
+ "no-liability"
+ ],
+ "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]"
+ },
+ {
+ "key": "mit",
+ "name": "MIT License",
+ "nickname": null,
+ "featured": true,
+ "html_url": "http://choosealicense.com/licenses/mit/",
+ "source_url": "http://opensource.org/licenses/MIT",
+ "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.",
+ "conditions": [
+ "include-copyright"
+ ],
+ "permissions": [
+ "commercial-use",
+ "modifications",
+ "distribution",
+ "private-use"
+ ],
+ "limitations": [
+ "no-liability"
+ ],
+ "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]"
+ }
+]
+```
+
+## Single license template
+
+Get a single license template. You can pass parameters to replace the license
+placeholder.
+
+```
+GET /licenses/:key
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ------ | -------- | ----------- |
+| `key` | string | yes | The key of the license template |
+| `project` | string | no | The copyrighted project name |
+| `fullname` | string | no | The full-name of the copyright holder |
+
+>**Note:**
+If you omit the `fullname` parameter but authenticate your request, the name of
+the authenticated user will be used to replace the copyright holder placeholder.
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project
+```
+
+Example response:
+
+```json
+{
+ "key": "mit",
+ "name": "MIT License",
+ "nickname": null,
+ "featured": true,
+ "html_url": "http://choosealicense.com/licenses/mit/",
+ "source_url": "http://opensource.org/licenses/MIT",
+ "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.",
+ "conditions": [
+ "include-copyright"
+ ],
+ "permissions": [
+ "commercial-use",
+ "modifications",
+ "distribution",
+ "private-use"
+ ],
+ "limitations": [
+ "no-liability"
+ ],
+ "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]"
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2057f9d77aa..8217e30fe25 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -67,7 +67,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : false
+ "subscribed" : false,
+ "user_notes_count": 1
}
]
```
@@ -130,7 +131,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -230,6 +232,7 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "user_notes_count": 1,
"changes": [
{
"old_path": "VERSION",
@@ -308,7 +311,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 0
}
```
@@ -378,7 +382,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -472,7 +477,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -537,7 +543,8 @@ Parameters:
},
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : true
+ "subscribed" : true,
+ "user_notes_count": 1
}
```
@@ -602,7 +609,8 @@ Example response:
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
- "labels" : []
+ "labels" : [],
+ "user_notes_count": 1
},
]
```
diff --git a/doc/api/users.md b/doc/api/users.md
index 7d2b4897cff..7e848586dbd 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -20,6 +20,7 @@ GET /users
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "web_url": "http://localhost:3000/u/john_smith"
},
{
"id": 2,
@@ -27,6 +28,7 @@ GET /users
"name": "Jack Smith",
"state": "blocked",
"avatar_url": "http://gravatar.com/../e32131cd8.jpeg",
+ "web_url": "http://localhost:3000/u/jack_smith"
}
]
```
@@ -45,21 +47,31 @@ GET /users
"email": "john@example.com",
"name": "John Smith",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
+ "location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "john.smith",
- "provider": "provider_name",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john.smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
- "current_sign_in_at": "2014-03-19T13:12:15Z",
- "two_factor_enabled": true
+ "can_create_project": true,
+ "two_factor_enabled": true,
+ "external": false
},
{
"id": 2,
@@ -67,24 +79,27 @@ GET /users
"email": "jack@example.com",
"name": "Jack Smith",
"state": "blocked",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
+ "web_url": "http://localhost:3000/u/jack_smith",
"created_at": "2012-05-23T08:01:01Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "jack.smith",
- "provider": "provider_name",
+ "last_sign_in_at": null,
+ "confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1,
"color_scheme_id": 3,
- "is_admin": false,
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
- "can_create_group": true,
- "can_create_project": true,
"projects_limit": 100,
"current_sign_in_at": "2014-03-19T17:54:13Z",
- "two_factor_enabled": false
+ "identities": [],
+ "can_create_group": true,
+ "can_create_project": true,
+ "two_factor_enabled": true,
+ "external": false
}
]
```
@@ -124,6 +139,7 @@ Parameters:
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
@@ -152,23 +168,31 @@ Parameters:
"email": "john@example.com",
"name": "John Smith",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
- "confirmed_at": "2012-05-23T08:00:58Z",
- "last_sign_in_at": "2015-03-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "extern_uid": "john.smith",
- "provider": "provider_name",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john.smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
"can_create_project": true,
- "projects_limit": 100
+ "two_factor_enabled": true,
+ "external": false
}
```
@@ -261,21 +285,33 @@ GET /user
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
- "private_token": "dd34asd13as",
"state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/u/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1,
"color_scheme_id": 2,
- "is_admin": false,
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john_smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
"can_create_group": true,
"can_create_project": true,
- "projects_limit": 100
+ "two_factor_enabled": true,
+ "external": false,
+ "private_token": "dd34asd13as"
}
```
diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md
index d100e261178..79761a893da 100644
--- a/doc/ci/api/builds.md
+++ b/doc/ci/api/builds.md
@@ -26,48 +26,114 @@ This API uses two types of authentication:
### Runs oldest pending build by runner
- POST /ci/api/v1/builds/register
+```
+POST /ci/api/v1/builds/register
+```
-Parameters:
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `token` | string | yes | Unique runner token |
- * `token` (required) - Unique runner token
+```
+curl -X POST "https://gitlab.example.com/ci/api/v1/builds/register" -F "token=t0k3n"
+```
### Update details of an existing build
- PUT /ci/api/v1/builds/:id
+```
+PUT /ci/api/v1/builds/:id
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|----------------------|
+| `id` | integer | yes | The ID of a project |
+| `token` | string | yes | Unique runner token |
+| `state` | string | no | The state of a build |
+| `trace` | string | no | The trace of a build |
+
+```
+curl -X PUT "https://gitlab.example.com/ci/api/v1/builds/1234" -F "token=t0k3n" -F "state=running" -F "trace=Running git clone...\n"
+```
+
+### Incremental build trace update
+
+Using this method you need to send trace content as a request body. You also need to provide the `Content-Range` header
+with a range of sent trace part. Note that you need to send parts in the proper order, so the begining of the part
+must start just after the end of the previous part. If you provide the wrong part, then GitLab CI API will return `416
+Range Not Satisfiable` response with a header `Range: 0-X`, where `X` is the current trace length.
+
+For example, if you receive `Range: 0-11` in the response, then your next part must contain a `Content-Range: 11-...`
+header and a trace part covered by this range.
+
+For a valid update API will return `202` response with:
+* `Build-Status: {status}` header containing current status of the build,
+* `Range: 0-{length}` header with the current trace length.
+
+```
+PATCH /ci/api/v1/builds/:id/trace.txt
+```
Parameters:
- * `id` (required) - The ID of a project
- * `token` (required) - Unique runner token
- * `state` (optional) - The state of a build
- * `trace` (optional) - The trace of a build
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|----------------------|
+| `id` | integer | yes | The ID of a build |
+
+Headers:
+
+| Attribute | Type | Required | Description |
+|-----------------|---------|----------|-----------------------------------|
+| `BUILD-TOKEN` | string | yes | The build authorization token |
+| `Content-Range` | string | yes | Bytes range of trace that is sent |
+
+```
+curl -X PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" -H "BUILD-TOKEN=build_t0k3n" -H "Content-Range=0-21" -d "Running git clone...\n"
+```
+
### Upload artifacts to build
- POST /ci/api/v1/builds/:id/artifacts
+```
+POST /ci/api/v1/builds/:id/artifacts
+```
-Parameters:
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|-------------------------------|
+| `id` | integer | yes | The ID of a build |
+| `token` | string | yes | The build authorization token |
+| `file` | mixed | yes | Artifacts file |
- * `id` (required) - The ID of a build
- * `token` (required) - The build authorization token
- * `file` (required) - Artifacts file
+```
+curl -X POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -F "file=@/path/to/file"
+```
### Download the artifacts file from build
- GET /ci/api/v1/builds/:id/artifacts
+```
+GET /ci/api/v1/builds/:id/artifacts
+```
-Parameters:
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|-------------------------------|
+| `id` | integer | yes | The ID of a build |
+| `token` | string | yes | The build authorization token |
- * `id` (required) - The ID of a build
- * `token` (required) - The build authorization token
+```
+curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n"
+```
### Remove the artifacts file from build
- DELETE /ci/api/v1/builds/:id/artifacts
+```
+DELETE /ci/api/v1/builds/:id/artifacts
+```
-Parameters:
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|-------------------------------|
+| ` id` | integer | yes | The ID of a build |
+| `token` | string | yes | The build authorization token |
- * ` id` (required) - The ID of a build
- * `token` (required) - The build authorization token
+```
+curl -X DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n"
+```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 4b1788a9af0..ca52a483a59 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -8,7 +8,7 @@ This is one of new trends in Continuous Integration/Deployment to:
1. create application image,
1. run test against created image,
-1. push image to remote registry,
+1. push image to remote registry,
1. deploy server from pushed image
It's also useful in case when your application already has the `Dockerfile` that can be used to create and test image:
@@ -41,27 +41,27 @@ GitLab Runner then executes build scripts as `gitlab-runner` user.
--description "My Runner"
```
-2. Install Docker on server.
+2. Install Docker Engine on server.
- For more information how to install Docker on different systems checkout the [Supported installations](https://docs.docker.com/installation/).
+ For more information how to install Docker Engine on different systems checkout the [Supported installations](https://docs.docker.com/engine/installation/).
3. Add `gitlab-runner` user to `docker` group:
-
+
```bash
$ sudo usermod -aG docker gitlab-runner
```
4. Verify that `gitlab-runner` has access to Docker:
-
+
```bash
$ sudo -u gitlab-runner -H docker info
```
-
+
You can now verify that everything works by adding `docker info` to `.gitlab-ci.yml`:
```yaml
before_script:
- docker info
-
+
build_image:
script:
- docker build -t my-docker-image .
@@ -75,37 +75,80 @@ For more information please checkout [On Docker security: `docker` group conside
## 2. Use docker-in-docker executor
-Second approach is to use special Docker image with all tools installed (`docker` and `docker-compose`) and run build script in context of that image in privileged mode.
+The second approach is to use the special Docker image with all tools installed
+(`docker` and `docker-compose`) and run the build script in context of that
+image in privileged mode.
+
In order to do that follow the steps:
1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
-1. Register GitLab Runner from command line to use `docker` and `privileged` mode:
+1. Register GitLab Runner from the command line to use `docker` and `privileged`
+ mode:
```bash
- $ sudo gitlab-runner register -n \
+ sudo gitlab-runner register -n \
--url https://gitlab.com/ci \
--token RUNNER_TOKEN \
--executor docker \
--description "My Docker Runner" \
- --docker-image "gitlab/dind:latest" \
+ --docker-image "docker:latest" \
--docker-privileged
```
-
- The above command will register new Runner to use special [gitlab/dind](https://registry.hub.docker.com/u/gitlab/dind/) image which is provided by GitLab Inc.
- The image at the start runs Docker daemon in [docker-in-docker](https://blog.docker.com/2013/09/docker-can-now-run-within-docker/) mode.
+
+ The above command will register a new Runner to use the special
+ `docker:latest` image which is provided by Docker. **Notice that it's using
+ the `privileged` mode to start the build and service containers.** If you
+ want to use [docker-in-docker] mode, you always have to use `privileged = true`
+ in your Docker containers.
+
+ The above command will create a `config.toml` entry similar to this:
+
+ ```
+ [[runners]]
+ url = "https://gitlab.com/ci"
+ token = TOKEN
+ executor = "docker"
+ [runners.docker]
+ tls_verify = false
+ image = "docker:latest"
+ privileged = true
+ disable_cache = false
+ volumes = ["/cache"]
+ [runners.cache]
+ Insecure = false
+ ```
+
+ If you want to use the Shared Runners available on your GitLab CE/EE
+ installation in order to build Docker images, then make sure that your
+ Shared Runners configuration has the `privileged` mode set to `true`.
1. You can now use `docker` from build script:
-
+
```yaml
+ image: docker:latest
+
+ services:
+ - docker:dind
+
before_script:
- - docker info
-
- build_image:
+ - docker info
+
+ build:
+ stage: build
script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
```
-1. However, by enabling `--docker-privileged` you are effectively disables all security mechanisms of containers and exposing your host to privilege escalation which can lead to container breakout.
-For more information, check out [Runtime privilege](https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration). \ No newline at end of file
+1. However, by enabling `--docker-privileged` you are effectively disabling all
+ the security mechanisms of containers and exposing your host to privilege
+ escalation which can lead to container breakout.
+
+ For more information, check out the official Docker documentation on
+ [Runtime privilege and Linux capabilities][docker-cap].
+
+An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
+
+[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
+[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index bd748f1b986..56ac2195c49 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -64,7 +64,7 @@ You can see some widely used services examples in the relevant documentation of
### How is service linked to the build
To better understand how the container linking works, read
-[Linking containers together](https://docs.docker.com/userguide/dockerlinks/).
+[Linking containers together][linking-containers].
To summarize, if you add `mysql` as service to your application, the image will
then be used to create a container that is linked to the build container.
@@ -239,8 +239,8 @@ is specific to your project.
Then create some service containers:
```
-docker run -d -n service-mysql mysql:latest
-docker run -d -n service-postgres postgres:latest
+docker run -d --name service-mysql mysql:latest
+docker run -d --name service-postgres postgres:latest
```
This will create two service containers, named `service-mysql` and
@@ -273,7 +273,7 @@ creation.
[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
-[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
-[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/
-[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/
+[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/
+[postgres-hub]: https://hub.docker.com/r/_/postgres/
+[mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index cc059dc4376..61294be599d 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -4,12 +4,12 @@
- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
- [Test a Clojure application](test-clojure-application.md)
-- [Using `dpl` as deployment tool](deployment/README.md)
+- [Using `dpl` as deployment tool](../deployment/README.md)
- Help your favorite programming language and GitLab by sending a merge request
with a guide for that language.
## Outside the documentation
-- [Blost post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
+- [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index aeadd6a448e..26953014502 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -40,7 +40,7 @@ repository with the following content:
#!/bin/bash
# We need to install dependencies only for Docker
-[[ ! -e /.dockerinit ]] && exit 0
+[[ ! -e /.dockerenv ]] && [[ ! -e /.dockerinit ]] && exit 0
set -xe
@@ -60,7 +60,7 @@ docker-php-ext-install pdo_mysql
You might wonder what `docker-php-ext-install` is. In short, it is a script
provided by the official php docker image that you can use to easilly install
extensions. For more information read the the documentation at
-<https://hub.docker.com/_/php/>.
+<https://hub.docker.com/r/_/php/>.
Now that we created the script that contains all prerequisites for our build
environment, let's add it in `.gitlab-ci.yml`:
@@ -92,7 +92,7 @@ Finally, commit your files and push them to GitLab to see your build succeeding
The final `.gitlab-ci.yml` should look similar to this:
```yaml
-# Select image from https://hub.docker.com/_/php/
+# Select image from https://hub.docker.com/r/_/php/
image: php:5.6
before_script:
@@ -278,7 +278,7 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[php-hub]: https://hub.docker.com/_/php/
+[php-hub]: https://hub.docker.com/r/_/php/
[phpenv]: https://github.com/phpenv/phpenv
[phpenv-installation]: https://github.com/phpenv/phpenv#installation
[php-example-repo]: https://gitlab.com/gitlab-examples/php
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index a236da53fe9..e4d3970deac 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -8,7 +8,7 @@ This is what the `.gitlab-ci.yml` file looks like for this project:
```yaml
test:
script:
- # this configures django application to use attached postgres database that is run on `postgres` host
+ # this configures Django application to use attached postgres database that is run on `postgres` host
- export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app
- apt-get update -qy
- apt-get install -y python-dev python-pip
@@ -37,7 +37,7 @@ production:
```
This project has three jobs:
-1. `test` - used to test rails application,
+1. `test` - used to test Django application,
2. `staging` - used to automatically deploy staging environment every push to `master` branch
3. `production` - used to automatically deploy production environmnet for every created tag
@@ -61,12 +61,12 @@ gitlab-ci-multi-runner register \
--non-interactive \
--url "https://gitlab.com/ci/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "python-3.2" \
+ --description "python-3.5" \
--executor "docker" \
- --docker-image python:3.2 \
+ --docker-image python:3.5 \
--docker-postgres latest
```
-With the command above, you create a runner that uses [python:3.2](https://registry.hub.docker.com/u/library/python/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [python:3.5](https://hub.docker.com/r/_/python/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index f5645d586ae..08c10d391ea 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -1,5 +1,5 @@
## Test and Deploy a ruby application
-This example will guide you how to run tests in your Ruby application and deploy it automatically as Heroku application.
+This example will guide you how to run tests in your Ruby on Rails application and deploy it automatically as Heroku application.
You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://gitlab.com/ayufan/ruby-getting-started/builds?scope=all).
@@ -32,7 +32,7 @@ production:
```
This project has three jobs:
-1. `test` - used to test rails application,
+1. `test` - used to test Rails application,
2. `staging` - used to automatically deploy staging environment every push to `master` branch
3. `production` - used to automatically deploy production environmnet for every created tag
@@ -62,6 +62,6 @@ gitlab-ci-multi-runner register \
--docker-postgres latest
```
-With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [ruby:2.2](https://hub.docker.com/r/_/ruby/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index c7df0713a3d..a06650b3387 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -7,6 +7,10 @@ through the coordinator API of GitLab CI.
A runner can be specific to a certain project or serve any project
in GitLab CI. A runner that serves all projects is called a shared runner.
+Ideally, GitLab Runner should not be installed on the same machine as GitLab.
+Read the [requirements documentation](../../install/requirements.md#gitlab-runner)
+for more information.
+
## Shared vs. Specific Runners
A runner that is specific only runs for the specified project. A shared runner
@@ -140,7 +144,7 @@ to it. This means that if you have shared runners setup for a project and
someone forks that project, the shared runners will also serve jobs of this
project.
-# Attack vectors in runners
+## Attack vectors in Runners
Mentioned briefly earlier, but the following things of runners can be exploited.
We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md).
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index c66d77122b2..aaf3aa77837 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -16,7 +16,7 @@ services:
- mysql:latest
variables:
- # Configure mysql environment variables (https://hub.docker.com/_/mysql/)
+ # Configure mysql environment variables (https://hub.docker.com/r/_/mysql/)
MYSQL_DATABASE: el_duderino
MYSQL_ROOT_PASSWORD: mysql_strong_password
```
@@ -114,5 +114,5 @@ available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-mysql]: https://hub.docker.com/_/mysql/
+[hub-mysql]: https://hub.docker.com/r/_/mysql/
[mysql-example-repo]: https://gitlab.com/gitlab-examples/mysql
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index 17d21dbda1c..f787cc0a124 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -110,5 +110,5 @@ available [shared runners](../runners/README.md).
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-pg]: https://hub.docker.com/_/postgres/
+[hub-pg]: https://hub.docker.com/r/_/postgres/
[postgres-example-repo]: https://gitlab.com/gitlab-examples/postgres
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index b281e8f9f60..80705024d2f 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -65,5 +65,5 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the build will begin.
-[hub-redis]: https://hub.docker.com/_/redis/
+[hub-redis]: https://hub.docker.com/r/_/redis/
[redis-example-repo]: https://gitlab.com/gitlab-examples/redis
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 7f825e6a065..7c0fb225dac 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -57,7 +57,7 @@ before_script:
# WARNING: Use this only with the Docker executor, if you use it with shell
# you will overwrite your user's SSH config.
- mkdir -p ~/.ssh
- - '[[ -f /.dockerinit ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+ - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
```
As a final step, add the _public_ key from the one you created earlier to the
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 9f7c1bfe6a0..79ed512aabb 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -85,6 +85,12 @@ curl -X POST \
In this case, the project with ID `9` will get rebuilt on `master` branch.
+Alternatively, you can pass the `token` and `ref` arguments in the query string:
+
+```bash
+curl -X POST \
+ "https://gitlab.example.com/api/v3/projects/9/trigger/builds?token=TOKEN&ref=master"
+```
### Triggering a build within `.gitlab-ci.yml`
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index b0e53cbc261..70fb81492d6 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -1,17 +1,20 @@
## Variables
+
When receiving a build from GitLab CI, the runner prepares the build environment.
It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables**
The variables can be overwritten. They take precedence over each other in this order:
+1. Trigger variables
1. Secure variables
-1. YAML-defined variables
+1. YAML-defined job-level variables
+1. YAML-defined global variables
1. Predefined variables
For example, if you define:
-1. API_TOKEN=SECURE as Secure Variable
-1. API_TOKEN=YAML as YAML-defined variable
+1. `API_TOKEN=SECURE` as Secure Variable
+1. `API_TOKEN=YAML` as YAML-defined variable
-The API_TOKEN will take the Secure Variable value: `SECURE`.
+The `API_TOKEN` will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
@@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
+Variables can be defined at a global level, but also at a job level.
+
More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md).
### User-defined variables (Secure Variables)
**This feature requires GitLab Runner 0.4.0 or higher**
-GitLab CI allows you to define per-project **Secure Variables** that are set in build environment.
+GitLab CI allows you to define per-project **Secure Variables** that are set in
+the build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
-The variables are securely passed to GitLab Runner and are available in build environment.
-It's desired method to use them for storing passwords, secret keys or whatever you want.
+The variables are securely passed to GitLab Runner and are available in the
+build environment.
+It's desired method to use them for storing passwords, secret keys or whatever
+you want.
**The value of the variable can be shown in build log if explicitly asked to do so.**
If your project is public or internal you can make the builds private.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index abb6e97e5e6..7e9bced7616 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -15,6 +15,7 @@ If you want a quick introduction to GitLab CI, follow our
- [.gitlab-ci.yml](#gitlab-ci-yml)
- [image and services](#image-and-services)
- [before_script](#before_script)
+ - [after_script](#after_script)
- [stages](#stages)
- [types](#types)
- [variables](#variables)
@@ -23,12 +24,14 @@ If you want a quick introduction to GitLab CI, follow our
- [Jobs](#jobs)
- [script](#script)
- [stage](#stage)
+ - [job variables](#job-variables)
- [only and except](#only-and-except)
- [tags](#tags)
- [when](#when)
- [artifacts](#artifacts)
- [artifacts:name](#artifacts-name)
- [dependencies](#dependencies)
+ - [before_script and after_script](#before_script-and-after_script)
- [Hidden jobs](#hidden-jobs)
- [Special YAML features](#special-yaml-features)
- [Anchors](#anchors)
@@ -80,6 +83,9 @@ services:
before_script:
- bundle install
+after_script:
+ - rm secrets
+
stages:
- build
- test
@@ -104,6 +110,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| stages | no | Define build stages |
| types | no | Alias for `stages` |
| before_script | no | Define commands that run before each job's script |
+| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables |
| cache | no | Define list of files that should be cached between subsequent runs |
@@ -118,6 +125,14 @@ used for time of the build. The configuration of this feature is covered in
`before_script` is used to define the command that should be run before all
builds, including deploy builds. This can be an array or a multi-line string.
+### after_script
+
+>**Note:**
+Introduced in GitLab 8.7 and GitLab Runner v1.2.
+
+`after_script` is used to define the command that will be run after for all
+builds. This has to be an array or a multi-line string.
+
### stages
`stages` is used to define build stages that can be used by jobs.
@@ -174,6 +189,8 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers,
thus allowing to fine tune them.
+Variables can be also defined on [job level](#job-variables).
+
### cache
>**Note:**
@@ -324,6 +341,7 @@ job_name:
| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| stage | no | Defines a build stage (default: `test`) |
| type | no | Alias for `stage` |
+| variables | no | Define build variables on a job level |
| only | no | Defines a list of git refs for which build is created |
| except | no | Defines a list of git refs for which build is not created |
| tags | no | Defines a list of tags which are used to select Runner |
@@ -332,6 +350,8 @@ job_name:
| dependencies | no | Define other builds that a build depends on so that you can pass artifacts between them|
| artifacts | no | Define list build artifacts |
| cache | no | Define list of files that should be cached between subsequent runs |
+| before_script | no | Override a set of commands that are executed before build |
+| after_script | no | Override a set of commands that are executed after build |
### script
@@ -414,6 +434,18 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
+### job variables
+
+It is possible to define build variables using a `variables` keyword on a job
+level. It works basically the same way as its global-level equivalent but
+allows you to define job-specific build variables.
+
+When the `variables` keyword is used on a job level, it overrides global YAML
+build variables and predefined variables.
+
+Build variables priority is defined in
+[variables documentation](../variables/README.md).
+
### tags
`tags` is used to select specific Runners from the list of all Runners that are
@@ -676,6 +708,23 @@ deploy:
script: make deploy
```
+### before_script and after_script
+
+It's possible to overwrite globally defined `before_script` and `after_script`:
+
+```yaml
+before_script
+- global before script
+
+job:
+ before_script:
+ - execute this instead of global before script
+ script:
+ - my command
+ after_script:
+ - execute this after my script
+```
+
## Hidden jobs
>**Note:**
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
index bd2c242afc2..c46ce2ee203 100644
--- a/doc/customization/libravatar.md
+++ b/doc/customization/libravatar.md
@@ -67,3 +67,16 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect.
In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
+
+
+## Usage examples
+
+#### For Microsoft Office 365
+
+If your users are Office 365-users, the "GetPersonaPhoto" service can be used. Note that this service requires login, so this use case is
+most useful in a corporate installation, where all users have access to Office 365.
+
+```ruby
+gitlab_rails['gravatar_plain_url'] = 'http://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=%{email}&size=HR120x120'
+gitlab_rails['gravatar_ssl_url'] = 'https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=%{email}&size=HR120x120'
+```
diff --git a/doc/development/README.md b/doc/development/README.md
index 3f3ef068f96..aa7d54c01d0 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,6 +8,7 @@
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index c1cf2e77c26..9168c70945a 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -1,12 +1,125 @@
# Instrumenting Ruby Code
-GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby
-code. This can be used to measure the time spent in a specific part of a larger
-chunk of code. The resulting data is stored as a field in the transaction that
-executed the block.
+GitLab Performance Monitoring allows instrumenting of both methods and custom
+blocks of Ruby code. Method instrumentation is the primary form of
+instrumentation with block-based instrumentation only being used when we want to
+drill down to specific regions of code within a method.
-To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure`
-and give it a name:
+## Instrumenting Methods
+
+Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation`
+module. This module offers a few different methods that can be used to
+instrument code:
+
+* `instrument_method`: instruments a single class method.
+* `instrument_instance_method`: instruments a single instance method.
+* `instrument_class_hierarchy`: given a Class this method will recursively
+ instrument all sub-classes (both class and instance methods).
+* `instrument_methods`: instruments all public class methods of a Module.
+* `instrument_instance_methods`: instruments all public instance methods of a
+ Module.
+
+To remove the need for typing the full `Gitlab::Metrics::Instrumentation`
+namespace you can use the `configure` class method. This method simply yields
+the supplied block while passing `Gitlab::Metrics::Instrumentation` as its
+argument. An example:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_method(Foo, :bar)
+ conf.instrument_method(Foo, :baz)
+end
+```
+
+Using this method is in general preferred over directly calling the various
+instrumentation methods.
+
+Method instrumentation should be added in the initializer
+`config/initializers/metrics.rb`.
+
+### Examples
+
+Instrumenting a single method:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_method(User, :find_by)
+end
+```
+
+Instrumenting an entire class hierarchy:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_class_hierarchy(ActiveRecord::Base)
+end
+```
+
+Instrumenting all public class methods:
+
+```
+Gitlab::Metrics::Instrumentation.configure do |conf|
+ conf.instrument_methods(User)
+end
+```
+
+### Checking Instrumented Methods
+
+The easiest way to check if a method has been instrumented is to check its
+source location. For example:
+
+```
+method = Rugged::TagCollection.instance_method(:[])
+
+method.source_location
+```
+
+If the source location points to `lib/gitlab/metrics/instrumentation.rb` you
+know the method has been instrumented.
+
+If you're using Pry you can use the `$` command to display the source code of a
+method (along with its source location), this is easier than running the above
+Ruby code. In case of the above snippet you'd run the following:
+
+```
+$ Rugged::TagCollection#[]
+```
+
+This will print out something along the lines of:
+
+```
+From: /path/to/your/gitlab/lib/gitlab/metrics/instrumentation.rb @ line 148:
+Owner: #<Module:0x0055f0865c6d50>
+Visibility: public
+Number of lines: 21
+
+def #{name}(#{args_signature})
+ trans = Gitlab::Metrics::Instrumentation.transaction
+
+ if trans
+ start = Time.now
+ retval = super
+ duration = (Time.now - start) * 1000.0
+
+ if duration >= Gitlab::Metrics.method_call_threshold
+ trans.increment(:method_duration, duration)
+
+ trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
+ { duration: duration },
+ method: #{label.inspect})
+ end
+
+ retval
+ else
+ super
+ end
+end
+```
+
+## Instrumenting Ruby Blocks
+
+Measuring blocks of Ruby code is done by calling `Gitlab::Metrics.measure` and
+passing it a block. For example:
```ruby
Gitlab::Metrics.measure(:foo) do
@@ -14,6 +127,10 @@ Gitlab::Metrics.measure(:foo) do
end
```
+The block is executed and the execution time is stored as a set of fields in the
+currently running transaction. If no transaction is present the block is yielded
+without measuring anything.
+
3 values are measured for a block:
1. The real time elapsed, stored in NAME_real_time.
diff --git a/doc/development/performance.md b/doc/development/performance.md
new file mode 100644
index 00000000000..fb37b3a889c
--- /dev/null
+++ b/doc/development/performance.md
@@ -0,0 +1,258 @@
+# Performance Guidelines
+
+This document describes various guidelines to follow to ensure good and
+consistent performance of GitLab.
+
+## Workflow
+
+The process of solving performance problems is roughly as follows:
+
+1. Make sure there's an issue open somewhere (e.g., on the GitLab CE issue
+ tracker), create one if there isn't. See [#15607][#15607] for an example.
+2. Measure the performance of the code in a production environment such as
+ GitLab.com (see the [Tooling](#tooling) section below). Performance should be
+ measured over a period of _at least_ 24 hours.
+3. Add your findings based on the measurement period (screenshots of graphs,
+ timings, etc) to the issue mentioned in step 1.
+4. Solve the problem.
+5. Create a merge request, assign the "performance" label and ping the right
+ people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]).
+6. Once a change has been deployed make sure to _again_ measure for at least 24
+ hours to see if your changes have any impact on the production environment.
+7. Repeat until you're done.
+
+When providing timings make sure to provide:
+
+* The 95th percentile
+* The 99th percentile
+* The mean
+
+When providing screenshots of graphs, make sure that both the X and Y axes and
+the legend are clearly visible. If you happen to have access to GitLab.com's own
+monitoring tools you should also provide a link to any relevant
+graphs/dashboards.
+
+## Tooling
+
+GitLab provides two built-in tools to aid the process of improving performance:
+
+* [Sherlock](doc/development/profiling.md#sherlock)
+* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md)
+
+GitLab employees can use GitLab.com's performance monitoring systems located at
+<http://performance.gitlab.net>, this requires you to log in using your
+`@gitlab.com` Email address. Non-GitLab employees are advised to set up their
+own InfluxDB + Grafana stack.
+
+## Benchmarks
+
+Benchmarks are almost always useless. Benchmarks usually only test small bits of
+code in isolation and often only measure the best case scenario. On top of that,
+benchmarks for libraries (e.g., a Gem) tend to be biased in favour of the
+library. After all there's little benefit to an author publishing a benchmark
+that shows they perform worse than their competitors.
+
+Benchmarks are only really useful when you need a rough (emphasis on "rough")
+understanding of the impact of your changes. For example, if a certain method is
+slow a benchmark can be used to see if the changes you're making have any impact
+on the method's performance. However, even when a benchmark shows your changes
+improve performance there's no guarantee the performance also improves in a
+production environment.
+
+When writing benchmarks you should almost always use
+[benchmark-ips](https://github.com/evanphx/benchmark-ips). Ruby's `Benchmark`
+module that comes with the standard library is rarely useful as it runs either a
+single iteration (when using `Benchmark.bm`) or two iterations (when using
+`Benchmark.bmbm`). Running this few iterations means external factors (e.g. a
+video streaming in the background) can very easily skew the benchmark
+statistics.
+
+Another problem with the `Benchmark` module is that it displays timings, not
+iterations. This means that if a piece of code completes in a very short period
+of time it can be very difficult to compare the timings before and after a
+certain change. This in turn leads to patterns such as the following:
+
+```ruby
+Benchmark.bmbm(10) do |bench|
+ bench.report 'do something' do
+ 100.times do
+ ... work here ...
+ end
+ end
+end
+```
+
+This however leads to the question: how many iterations should we run to get
+meaningful statistics?
+
+The benchmark-ips Gem basically takes care of all this and much more, and as a
+result of this should be used instead of the `Benchmark` module.
+
+In short:
+
+1. Don't trust benchmarks you find on the internet.
+2. Never make claims based on just benchmarks, always measure in production to
+ confirm your findings.
+3. X being N times faster than Y is meaningless if you don't know what impact it
+ will actually have on your production environment.
+4. A production environment is the _only_ benchmark that always tells the truth
+ (unless your performance monitoring systems are not set up correctly).
+5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's
+ `Benchmark` module.
+
+## Importance of Changes
+
+When working on performance improvements, it's important to always ask yourself
+the question "How important is it to improve the performance of this piece of
+code?". Not every piece of code is equally important and it would be a waste to
+spend a week trying to improve something that only impacts a tiny fraction of
+our users. For example, spending a week trying to squeeze 10 milliseconds out of
+a method is a waste of time when you could have spent a week squeezing out 10
+seconds elsewhere.
+
+There is no clear set of steps that you can follow to determine if a certain
+piece of code is worth optimizing. The only two things you can do are:
+
+1. Think about what the code does, how it's used, how many times it's called and
+ how much time is spent in it relative to the total execution time (e.g., the
+ total time spent in a web request).
+2. Ask others (preferably in the form of an issue).
+
+Some examples of changes that aren't really important/worth the effort:
+
+* Replacing double quotes with single quotes.
+* Replacing usage of Array with Set when the list of values is very small.
+* Replacing library A with library B when both only take up 0.1% of the total
+ execution time.
+* Calling `freeze` on every string (see [String Freezing](#string-freezing)).
+
+## Slow Operations & Sidekiq
+
+Slow operations (e.g. merging branches) or operations that are prone to errors
+(using external APIs) should be performed in a Sidekiq worker instead of
+directly in a web request as much as possible. This has numerous benefits such
+as:
+
+1. An error won't prevent the request from completing.
+2. The process being slow won't affect the loading time of a page.
+3. In case of a failure it's easy to re-try the process (Sidekiq takes care of
+ this automatically).
+4. By isolating the code from a web request it will hopefully be easier to test
+ and maintain.
+
+It's especially important to use Sidekiq as much as possible when dealing with
+Git operations as these operations can take quite some time to complete
+depending on the performance of the underlying storage system.
+
+## Git Operations
+
+Care should be taken to not run unnecessary Git operations. For example,
+retrieving the list of branch names using `Repository#branch_names` can be done
+without an explicit check if a repository exists or not. In other words, instead
+of this:
+
+```ruby
+if repository.exists?
+ repository.branch_names.each do |name|
+ ...
+ end
+end
+```
+
+You can just write:
+
+```ruby
+repository.branch_names.each do |name|
+ ...
+end
+```
+
+## Caching
+
+Operations that will often return the same result should be cached using Redis,
+in particular Git operations. When caching data in Redis, make sure the cache is
+flushed whenever needed. For example, a cache for the list of tags should be
+flushed whenever a new tag is pushed or a tag is removed.
+
+When adding cache expiration code for repositories, this code should be placed
+in one of the before/after hooks residing in the Repository class. For example,
+if a cache should be flushed after importing a repository this code should be
+added to `Repository#after_import`. This ensures the cache logic stays within
+the Repository class instead of leaking into other classes.
+
+When caching data, make sure to also memoize the result in an instance variable.
+While retrieving data from Redis is much faster than raw Git operations, it still
+has overhead. By caching the result in an instance variable, repeated calls to
+the same method won't end up retrieving data from Redis upon every call. When
+memoizing cached data in an instance variable, make sure to also reset the
+instance variable when flushing the cache. An example:
+
+
+```ruby
+def first_branch
+ @first_branch ||= cache.fetch(:first_branch) { branches.first }
+end
+
+def expire_first_branch_cache
+ cache.expire(:first_branch)
+ @first_branch = nil
+end
+```
+
+## Anti-Patterns
+
+This is a collection of [anti-patterns][anti-pattern] that should be avoided
+unless these changes have a measurable, significant and positive impact on
+production environments.
+
+### String Freezing
+
+In recent Ruby versions calling `freeze` on a String leads to it being allocated
+only once and re-used. For example, on Ruby 2.3 this will only allocate the
+"foo" String once:
+
+```ruby
+10.times do
+ 'foo'.freeze
+end
+```
+
+Blindly adding a `.freeze` call to every String is an anti-pattern that should
+be avoided unless one can prove (using production data) the call actually has a
+positive impact on performance.
+
+This feature of Ruby wasn't really meant to make things faster directly, instead
+it was meant to reduce the number of allocations. Depending on the size of the
+String and how frequently it would be allocated (before the `.freeze` call was
+added), this _may_ make things faster, but there's no guarantee it will.
+
+Another common flavour of this is to not only freeze a String, but also assign
+it to a constant, for example:
+
+```ruby
+SOME_CONSTANT = 'foo'.freeze
+
+9000.times do
+ SOME_CONSTANT
+end
+```
+
+The only reason you should be doing this is to prevent somebody from mutating
+the global String. However, since you can just re-assign constants in Ruby
+there's nothing stopping somebody from doing this elsewhere in the code:
+
+```ruby
+SOME_CONSTANT = 'bar'
+```
+
+### Moving Allocations to Constants
+
+Storing an object as a constant so you only allocate it once _may_ improve
+performance, but there's no guarantee this will. Looking up constants has an
+impact on runtime performance, and as such, using a constant instead of
+referencing an object directly may even slow code down.
+
+[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
+[yorickpeterse]: https://gitlab.com/u/yorickpeterse
+[joshfng]: https://gitlab.com/u/joshfng
+[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 672e3fb4649..33eed29ba5c 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -64,6 +64,7 @@ the command line via `bundle exec teaspoon`, or via a web browser at
methods.
- Use `context` to test branching logic.
- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
+- Don't supply the `:each` argument to hooks since it's the default.
- Prefer `not_to` to `to_not`.
- Try to match the ordering of tests to the ordering within the class.
- Try to follow the [Four-Phase Test][four-phase-test] pattern, using newlines
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
new file mode 100644
index 00000000000..3625c4191b8
--- /dev/null
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -0,0 +1,82 @@
+# Downgrading from EE to CE
+
+If you ever decide to downgrade your Enterprise Edition back to the Community
+Edition, there are a few steps you need take before installing the CE package
+on top of the current EE package, or, if you are in an installation from source,
+before you change remotes and fetch the latest CE code.
+
+## Disable Enterprise-only features
+
+First thing to do is to disable the following features.
+
+### Authentication mechanisms
+
+Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so
+you should disable these mechanisms before downgrading and you should provide
+alternative authentication methods to your users.
+
+### Git Annex
+
+Git Annex is also only available on the Enterprise Edition. This means that if
+you have repositories that use Git Annex to store large files, these files will
+no longer be easily available via Git. You should consider migrating these
+repositories to use Git LFS before downgrading to the Community Edition.
+
+### Remove Jenkins CI Service entries from the database
+
+The `JenkinsService` class is only available on the Enterprise Edition codebase,
+so if you downgrade to the Community Edition, you'll come across the following
+error:
+
+```
+Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms)
+
+ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'JenkinsService'. This
+error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this
+column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to
+use another column for that information.)
+```
+
+All services are created automatically for every project you have, so in order
+to avoid getting this error, you need to remove all instances of the
+`JenkinsService` from your database:
+
+**Omnibus Installation**
+
+```
+$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all"
+```
+
+**Source Installation**
+
+```
+$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production
+```
+
+## Downgrade to CE
+
+After performing the above mentioned steps, you are now ready to downgrade your
+GitLab installation to the Community Edition.
+
+**Omnibus Installation**
+
+To downgrade an Omnibus installation, it is sufficient to install the Community
+Edition package on top of the currently installed one. You can do this manually,
+by directly [downloading the package](https://packages.gitlab.com/gitlab/gitlab-ce)
+you need, or by adding our CE package repository and following the
+[CE installation instructions](https://about.gitlab.com/downloads/).
+
+**Source Installation**
+
+To downgrade a source installation, you need to replace the current remote of
+your GitLab installation with the Community Edition's remote, fetch the latest
+changes, and checkout the latest stable branch:
+
+```
+$ git remote set-url origin git@gitlab.com:gitlab-org/gitlab-ce.git
+$ git fetch --all
+$ git checkout 8-x-stable
+```
+
+Remember to follow the correct [update guides](../update/README.md) to make
+sure all dependencies are up to date.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index e721e70a596..e3af3022262 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -157,22 +157,64 @@ Create a `git` user for GitLab:
## 5. Database
-We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1.
+We recommend using a PostgreSQL database. For MySQL check the
+[MySQL setup guide](database_mysql.md).
- # Install the database packages
- sudo apt-get install -y postgresql postgresql-client libpq-dev
+> **Note**: because we need to make use of extensions you need at least pgsql 9.1.
- # Create a user for GitLab
+1. Install the database packages:
+
+ ```bash
+ sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
+ ```
+
+1. Create a database user for GitLab:
+
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
+ ```
+
+1. Create the GitLab production database and grant all privileges on database:
- # Create the GitLab production database & grant all privileges on database
+ ```bash
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
+ ```
+
+1. Create the `pg_trgm` extension (required for GitLab 8.6+):
+
+ ```bash
+ sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
+ ```
+
+1. Try connecting to the new database with the new user:
- # Try connecting to the new database with the new user
+ ```bash
sudo -u git -H psql -d gitlabhq_production
+ ```
+
+1. Check if the `pg_trgm` extension is enabled:
+
+ ```bash
+ SELECT true AS enabled
+ FROM pg_available_extensions
+ WHERE name = 'pg_trgm'
+ AND installed_version IS NOT NULL;
+ ```
+
+ If the extension is enabled this will produce the following output:
- # Quit the database session
+ ```
+ enabled
+ ---------
+ t
+ (1 row)
+ ```
+
+1. Quit the database session:
+
+ ```bash
gitlabhq_production> \q
+ ```
## 6. Redis
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 03cb08dd1f1..df8e8bdc476 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -64,7 +64,10 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### Memory
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
-With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
+The operating system and any other running applications will also be using memory
+so keep in mind that you need at least 2GB available before running GitLab. With
+less memory GitLab will give strange errors during the reconfigure run and 500
+errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow
@@ -77,8 +80,32 @@ With less memory GitLab will give strange errors during the reconfigure run and
- 128GB RAM supports up to 32,000 users
- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+We recommend having at least 1GB of swap on your server, even if you currently have
+enough available RAM. Having swap will help reduce the chance of errors occuring
+if your available memory changes.
+
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
+## Gitlab Runner
+
+We strongly advise against installing GitLab Runner on the same machine you plan
+to install GitLab on. Depending on how you decide to configure GitLab Runner and
+what tools you use to exercise your application in the CI environment, GitLab
+Runner can consume significant amount of available memory.
+
+Memory consumption calculations, that are available above, will not be valid if
+you decide to run GitLab Runner and the GitLab Rails application on the same
+machine.
+
+It is also not safe to install everything on a single machine, because of the
+[security reasons] - especially when you plan to use shell executor with GitLab
+Runner.
+
+We recommend using a separate machine for each GitLab Runner, if you plan to
+use the CI features.
+
+[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
+
## Unicorn Workers
It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests.
@@ -122,4 +149,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
-- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
+- Internet Explorer (IE) 11+ but please make sure that you have the `Compatibility View` mode disabled.
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 1890edd7a4c..e7497e475c9 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -9,7 +9,9 @@ GitHub will generate an application ID and secret key for you to use.
1. Navigate to your individual user settings or an organization's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
-1. Select "Applications" in the left menu.
+1. Select "OAuth applications" in the left menu.
+
+1. If you already have applications listed, switch to the "Developer applications" tab.
1. Select "Register new application".
@@ -60,12 +62,26 @@ GitHub will generate an application ID and secret key for you to use.
For installation from source:
+ For GitHub.com:
+
```
- { name: 'github', app_id: 'YOUR_APP_ID',
app_secret: 'YOUR_APP_SECRET',
args: { scope: 'user:email' } }
```
+
+ For GitHub Enterprise:
+
+ ```
+ - { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.example.com/",
+ args: { scope: 'user:email' } }
+ ```
+
+ __Replace `https://github.example.com/` with your GitHub URL.__
+
1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7.
1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
diff --git a/doc/intro/README.md b/doc/intro/README.md
index fecbbe6317b..ab298d3808e 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -25,6 +25,7 @@ Create merge requests and review code.
- [Automatically close issues from merge requests](../customization/issue_closing.md)
- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md)
- [Revert any commit](../workflow/revert_changes.md)
+- [Cherry-pick any commit](../workflow/cherry_pick_changes.md)
## Test and Deploy
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
index 90e99302210..771584268d9 100644
--- a/doc/monitoring/performance/gitlab_configuration.md
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -37,4 +37,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index a79c8d48d3b..168bd85c26a 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -59,34 +59,53 @@ This will drop you in to an InfluxDB interactive session. Copy the entire
contents below and paste it in to the interactive session:
```
-CREATE RETENTION POLICY gitlab_30d ON gitlab DURATION 30d REPLICATION 1 DEFAULT
-CREATE RETENTION POLICY seven_days ON gitlab DURATION 7d REPLICATION 1
-CREATE CONTINUOUS QUERY rails_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.rails_transaction_counts FROM rails_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS "count" INTO gitlab.seven_days.sidekiq_transaction_counts FROM sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.rails_method_call_timings FROM rails_method_calls GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS "duration_95th", percentile("duration", 99.000) AS "duration_99th", mean("duration") AS "duration_mean" INTO gitlab.seven_days.sidekiq_method_call_timings FROM sidekiq_method_calls GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.rails_method_call_timings_per_method FROM rails_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.sidekiq_method_call_timings_per_method FROM sidekiq_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY rails_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.rails_memory_usage_per_minute FROM rails_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_memory_usage_per_minute ON gitlab BEGIN SELECT percentile(value, 95.000) AS memory_95th, percentile(value, 99.000) AS memory_99th, mean(value) AS memory_mean INTO gitlab.seven_days.sidekiq_memory_usage_per_minute FROM sidekiq_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.sidekiq_file_descriptors_per_minute FROM sidekiq_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_file_descriptors_per_minute ON gitlab BEGIN SELECT sum(value) AS value INTO gitlab.seven_days.rails_file_descriptors_per_minute FROM rails_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.rails_gc_counts_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_counts_per_minute ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.seven_days.sidekiq_gc_counts_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.rails_gc_timings_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_timings_per_minute ON gitlab BEGIN SELECT percentile(total_time, 95.000) AS duration_95th, percentile(total_time, 99.000) AS duration_99th, mean(total_time) AS duration_mean INTO gitlab.seven_days.sidekiq_gc_timings_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.rails_gc_major_minor_per_minute FROM rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_major_minor_per_minute ON gitlab BEGIN SELECT sum(major_gc_count) AS major, sum(minor_gc_count) AS minor INTO gitlab.seven_days.sidekiq_gc_major_minor_per_minute FROM sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_allowed_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_allowed_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_allowed_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/allowed' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_counts_per_minute ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_internal_authorized_keys_request_counts_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_request_timings_per_minute ON gitlab BEGIN SELECT percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean("duration") AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_request_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_internal_authorized_keys_sql_timings_per_minute ON gitlab BEGIN SELECT percentile(sql_duration, 95) AS duration_95th, percentile(sql_duration, 99) AS duration_99th, mean(sql_duration) AS duration_mean INTO gitlab.seven_days.grape_internal_authorized_keys_sql_timings_per_minute FROM rails_transactions WHERE request_uri = '/api/v3/internal/authorized_keys' GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.rails_transaction_timings FROM rails_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(view_duration, 95.000) AS view_duration_95th, percentile(view_duration, 99.000) AS view_duration_99th, mean(view_duration) AS view_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.sidekiq_transaction_timings FROM sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_seven_days ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.seven_days.grape_transaction_counts FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_seven_days ON gitlab BEGIN SELECT percentile("duration", 95.000) AS duration_95th, percentile("duration", 99.000) AS duration_99th, mean("duration") AS duration_mean, percentile(sql_duration, 95.000) AS sql_duration_95th, percentile(sql_duration, 99.000) AS sql_duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(cache_duration) AS cache_duration_mean INTO gitlab.seven_days.grape_transaction_timings FROM rails_transactions WHERE action !~ /.+/ GROUP BY time(1m) END;
+CREATE RETENTION POLICY default ON gitlab DURATION 1h REPLICATION 1 DEFAULT
+CREATE RETENTION POLICY downsampled ON gitlab DURATION 7d REPLICATION 1
+CREATE CONTINUOUS QUERY grape_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY grape_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY grape_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY grape_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY grape_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.rails_file_descriptor_counts FROM gitlab."default".rails_file_descriptors GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.rails_gc_counts FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.rails_gc_timings FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.rails_memory_usage_overall FROM gitlab."default".rails_memory_usage GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY rails_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY rails_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_overall FROM gitlab."default".rails_object_counts GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_per_type FROM gitlab."default".rails_object_counts GROUP BY time(1m), type END;
+CREATE CONTINUOUS QUERY rails_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY rails_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY rails_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_view_timings_per_action_and_view FROM gitlab."default".rails_views WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action, view END;
+CREATE CONTINUOUS QUERY sidekiq_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.sidekiq_file_descriptor_counts FROM gitlab."default".sidekiq_file_descriptors GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.sidekiq_gc_counts FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.sidekiq_gc_timings FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_git_timings_per_action FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_overall FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_per_action FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_markdown_timings_overall FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^Banzai/ GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.sidekiq_memory_usage_overall FROM gitlab."default".sidekiq_memory_usage GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_action_and_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method, action END;
+CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method END;
+CREATE CONTINUOUS QUERY sidekiq_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_overall FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_per_type FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m), type END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
+CREATE CONTINUOUS QUERY sidekiq_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
+CREATE CONTINUOUS QUERY sidekiq_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_view_timings_per_action_and_view FROM gitlab."default".sidekiq_views GROUP BY time(1m), action, view END;
+CREATE CONTINUOUS QUERY web_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.web_transaction_counts_overall FROM gitlab."default".rails_transactions GROUP BY time(1m) END;
```
## Import Dashboards
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
index 63aa03985ef..c30cd2950d8 100644
--- a/doc/monitoring/performance/influxdb_configuration.md
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -181,7 +181,7 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
index d31b3788f36..41861860b6d 100644
--- a/doc/monitoring/performance/influxdb_schema.md
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -85,4 +85,4 @@ Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
-- [Grafana Install/Configuration](grafana_configuration.md
+- [Grafana Install/Configuration](grafana_configuration.md)
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
index 811c2192a19..b5e78348989 100644
--- a/doc/operations/sidekiq_memory_killer.md
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -36,5 +36,5 @@ The MemoryKiller is controlled using environment variables.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq.
-- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to 'SIGTERM'. The name of
+- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of
the final signal sent to the Sidekiq process when we want it to shut down.
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 20aa90f0d69..17bb75ececd 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -58,6 +58,9 @@ you are logged in or not.
When visiting the public page of a user, you can only see the projects which
you are privileged to.
+If the public level is restricted, user profiles are only visible to logged in users.
+
+
## Restricting the use of public or internal projects
In the Admin area under **Settings** (`/admin/application_settings`), you can
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4329ac30a1c..fa976134341 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -295,36 +295,49 @@ Deleting tmp directories...[DONE]
### Omnibus installations
-We will assume that you have installed GitLab from an omnibus package and run
-`sudo gitlab-ctl reconfigure` at least once.
+This procedure assumes that:
-First make sure your backup tar file is in `/var/opt/gitlab/backups` (or wherever `gitlab_rails['backup_path']` points to).
+- You have installed the exact same version of GitLab Omnibus with which the
+ backup was created
+- You have run `sudo gitlab-ctl reconfigure` at least once
+- GitLab is running. If not, start it using `sudo gitlab-ctl start`.
+
+First make sure your backup tar file is in the backup directory described in the
+`gitlab.rb` configuration `gitlab_rails['backup_path']`. The default is
+`/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.
+Stop the processes that are connected to the database. Leave the rest of GitLab
+running:
```shell
-# Stop processes that are connected to the database
sudo gitlab-ctl stop unicorn
sudo gitlab-ctl stop sidekiq
+# Verify
+sudo gitlab-ctl status
+```
+Next, restore the backup, specifying the timestamp of the backup you wish to
+restore:
+
+```shell
# This command will overwrite the contents of your GitLab database!
sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186
+```
-# Start GitLab
-sudo gitlab-ctl start
+Restart and check GitLab:
-# Check GitLab
+```shell
+sudo gitlab-ctl start
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.
+version of GitLab, the restore command will abort with an error. Install the
+[correct GitLab version](https://www.gitlab.com/downloads/archives/) and try again.
## Configure cron to make daily backups
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 612376e3a49..c44930a4ceb 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -4,6 +4,12 @@ Your GitLab instance can perform HTTP POST requests on the following events: `pr
System hooks can be used, e.g. for logging or changing information in a LDAP server.
+> **Note:**
+>
+> We follow the same structure from Webhooks for Push and Tag events, but we never display commits.
+>
+> Same deprecations from Webhooks are valid here.
+
## Hooks request example
**Request header**:
@@ -240,3 +246,110 @@ X-Gitlab-Event: System Hook
"user_id": 41
}
```
+
+## Push events
+
+Triggered when you push to the repository except when pushing tags.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+**Request body:**
+
+```json
+{
+ "event_name": "push",
+ "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+ "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "ref": "refs/heads/master",
+ "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "user_id": 4,
+ "user_name": "John Smith",
+ "user_email": "john@example.com",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+ "project_id": 15,
+ "project":{
+ "name":"Diaspora",
+ "description":"",
+ "web_url":"http://example.com/mike/diaspora",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "namespace":"Mike",
+ "visibility_level":0,
+ "path_with_namespace":"mike/diaspora",
+ "default_branch":"master",
+ "homepage":"http://example.com/mike/diaspora",
+ "url":"git@example.com:mike/diaspora.git",
+ "ssh_url":"git@example.com:mike/diaspora.git",
+ "http_url":"http://example.com/mike/diaspora.git"
+ },
+ "repository":{
+ "name": "Diaspora",
+ "url": "git@example.com:mike/diaspora.git",
+ "description": "",
+ "homepage": "http://example.com/mike/diaspora",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "visibility_level":0
+ },
+ "commits": [],
+ "total_commits_count": 0
+}
+```
+
+## Tag events
+
+Triggered when you create (or delete) tags to the repository.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+**Request body:**
+
+```json
+{
+ "event_name": "tag_push",
+ "before": "0000000000000000000000000000000000000000",
+ "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+ "ref": "refs/tags/v1.0.0",
+ "checkout_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
+ "user_id": 1,
+ "user_name": "John Smith",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+ "project_id": 1,
+ "project":{
+ "name":"Example",
+ "description":"",
+ "web_url":"http://example.com/jsmith/example",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:jsmith/example.git",
+ "git_http_url":"http://example.com/jsmith/example.git",
+ "namespace":"Jsmith",
+ "visibility_level":0,
+ "path_with_namespace":"jsmith/example",
+ "default_branch":"master",
+ "homepage":"http://example.com/jsmith/example",
+ "url":"git@example.com:jsmith/example.git",
+ "ssh_url":"git@example.com:jsmith/example.git",
+ "http_url":"http://example.com/jsmith/example.git"
+ },
+ "repository":{
+ "name": "Example",
+ "url": "ssh://git@example.com/jsmith/example.git",
+ "description": "",
+ "homepage": "http://example.com/jsmith/example",
+ "git_http_url":"http://example.com/jsmith/example.git",
+ "git_ssh_url":"git@example.com:jsmith/example.git",
+ "visibility_level":0
+ },
+ "commits": [],
+ "total_commits_count": 0
+}
+```
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index 8599133a726..4a2c6ea91d2 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -45,8 +45,8 @@ sudo -u git -H git checkout 8-7-stable-ee
```bash
cd /home/git/gitlab-shell
-sudo -u git -H git fetch --all
-sudo -u git -H git checkout v2.7.0
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v2.7.2
```
### 5. Update gitlab-workhorse
diff --git a/doc/update/README.md b/doc/update/README.md
index 0241f036830..a770633c9b8 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -1,18 +1,95 @@
-Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs.
+# Updating GitLab
+
+Depending on the installation method and your GitLab version, there are multiple
+update guides.
+
+There are currently 3 official ways to install GitLab:
+
+- Omnibus packages
+- Source installation
+- Docker installation
+
+Based on your installation, choose a section below that fits your needs.
+
+---
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Omnibus Packages](#omnibus-packages)
+- [Installation from source](#installation-from-source)
+- [Installation using Docker](#installation-using-docker)
+- [Upgrading between editions](#upgrading-between-editions)
+ - [Community to Enterprise Edition](#community-to-enterprise-edition)
+ - [Enterprise to Community Edition](#enterprise-to-community-edition)
+- [Miscellaneous](#miscellaneous)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Omnibus Packages
-- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/).
+- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html)
+ contains the steps needed to update an Omnibus GitLab package.
## Installation from source
-- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source.
-- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status.
-- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source.
-- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1.
+- [Upgrading Community Edition from source][source-ce] - The individual
+ upgrade guides are for those who have installed GitLab CE from source.
+- [Upgrading Enterprise Edition from source][source-ee] - The individual
+ upgrade guides are for those who have installed GitLab EE from source.
+- [Patch versions](patch_versions.md) guide includes the steps needed for a
+ patch version, eg. 6.2.0 to 6.2.1, and apply to both Community and Enterprise
+ Editions.
+
+## Installation using Docker
+
+GitLab provides official Docker images for both Community and Enterprise
+editions. They are based on the Omnibus package and instructions on how to
+update them are in [a separate document][omnidocker].
+
+## Upgrading between editions
+
+GitLab comes in two flavors: [Community Edition][ce] which is MIT licensed,
+and [Enterprise Edition][ee] which builds on top of the Community Edition and
+includes extra features mainly aimed at organizations with more than 100 users.
+
+Below you can find some guides to help you change editions easily.
+
+### Community to Enterprise Edition
+
+>**Note:**
+The following guides are for subscribers of the Enterprise Edition only.
+
+If you wish to upgrade your GitLab installation from Community to Enterprise
+Edition, follow the guides below based on the installation method:
+
+- [Source CE to EE update guides][source-ee] - Find your version, and follow the
+ `-ce-to-ee.md` guide. The steps are very similar to a version upgrade: stop
+ the server, get the code, update config files for the new functionality,
+ install libraries and do migrations, update the init script, start the
+ application and check its status.
+- [Omnibus CE to EE][omni-ce-ee] - Follow this guide to update your Omnibus
+ GitLab Community Edition to the Enterprise Edition.
+
+### Enterprise to Community Edition
+
+If you need to downgrade your Enterprise Edition installation back to Community
+Edition, you can follow [this guide][ee-ce] to make the process as smooth as
+possible.
## Miscellaneous
-- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL.
-- [MySQL installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/database_mysql.md) contains additional information about configuring GitLab to work with a MySQL database.
+- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating
+ your database from MySQL to PostgreSQL.
+- [MySQL installation guide](../install/database_mysql.md) contains additional
+ information about configuring GitLab to work with a MySQL database.
- [Restoring from backup after a failed upgrade](restore_after_failure.md)
+
+[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html
+[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update
+[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
+[ee-ce]: ../downgrade_ee_to_ce/README.md
+[ce]: https://about.gitlab.com/features/#community
+[ee]: https://about.gitlab.com/features/#enterprise
+[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index f446ed0a35b..b4283a526f3 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -47,7 +47,7 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch
-sudo -u git -H git checkout `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b `cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION`
sudo -u git -H make
```
@@ -57,10 +57,10 @@ sudo -u git -H make
cd /home/git/gitlab
# PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+sudo -u git -H bundle install --without development test mysql --with postgres --deployment
# MySQL
-sudo -u git -H bundle install --without development test postgres --deployment
+sudo -u git -H bundle install --without development test postgres --with mysql --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 22e207b6d32..c1c51302e79 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
+ "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook
```json
{
"object_kind": "tag_push",
- "ref": "refs/tags/v1.0.0",
"before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+ "ref": "refs/tags/v1.0.0",
+ "checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1,
"user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 25893f948ea..9efe41308dc 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -20,6 +20,7 @@
- [Milestones](milestones.md)
- [Merge Requests](merge_requests.md)
- [Revert changes](revert_changes.md)
+- [Cherry-pick changes](cherry_pick_changes.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
- [Merge When Build Succeeds](merge_when_build_succeeds.md)
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
new file mode 100644
index 00000000000..4a499009842
--- /dev/null
+++ b/doc/workflow/cherry_pick_changes.md
@@ -0,0 +1,53 @@
+# Cherry-pick changes
+
+>**Note:**
+This feature was [introduced][ce-3514] in GitLab 8.7.
+
+---
+
+GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
+with introducing a **Cherry-pick** button in Merge Requests and commit details.
+
+## Cherry-picking a Merge Request
+
+After the Merge Request has been merged, a **Cherry-pick** button will be available
+to cherry-pick the changes introduced by that Merge Request:
+
+![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
+
+---
+
+You can cherry-pick the changes directly into the selected branch or you can opt to
+create a new Merge Request with the cherry-pick changes:
+
+![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png)
+
+## Cherry-picking a Commit
+
+You can cherry-pick a Commit from the Commit details page:
+
+![Cherry-pick commit](img/cherry_pick_changes_commit.png)
+
+---
+
+Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes
+directly into the target branch or create a new Merge Request to cherry-pick the
+changes:
+
+![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png)
+
+---
+
+Please note that when cherry-picking merge commits, the mainline will always be the
+first parent. If you want to use a different mainline then you need to do that
+from the command line.
+
+Here is a quick example to cherry-pick a merge commit using the second parent as the
+mainline:
+
+```bash
+git cherry-pick -m 2 7a39eb0
+```
+
+[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
+[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/workflow/img/cherry_pick_changes_commit.png
new file mode 100644
index 00000000000..ae91d2cae53
--- /dev/null
+++ b/doc/workflow/img/cherry_pick_changes_commit.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/workflow/img/cherry_pick_changes_commit_modal.png
new file mode 100644
index 00000000000..f502f87677a
--- /dev/null
+++ b/doc/workflow/img/cherry_pick_changes_commit_modal.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_mr.png b/doc/workflow/img/cherry_pick_changes_mr.png
new file mode 100644
index 00000000000..59c610e620b
--- /dev/null
+++ b/doc/workflow/img/cherry_pick_changes_mr.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/workflow/img/cherry_pick_changes_mr_modal.png
new file mode 100644
index 00000000000..96a80f4726d
--- /dev/null
+++ b/doc/workflow/img/cherry_pick_changes_mr_modal.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index f693f430a42..e670e415c71 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1,7 +1,8 @@
# Import your project from GitHub to GitLab
-_**Note:** In order to enable the GitHub import setting, you should first
-enable the [GitHub integration][gh-import] in your GitLab instance._
+>**Note:**
+In order to enable the GitHub import setting, you should first
+enable the [GitHub integration][gh-import] in your GitLab instance.
At its current state, GitHub importer can import:
@@ -10,10 +11,13 @@ At its current state, GitHub importer can import:
- the issues (introduced in GitLab 7.7)
- the pull requests (introduced in GitLab 8.4)
- the wiki pages (introduced in GitLab 8.4)
+- the milestones (introduced in GitLab 8.7)
+- the labels (introduced in GitLab 8.7)
-It is not yet possible to import your labels, milestones and cross-repository
-pull requests (those from forks). We are working on improving this in the near
-future.
+With GitLab 8.7+, references to pull requests and issues are preserved.
+
+It is not yet possible to import your cross-repository pull requests (those from
+forks). We are working on improving this in the near future.
The importer page is visible when you [create a new project][new-project].
Click on the **GitHub** link and you will be redirected to GitHub for
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index ba91685a20b..83db44c10b1 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -44,7 +44,7 @@ check it into your Git repository:
```bash
git clone git@gitlab.example.com:group/project.git
-git lfs init # initialize the Git LFS project project
+git lfs install # initialize the Git LFS project project
git lfs track "*.iso" # select the file extensions that you want to treat as large files
```
@@ -152,4 +152,4 @@ If you are using OS X you can use `osxkeychain` to store and encrypt your creden
For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
More details about various methods of storing the user credentials can be found
-on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
+on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md
index 6d57b5d98cd..1b5718c91c1 100644
--- a/doc/workflow/merge_requests.md
+++ b/doc/workflow/merge_requests.md
@@ -12,9 +12,9 @@ Locate the section for your GitLab remote in the `.git/config` file. It looks li
fetch = +refs/heads/*:refs/remotes/origin/*
```
-Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section.
+Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section.
-It should looks like this:
+It should look like this:
```
[remote "origin"]
@@ -43,7 +43,7 @@ $ git checkout origin/merge-requests/1
![MR diff](merge_requests/merge_request_diff.png)
-It you add `w=1` option to URL, you can see diff without whitespace changes.
+If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes.
![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png)
diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png
index 46b3a56a59b..dfd7ee220f0 100644
--- a/doc/workflow/merge_requests/commit_compare.png
+++ b/doc/workflow/merge_requests/commit_compare.png
Binary files differ
diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png
index ed08ae91bec..f368423c746 100644
--- a/doc/workflow/merge_requests/merge_request_diff.png
+++ b/doc/workflow/merge_requests/merge_request_diff.png
Binary files differ
diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png
index 67d67a64d12..b2d03bb66f9 100644
--- a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png
+++ b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png
Binary files differ
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index 83e562d6929..beb6c53ec77 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/docker/README.md b/docker/README.md
index 7514d610aec..ee1f32adc26 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,7 +1,7 @@
# GitLab Docker images
-* The official GitLab Community Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
-* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ee/).
+* The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/).
+* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/).
* The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/)
* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
-* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#Build-Docker-image)
+* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#build-docker-image)
diff --git a/features/groups.feature b/features/groups.feature
index 419a5d3963d..49e939807b5 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -7,10 +7,6 @@ Feature: Groups
When I visit group "NonExistentGroup" page
Then page status code should be 404
- Scenario: I should have back to group button
- When I visit group "Owned" page
- Then I should see back to dashboard button
-
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
deleted file mode 100644
index a4be39b2d40..00000000000
--- a/features/project/commits/tags.feature
+++ /dev/null
@@ -1,46 +0,0 @@
-@project_commits
-Feature: Project Commits Tags
- Background:
- Given I sign in as a user
- And I own project "Shop"
- Given I visit project tags page
-
- Scenario: I can see all git tags
- Then I should see "Shop" all tags list
-
- Scenario: I create a tag
- And I click new tag link
- And I submit new tag form
- Then I should see new tag created
-
- Scenario: I create a tag with release notes
- Given I click new tag link
- And I submit new tag form with release notes
- Then I should see new tag created
- And I should see tag release notes
-
- Scenario: I create a tag with invalid name
- And I click new tag link
- And I submit new tag form with invalid name
- Then I should see new an error that tag is invalid
-
- Scenario: I create a tag with invalid reference
- And I click new tag link
- And I submit new tag form with invalid reference
- Then I should see new an error that tag ref is invalid
-
- Scenario: I create a tag that already exists
- And I click new tag link
- And I submit new tag form with tag that already exists
- Then I should see new an error that tag already exists
-
- Scenario: I delete a tag
- Given I visit tag 'v1.1.0' page
- Given I delete tag 'v1.1.0'
- Then I should not see tag 'v1.1.0'
-
- Scenario: I add release notes to the tag
- Given I visit tag 'v1.1.0' page
- When I click edit tag link
- And I fill release notes and submit form
- Then I should see tag release notes
diff --git a/features/project/create.feature b/features/project/create.feature
index 27136798e36..67336d73bf7 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -7,20 +7,8 @@ Feature: Project Create
@javascript
Scenario: User create a project
Given I sign in as a user
- When I visit new project page
- And I have an ssh key
- And fill project form with valid data
- Then I should see project page
- And I should see empty project instuctions
-
- @javascript
- Scenario: Empty project instructions
- Given I sign in as a user
And I have an ssh key
When I visit new project page
And fill project form with valid data
- Then I see empty project instuctions
- And I click on HTTP
- Then Remote url should update to http link
- And If I click on SSH
- Then Remote url should update to ssh link
+ Then I should see project page
+ And I should see empty project instructions
diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature
index 47cf774094f..960b4100ee5 100644
--- a/features/project/deploy_keys.feature
+++ b/features/project/deploy_keys.feature
@@ -21,7 +21,6 @@ Feature: Project Deploy Keys
Scenario: I add new deploy key
Given I visit project deploy keys page
- When I click 'New Deploy Key'
And I submit new deploy key
Then I should be on deploy keys page
And I should see newly created deploy key
diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature
index e07f8053fb7..49d7a3b9af2 100644
--- a/features/project/issues/filter_labels.feature
+++ b/features/project/issues/filter_labels.feature
@@ -12,6 +12,7 @@ Feature: Project Issues Filter Labels
@javascript
Scenario: I filter by one label
Given I click link "bug"
+ And I click "dropdown close button"
Then I should see "Bugfix1" in issues list
And I should see "Bugfix2" in issues list
And I should not see "Feature1" in issues list
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 1e09dbc4c8f..fdffd71de85 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -124,19 +124,6 @@ Feature: Project Source Browse Files
And I can see the replacement commit message
@javascript
- Scenario: I can create file in empty repo
- Given I own an empty project
- And I visit my empty project page
- And I create bare repo
- When I click on "add a file" link
- And I edit code
- And I fill the new file name
- And I fill the commit message
- And I click on "Commit Changes"
- Then I am redirected to the new file
- And I should see its new content
-
- @javascript
Scenario: If I enter an illegal file name I see an error message
Given I click on "New file" link in repo
And I fill the new file name with an illegal name
diff --git a/features/search.feature b/features/search.feature
index 3cd52810e59..a946a836525 100644
--- a/features/search.feature
+++ b/features/search.feature
@@ -30,11 +30,13 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project code I am looking for
When I click project "Shop" link
And I search for "rspec"
Then I should see code results for project "Shop"
+ @javascript
Scenario: I should see project issues
And project has issues
When I click project "Shop" link
@@ -43,6 +45,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project merge requests
And project has merge requests
When I click project "Shop" link
@@ -51,6 +54,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see project milestones
And project has milestones
When I click project "Shop" link
@@ -59,6 +63,7 @@ Feature: Search
Then I should see "Foo" link in the search results
And I should not see "Bar" link in the search results
+ @javascript
Scenario: I should see Wiki blobs
And project has Wiki content
When I click project "Shop" link
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index a6e574f12a9..2b23df6764b 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -31,7 +31,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
expect(page).to have_content 'Done 0'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request !#{merge_request.iid}", merge_request.title)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title)
should_see_todo(2, "John Doe mentioned you on issue ##{issue.iid}", "#{current_user.to_reference} Wdyt?")
should_see_todo(3, "John Doe assigned you issue ##{issue.iid}", issue.title)
should_see_todo(4, "Mary Jane mentioned you on issue ##{issue.iid}", issue.title)
@@ -45,7 +45,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
page.within('.nav-sidebar') { expect(page).to have_content 'Todos 3' }
expect(page).to have_content 'To do 3'
expect(page).to have_content 'Done 1'
- should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
end
step 'I click on the "Done" tab' do
@@ -54,7 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
step 'I should see all todos marked as done' do
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request !#{merge_request.iid}", merge_request.title, false)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false)
end
step 'I filter by "Enterprise"' do
@@ -82,11 +82,11 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
step 'I should not see todos related to "Merge Requests" in the list' do
- should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
end
step 'I should not see todos related to "Assignments" in the list' do
- should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
should_not_see_todo "John Doe assigned you issue ##{issue.iid}"
end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index a167d259837..f5fddab357d 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser
step 'I click on group milestones' do
- click_link 'Milestones'
+ page.within('.layout-nav') do
+ click_link 'Milestones'
+ end
end
step 'I should see group milestones index page has no milestones' do
@@ -84,7 +86,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I click on the "Labels" tab' do
- page.within('.nav-links') do
+ page.within('.content .nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index e5b7db4c5e3..483370f41c6 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup
include SharedUser
- step 'I should see back to dashboard button' do
- expect(page).to have_content 'Go to dashboard'
- end
-
step 'I should see group "Owned"' do
expect(page).to have_content '@owned'
end
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 4724a326277..3b59089a093 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
step 'the active main tab should be Audit Log' do
ensure_active_main_tab('Audit Log')
end
+
+ def ensure_active_main_tab(content)
+ expect(find('.layout-nav li.active')).to have_content(content)
+ end
end
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
deleted file mode 100644
index eff4234a44a..00000000000
--- a/features/steps/project/commits/tags.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'I should see "Shop" all tags list' do
- expect(page).to have_content "Tags"
- expect(page).to have_content "v1.0.0"
- end
-
- step 'I click new tag link' do
- click_link 'New tag'
- end
-
- step 'I submit new tag form' do
- fill_in 'tag_name', with: 'v7.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with release notes' do
- fill_in 'tag_name', with: 'v7.0'
- fill_in 'ref', with: 'master'
- fill_in 'release_description', with: 'Awesome release notes'
- click_button 'Create tag'
- end
-
- step 'I fill release notes and submit form' do
- fill_in 'release_description', with: 'Awesome release notes'
- click_button 'Save changes'
- end
-
- step 'I submit new tag form with invalid name' do
- fill_in 'tag_name', with: 'v 1.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with invalid reference' do
- fill_in 'tag_name', with: 'foo'
- fill_in 'ref', with: 'foo'
- click_button 'Create tag'
- end
-
- step 'I submit new tag form with tag that already exists' do
- fill_in 'tag_name', with: 'v1.0.0'
- fill_in 'ref', with: 'master'
- click_button 'Create tag'
- end
-
- step 'I should see new tag created' do
- expect(page).to have_content 'v7.0'
- end
-
- step 'I should see new an error that tag is invalid' do
- expect(page).to have_content 'Tag name invalid'
- end
-
- step 'I should see new an error that tag ref is invalid' do
- expect(page).to have_content 'Invalid reference name'
- end
-
- step 'I should see new an error that tag already exists' do
- expect(page).to have_content 'Tag already exists'
- end
-
- step "I visit tag 'v1.1.0' page" do
- click_link 'v1.1.0'
- end
-
- step "I delete tag 'v1.1.0'" do
- page.within('.content') do
- first('.btn-remove').click
- end
- end
-
- step "I should not see tag 'v1.1.0'" do
- page.within '.tags' do
- expect(page).not_to have_link 'v1.1.0'
- end
- end
-
- step 'I click edit tag link' do
- click_link 'Edit release notes'
- end
-
- step 'I should see tag release notes' do
- expect(page).to have_content 'Awesome release notes'
- end
-end
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 40cada6da45..2d43be5a386 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -29,8 +29,9 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
def check_author_link(email, user)
author_link = find('.commit-author-link')
+
expect(author_link['href']).to eq user_path(user)
- expect(author_link['data-original-title']).to eq email
+ expect(author_link['title']).to eq email
expect(find('.commit-author-name').text).to eq user.name
end
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 422b151eaa2..5f5f806df36 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -13,33 +13,9 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
end
- step 'I should see empty project instuctions' do
+ step 'I should see empty project instructions' do
expect(page).to have_content "git init"
expect(page).to have_content "git remote"
expect(page).to have_content Project.last.url_to_repo
end
-
- step 'I see empty project instuctions' do
- expect(page).to have_content "git init"
- expect(page).to have_content "git remote"
- expect(page).to have_content Project.last.url_to_repo
- end
-
- step 'I click on HTTP' do
- find('#clone-dropdown').click
- find('.http-selector').click
- end
-
- step 'Remote url should update to http link' do
- expect(page).to have_content "git remote add origin #{Project.last.http_url_to_repo}"
- end
-
- step 'If I click on SSH' do
- find('#clone-dropdown').click
- find('.ssh-selector').click
- end
-
- step 'Remote url should update to ssh link' do
- expect(page).to have_content "git remote add origin #{Project.last.url_to_repo}"
- end
end
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index a4d6c9a1b8e..83b9ef48392 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -8,19 +8,19 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should see project deploy key' do
- page.within '.enabled-keys' do
+ page.within '.deploy-keys' do
expect(page).to have_content deploy_key.title
end
end
step 'I should see other project deploy key' do
- page.within '.available-keys' do
+ page.within '.deploy-keys' do
expect(page).to have_content other_deploy_key.title
end
end
step 'I should see public deploy key' do
- page.within '.available-keys' do
+ page.within '.deploy-keys' do
expect(page).to have_content public_deploy_key.title
end
end
@@ -32,7 +32,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
step 'I submit new deploy key' do
fill_in "deploy_key_title", with: "laptop"
fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
- click_button "Create"
+ click_button "Add key"
end
step 'I should be on deploy keys page' do
@@ -40,7 +40,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should see newly created deploy key' do
- page.within '.enabled-keys' do
+ page.within '.deploy-keys' do
expect(page).to have_content(deploy_key.title)
end
end
@@ -56,7 +56,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should only see the same deploy key once' do
- page.within '.available-keys' do
+ page.within '.deploy-keys' do
expect(page).to have_selector('ul li', count: 1)
end
end
@@ -66,7 +66,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I click attach deploy key' do
- page.within '.available-keys' do
+ page.within '.deploy-keys' do
click_link 'Enable'
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 612bb8fd8b1..0ead83d6937 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- expect(page).to have_content "Edit merge request ##{@merge_request.id}"
+ expect(page).to have_content "Edit merge request #{@merge_request.to_reference}"
expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project"
end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 4994df589a7..b1ffe7f7b4c 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -48,12 +48,12 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'I click test hook button' do
stub_request(:post, @hook.url).to_return(status: 200)
- click_link 'Test Hook'
+ click_link 'Test'
end
step 'I click test hook button with invalid URL' do
stub_request(:post, @hook.url).to_raise(SocketError)
- click_link 'Test Hook'
+ click_link 'Test'
end
step 'hook should be triggered' do
diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index 6d50501a722..d82c6856918 100644
--- a/features/steps/project/issues/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -32,6 +32,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
page.find('.js-label-select').click
sleep 0.5
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
+ end
+
+ step 'I click "dropdown close button"' do
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 1d619a11d23..6a9a079592c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -519,7 +519,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step '"Bug NS-05" has CI status' do
project = merge_request.source_project
project.enable_ci
- ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id
+ ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch
create :ci_build, commit: ci_commit
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index e072505e5d7..c26d7a15212 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -282,8 +282,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_link 'Create empty bare repository'
end
- step 'I click on "add a file" link' do
- click_link 'adding README'
+ step 'I click on "README" link' do
+ click_link 'README'
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
diff --git a/features/steps/search.rb b/features/steps/search.rb
index 0ad837ebe1d..f885baf8453 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -35,6 +35,7 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I click project "Shop" link' do
+ click_button 'Project'
page.within '.project-filter' do
click_link project.name_with_namespace
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index c4c7672a432..cf30e23b6bd 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -10,16 +10,16 @@ module SharedBuilds
end
step 'project has a recent build' do
- @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
+ @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master')
@build = create(:ci_build_with_coverage, commit: @ci_commit)
end
step 'recent build is successful' do
- @build.update_column(:status, 'success')
+ @build.update(status: 'success')
end
step 'recent build failed' do
- @build.update_column(:status, 'failed')
+ @build.update(status: 'failed')
end
step 'project has another build that is running' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 24b3fb6eacb..a58b3cb7e16 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL
def edit_issuable
- find(:css, '.issuable-edit').click
+ find('.issuable-edit', visible: true).click
end
step 'project "Community" has "Community issue" open issue' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index b13e82f276b..ea5f9580308 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -230,7 +230,7 @@ module SharedProject
step 'project "Shop" has CI build' do
project = Project.find_by(name: "Shop")
- create :ci_commit, project: project, sha: project.commit.sha
+ create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped'
end
step 'I should see last commit with CI status' do
diff --git a/features/steps/user.rb b/features/steps/user.rb
index 3230234cb6d..b1d088f07f9 100644
--- a/features/steps/user.rb
+++ b/features/steps/user.rb
@@ -12,7 +12,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
user = User.find_by(name: 'John Doe')
project = contributed_project
- # Issue controbution
+ # Issue contribution
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(project, user, issue_params).execute
@@ -28,7 +28,7 @@ class Spinach::Features::User < Spinach::FeatureSteps
end
step 'I should see contributed projects' do
- page.within '.contributed-projects' do
+ page.within '#contributed' do
expect(page).to have_content(@contributed_project.name)
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 7d65145176b..cc1004f8005 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -57,5 +57,6 @@ module API
mount Builds
mount Variables
mount Runners
+ mount Licenses
end
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 8e74e177ea0..7388ed2f4ea 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -21,10 +21,9 @@ module API
authorize!(:read_commit_status, user_project)
not_found!('Commit') unless user_project.commit(params[:sha])
- ci_commit = user_project.ci_commit(params[:sha])
- return [] unless ci_commit
- statuses = ci_commit.statuses
+ ci_commits = user_project.ci_commits.where(sha: params[:sha])
+ statuses = ::CommitStatus.where(commit: ci_commits)
statuses = statuses.latest unless parse_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
@@ -51,7 +50,21 @@ module API
commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit
- ci_commit = @project.ensure_ci_commit(commit.sha)
+ # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline)
+ # We need to always have the pipeline object
+ # To have a valid pipeline object that can be attached to specific MR
+ # Other CI service needs to send `ref`
+ # If we don't receive it, we will attach the CommitStatus to
+ # the first found branch on that commit
+
+ ref = params[:ref]
+ unless ref
+ branches = @project.repository.branch_names_contains(commit.sha)
+ not_found! 'References for commit' if branches.none?
+ ref = branches.first
+ end
+
+ ci_commit = @project.ensure_ci_commit(commit.sha, ref)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4544a41b1e3..93a3a5ce089 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -12,14 +12,20 @@ module API
# Parameters:
# id (required) - The ID of a project
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # since (optional) - Only commits after or in this date will be returned
+ # until (optional) - Only commits before or in this date will be returned
# Example Request:
# GET /projects/:id/repository/commits
get ":id/repository/commits" do
+ datetime_attributes! :since, :until
+
page = (params[:page] || 0).to_i
per_page = (params[:per_page] || 20).to_i
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ after = params[:since]
+ before = params[:until]
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before)
present commits, with: Entities::RepoCommit
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b3769ba9c2d..f91ca9d5805 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -170,11 +170,11 @@ module API
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :upvotes, :downvotes
expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user])
end
+ expose :user_notes_count
end
class MergeRequest < ProjectEntity
@@ -188,10 +188,10 @@ module API
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
expose :merge_status
-
expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user])
end
+ expose :user_notes_count
end
class MergeRequestChanges < MergeRequest
@@ -440,5 +440,17 @@ module API
class Variable < Grape::Entity
expose :key, :value
end
+
+ class RepoLicense < Grape::Entity
+ expose :key, :name, :nickname
+ expose :featured, as: :popular
+ expose :url, as: :html_url
+ expose(:source_url) { |license| license.meta['source'] }
+ expose(:description) { |license| license.meta['description'] }
+ expose(:conditions) { |license| license.meta['conditions'] }
+ expose(:permissions) { |license| license.meta['permissions'] }
+ expose(:limitations) { |license| license.meta['limitations'] }
+ expose :content
+ end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 5bbf721321d..40c967453fb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -183,6 +183,22 @@ module API
Gitlab::Access.options_with_owner.values.include? level.to_i
end
+ # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
+ # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - An array consisting of elements that must be parseable as dates from the params hash
+ def datetime_attributes!(*keys)
+ keys.each do |key|
+ begin
+ params[key] = Time.xmlschema(params[key]) if params[key].present?
+ rescue ArgumentError
+ message = "\"" + key.to_s + "\" must be a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"
+ render_api_error!(message, 400)
+ end
+ end
+ end
+
def issuable_order_by
if params["order_by"] == 'updated_at'
'updated_at'
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 2200208b946..3ac7b50c4ce 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -23,9 +23,11 @@ module API
end
post "/allowed" do
+ Gitlab::Metrics.action = 'Grape#/internal/allowed'
+
status 200
- actor =
+ actor =
if params[:key_id]
Key.find_by(id: params[:key_id])
elsif params[:user_id]
@@ -33,7 +35,7 @@ module API
end
project_path = params[:project]
-
+
# Check for *.wiki repositories.
# Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 8aa08fd5acc..40928749481 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -24,8 +24,8 @@ module API
def create_spam_log(project, current_user, attrs)
params = attrs.merge({
- source_ip: env['REMOTE_ADDR'],
- user_agent: env['HTTP_USER_AGENT'],
+ source_ip: client_ip(env),
+ user_agent: user_agent(env),
noteable_type: 'Issue',
via_api: true
})
diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb
new file mode 100644
index 00000000000..187d2c04703
--- /dev/null
+++ b/lib/api/licenses.rb
@@ -0,0 +1,58 @@
+module API
+ # Licenses API
+ class Licenses < Grape::API
+ PROJECT_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]/xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]/xi.freeze
+
+ # Get the list of the available license templates
+ #
+ # Parameters:
+ # popular - Filter licenses to only the popular ones
+ #
+ # Example Request:
+ # GET /licenses
+ # GET /licenses?popular=1
+ get 'licenses' do
+ options = {
+ featured: params[:popular].present? ? true : nil
+ }
+ present Licensee::License.all(options), with: Entities::RepoLicense
+ end
+
+ # Get text for specific license
+ #
+ # Parameters:
+ # key (required) - The key of a license
+ # project - Copyrighted project name
+ # fullname - Full name of copyright holder
+ #
+ # Example Request:
+ # GET /licenses/mit
+ #
+ get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do
+ required_attributes! [:key]
+
+ not_found!('License') unless Licensee::License.find(params[:key])
+
+ # We create a fresh Licensee::License object since we'll modify its
+ # content in place below.
+ license = Licensee::License.new(params[:key])
+
+ license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
+ license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
+
+ fullname = params[:fullname].presence || current_user.try(:name)
+ license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
+
+ present license, with: Entities::RepoLicense
+ end
+ end
+end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 84b4d4cdd6d..132043cf3f7 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -105,7 +105,15 @@ module API
authorize! :read_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_title: @milestone.title,
+ state: 'all'
+ }
+
+ issues = IssuesFinder.new(current_user, finder_params).execute
+ present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cf9938d25a7..ccca65cbe1c 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -103,10 +103,10 @@ module API
required_attributes! [:hook_id]
begin
- @hook = ProjectHook.find(params[:hook_id])
- @hook.destroy
+ @hook = user_project.hooks.destroy(params[:hook_id])
rescue
# ProjectHook can raise Error if hook_id not found
+ not_found!("Error deleting hook #{params[:hook_id]}")
end
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 22ce3c6a066..ce1bf0d26d2 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -11,6 +11,11 @@ module API
end
not_found!
end
+
+ def snippets_for_current_user
+ finder_params = { filter: :by_project, project: user_project }
+ SnippetsFinder.new.execute(current_user, finder_params)
+ end
end
# Get a project snippets
@@ -20,7 +25,7 @@ module API
# Example Request:
# GET /projects/:id/snippets
get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
end
# Get a project snippet
@@ -31,7 +36,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id
get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
present @snippet, with: Entities::ProjectSnippet
end
@@ -73,7 +78,7 @@ module API
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
@@ -97,7 +102,7 @@ module API
# DELETE /projects/:id/snippets/:snippet_id
delete ":id/snippets/:snippet_id" do
begin
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
authorize! :update_project_snippet, @snippet
@snippet.destroy
rescue
@@ -113,7 +118,7 @@ module API
# Example Request:
# GET /projects/:id/snippets/:snippet_id/raw
get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
+ @snippet = snippets_for_current_user.find(params[:snippet_id])
env['api.format'] = :txt
content_type 'text/plain'
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index d1a10479e44..3e1ed3fe5c7 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -12,7 +12,7 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse,
+ present user_project.repository.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 0a14bac07c0..ea6fa2dc8a8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -11,6 +11,10 @@ module API
# GET /users?search=Admin
# GET /users?username=root
get do
+ unless can?(current_user, :read_users_list, nil)
+ render_api_error!("Not authorized.", 403)
+ end
+
if params[:username].present?
@users = User.where(username: params[:username])
else
@@ -36,10 +40,12 @@ module API
get ":id" do
@user = User.find(params[:id])
- if current_user.is_admin?
+ if current_user && current_user.is_admin?
present @user, with: Entities::UserFull
- else
+ elsif can?(current_user, :read_user, @user)
present @user, with: Entities::User
+ else
+ render_api_error!("User not found.", 404)
end
end
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index d179bea181e..38c4219518e 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -1,7 +1,6 @@
module Banzai
module Filter
- # HTML Filter to add a `rel="nofollow"` attribute to external links
- #
+ # HTML Filter to modify the attributes of external links
class ExternalLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |node|
@@ -15,7 +14,7 @@ module Banzai
# Skip internal links
next if link.start_with?(internal_url)
- node.set_attribute('rel', 'nofollow')
+ node.set_attribute('rel', 'nofollow noreferrer')
end
doc
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index 2732e0b5145..59c5e89c546 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -10,6 +10,9 @@ module Banzai
end
def self.user_can_see_reference?(user, node, context)
+ # It is not possible to check access rights for external issue trackers
+ return true if context[:project].try(:external_issue_tracker)
+
issue = Issue.find(node.attr('data-issue')) rescue nil
Ability.abilities.allowed?(user, :read_issue, issue)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index a2987850d03..8488a493b55 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -18,9 +18,7 @@ module Banzai
def references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
- project = project_from_ref($~[:project])
- params = label_params($~[:label_id].to_i, $~[:label_name])
- label = project.labels.find_by(params)
+ label = find_label($~[:project], $~[:label_id], $~[:label_name])
if label
yield match, label.id, $~[:project], $~
@@ -30,18 +28,12 @@ module Banzai
end
end
- def url_for_object(label, project)
- h = Gitlab::Routing.url_helpers
- h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
- end
+ def find_label(project_ref, label_id, label_name)
+ project = project_from_ref(project_ref)
+ return unless project
- def object_link_text(object, matches)
- if context[:project] == object.project
- LabelsHelper.render_colored_label(object)
- else
- LabelsHelper.render_colored_cross_project_label(object)
- end
+ label_params = label_params(label_id, label_name)
+ project.labels.find_by(label_params)
end
# Parameters to pass to `Label.find_by` based on the given arguments
@@ -55,7 +47,21 @@ module Banzai
if name
{ name: name.tr('"', '') }
else
- { id: id }
+ { id: id.to_i }
+ end
+ end
+
+ def url_for_object(label, project)
+ h = Gitlab::Routing.url_helpers
+ h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
+ only_path: context[:only_path])
+ end
+
+ def object_link_text(object, matches)
+ if context[:project] == object.project
+ LabelsHelper.render_colored_label(object)
+ else
+ LabelsHelper.render_colored_cross_project_label(object)
end
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 42dbab9d27e..ca80aac5a08 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -63,7 +63,7 @@ module Banzai
begin
uri = Addressable::URI.parse(node['href'])
- uri.scheme.strip! if uri.scheme
+ uri.scheme = uri.scheme.strip.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 4e85d2c3c74..353c4ddebf8 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -23,6 +23,8 @@ module Ci
rack_response({ 'message' => '500 Internal Server Error' }, 500)
end
+ content_type :txt, 'text/plain'
+ content_type :json, 'application/json'
format :json
helpers ::Ci::API::Helpers
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 2e9a5d311f9..607359769d1 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -50,6 +50,39 @@ module Ci
end
end
+ # Send incremental log update - Runners only
+ #
+ # Parameters:
+ # id (required) - The ID of a build
+ # Body:
+ # content of logs to append
+ # Headers:
+ # Content-Range (required) - range of content that was sent
+ # BUILD-TOKEN (required) - The build authorization token
+ # Example Request:
+ # PATCH /builds/:id/trace.txt
+ patch ":id/trace.txt" do
+ build = Ci::Build.find_by_id(params[:id])
+ not_found! unless build
+ authenticate_build_token!(build)
+ forbidden!('Build has been erased!') if build.erased?
+
+ error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range')
+ content_range = request.headers['Content-Range']
+ content_range = content_range.split('-')
+
+ current_length = build.trace_length
+ unless current_length == content_range[0].to_i
+ return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{current_length}" })
+ end
+
+ build.append_trace(request.body.read, content_range[0].to_i)
+
+ status 202
+ header 'Build-Status', build.status
+ header 'Range', "0-#{build.trace_length}"
+ end
+
# Authorize artifacts uploading for build - Runners only
#
# Parameters:
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index b7209c14148..504d3df9d34 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -4,12 +4,12 @@ module Ci
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
- ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache]
+ ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
:allow_failure, :type, :stage, :when, :artifacts, :cache,
- :dependencies]
+ :dependencies, :before_script, :after_script, :variables]
- attr_reader :before_script, :image, :services, :variables, :path, :cache
+ attr_reader :before_script, :after_script, :image, :services, :path, :cache
def initialize(config, path = nil)
@config = YAML.safe_load(config, [Symbol], [], true)
@@ -40,39 +40,49 @@ module Ci
@stages || DEFAULT_STAGES
end
+ def global_variables
+ @variables
+ end
+
+ def job_variables(name)
+ job = @jobs[name.to_sym]
+ return [] unless job
+
+ job.fetch(:variables, [])
+ end
+
private
def initial_parsing
@before_script = @config[:before_script] || []
+ @after_script = @config[:after_script]
@image = @config[:image]
@services = @config[:services]
@stages = @config[:stages] || @config[:types]
@variables = @config[:variables] || {}
@cache = @config[:cache]
+ @jobs = {}
+
@config.except!(*ALLOWED_YAML_KEYS)
+ @config.each { |name, param| add_job(name, param) }
- # anything that doesn't have script is considered as unknown
- @config.each do |name, param|
- raise ValidationError, "Unknown parameter: #{name}" unless param.is_a?(Hash) && param.has_key?(:script)
- end
+ raise ValidationError, "Please define at least one job" if @jobs.none?
+ end
- unless @config.values.any?{|job| job.is_a?(Hash)}
- raise ValidationError, "Please define at least one job"
- end
+ def add_job(name, job)
+ return if name.to_s.start_with?('.')
- @jobs = {}
- @config.each do |key, job|
- next if key.to_s.start_with?('.')
- stage = job[:stage] || job[:type] || DEFAULT_STAGE
- @jobs[key] = { stage: stage }.merge(job)
- end
+ raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
+
+ stage = job[:stage] || job[:type] || DEFAULT_STAGE
+ @jobs[name] = { stage: stage }.merge(job)
end
def build_job(name, job)
{
stage_idx: stages.index(job[:stage]),
stage: job[:stage],
- commands: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
+ commands: [job[:before_script] || @before_script, job[:script]].flatten.join("\n"),
tag_list: job[:tags] || [],
name: name,
only: job[:only],
@@ -85,23 +95,30 @@ module Ci
artifacts: job[:artifacts],
cache: job[:cache] || @cache,
dependencies: job[:dependencies],
+ after_script: job[:after_script] || @after_script,
}.compact
}
end
- def normalize_script(script)
- if script.is_a? Array
- script.join("\n")
- else
- script
+ def validate!
+ validate_global!
+
+ @jobs.each do |name, job|
+ validate_job!(name, job)
end
+
+ true
end
- def validate!
+ def validate_global!
unless validate_array_of_strings(@before_script)
raise ValidationError, "before_script should be an array of strings"
end
+ unless @after_script.nil? || validate_array_of_strings(@after_script)
+ raise ValidationError, "after_script should be an array of strings"
+ end
+
unless @image.nil? || @image.is_a?(String)
raise ValidationError, "image should be a string"
end
@@ -115,43 +132,39 @@ module Ci
end
unless @variables.nil? || validate_variables(@variables)
- raise ValidationError, "variables should be a map of key-valued strings"
+ raise ValidationError, "variables should be a map of key-value strings"
end
- if @cache
- if @cache[:key] && !validate_string(@cache[:key])
- raise ValidationError, "cache:key parameter should be a string"
- end
-
- if @cache[:untracked] && !validate_boolean(@cache[:untracked])
- raise ValidationError, "cache:untracked parameter should be an boolean"
- end
+ validate_global_cache! if @cache
+ end
- if @cache[:paths] && !validate_array_of_strings(@cache[:paths])
- raise ValidationError, "cache:paths parameter should be an array of strings"
- end
+ def validate_global_cache!
+ if @cache[:key] && !validate_string(@cache[:key])
+ raise ValidationError, "cache:key parameter should be a string"
end
- @jobs.each do |name, job|
- validate_job!(name, job)
+ if @cache[:untracked] && !validate_boolean(@cache[:untracked])
+ raise ValidationError, "cache:untracked parameter should be an boolean"
end
- true
+ if @cache[:paths] && !validate_array_of_strings(@cache[:paths])
+ raise ValidationError, "cache:paths parameter should be an array of strings"
+ end
end
def validate_job!(name, job)
validate_job_name!(name)
validate_job_keys!(name, job)
validate_job_types!(name, job)
+ validate_job_script!(name, job)
validate_job_stage!(name, job) if job[:stage]
+ validate_job_variables!(name, job) if job[:variables]
validate_job_cache!(name, job) if job[:cache]
validate_job_artifacts!(name, job) if job[:artifacts]
validate_job_dependencies!(name, job) if job[:dependencies]
end
- private
-
def validate_job_name!(name)
if name.blank? || !validate_string(name)
raise ValidationError, "job name should be non-empty string"
@@ -167,10 +180,6 @@ module Ci
end
def validate_job_types!(name, job)
- if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
- raise ValidationError, "#{name} job: script should be a string or an array of a strings"
- end
-
if job[:image] && !validate_string(job[:image])
raise ValidationError, "#{name} job: image should be a string"
end
@@ -200,12 +209,33 @@ module Ci
end
end
+ def validate_job_script!(name, job)
+ if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
+ raise ValidationError, "#{name} job: script should be a string or an array of a strings"
+ end
+
+ if job[:before_script] && !validate_array_of_strings(job[:before_script])
+ raise ValidationError, "#{name} job: before_script should be an array of strings"
+ end
+
+ if job[:after_script] && !validate_array_of_strings(job[:after_script])
+ raise ValidationError, "#{name} job: after_script should be an array of strings"
+ end
+ end
+
def validate_job_stage!(name, job)
unless job[:stage].is_a?(String) && job[:stage].in?(stages)
raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
end
end
+ def validate_job_variables!(name, job)
+ unless validate_variables(job[:variables])
+ raise ValidationError,
+ "#{name} job: variables should be a map of key-value strings"
+ end
+ end
+
def validate_job_cache!(name, job)
if job[:cache][:key] && !validate_string(job[:cache][:key])
raise ValidationError, "#{name} job: cache:key parameter should be a string"
diff --git a/lib/ci/status.rb b/lib/ci/status.rb
deleted file mode 100644
index 3fb1fe29494..00000000000
--- a/lib/ci/status.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
- class Status
- def self.get_status(statuses)
- if statuses.none?
- 'skipped'
- elsif statuses.all? { |status| status.success? || status.ignored? }
- 'success'
- elsif statuses.all?(&:pending?)
- 'pending'
- elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
- 'running'
- elsif statuses.all?(&:canceled?)
- 'canceled'
- else
- 'failed'
- end
- end
- end
-end
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
index 2eae55e534b..440dd44ece7 100644
--- a/lib/file_size_validator.rb
+++ b/lib/file_size_validator.rb
@@ -1,9 +1,9 @@
class FileSizeValidator < ActiveModel::EachValidator
- MESSAGES = { is: :wrong_size, minimum: :size_too_small, maximum: :size_too_big }.freeze
- CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
+ MESSAGES = { is: :wrong_size, minimum: :size_too_small, maximum: :size_too_big }.freeze
+ CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
- DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
- RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
+ DEFAULT_TOKENIZER = -> (value) { value.split(//) }.freeze
+ RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long].freeze
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
index b366c89889e..04676fdb748 100644
--- a/lib/gitlab/akismet_helper.rb
+++ b/lib/gitlab/akismet_helper.rb
@@ -9,14 +9,22 @@ module Gitlab
Gitlab.config.gitlab.url)
end
+ def client_ip(env)
+ env['action_dispatch.remote_ip'].to_s
+ end
+
+ def user_agent(env)
+ env['HTTP_USER_AGENT']
+ end
+
def check_for_spam?(project, user)
akismet_enabled? && !project.team.member?(user)
end
def is_spam?(environment, user, text)
client = akismet_client
- ip_address = environment['REMOTE_ADDR']
- user_agent = environment['HTTP_USER_AGENT']
+ ip_address = client_ip(environment)
+ user_agent = user_agent(environment)
params = {
type: 'comment',
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 5e2fb863a8f..132f9cd1966 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -79,24 +79,6 @@ module Gitlab
'rm-project', "#{name}.git"])
end
- # Add repository tag from passed ref
- #
- # path - project path with namespace
- # tag_name - new tag name
- # ref - HEAD for new tag
- # message - optional message for tag (annotated tag)
- #
- # Ex.
- # add_tag("gitlab/gitlab-ci", "v4.0", "master")
- # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message")
- #
- def add_tag(path, tag_name, ref, message = nil)
- cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
- #{tag_name} #{ref})
- cmd << message unless message.nil? || message.empty?
- Gitlab::Utils.system_silent(cmd)
- end
-
# Gc repository
#
# path - project path with namespace
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
index 9bb507b5edd..9b83292ef33 100644
--- a/lib/gitlab/bitbucket_import/client.rb
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -12,7 +12,7 @@ module Gitlab
token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret]
new(token, token_secret)
else
- raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{project.id}"
end
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 3ed1eec517c..d2a0e316cbe 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -122,20 +122,25 @@ module Gitlab
build_status_object(true)
end
+ def can_user_do_action?(action)
+ @permission_cache ||= {}
+ @permission_cache[action] ||= user.can?(action, project)
+ end
+
def change_access_check(change)
oldrev, newrev, ref = change.split(' ')
action =
if project.protected_branch?(branch_name(ref))
protected_branch_action(oldrev, newrev, branch_name(ref))
- elsif protected_tag?(tag_name(ref))
+ elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref)
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
:push_code
end
- unless user.can?(action, project)
+ unless can_user_do_action?(action)
status =
case action
when :force_push_code_to_protected_branches
@@ -176,7 +181,7 @@ module Gitlab
end
def protected_tag?(tag_name)
- project.repository.tag_names.include?(tag_name)
+ project.repository.tag_exists?(tag_name)
end
def user_allowed?
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 74d1529e1ff..67988ea3460 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -7,12 +7,19 @@ module Gitlab
@client = ::OAuth2::Client.new(
config.app_id,
config.app_secret,
- github_options
+ github_options.merge(ssl: { verify: config['verify_ssl'] })
)
if access_token
::Octokit.auto_paginate = true
- @api = ::Octokit::Client.new(access_token: access_token)
+
+ @api = ::Octokit::Client.new(
+ access_token: access_token,
+ api_endpoint: github_options[:site],
+ connection_options: {
+ ssl: { verify: config['verify_ssl'] }
+ }
+ )
end
end
@@ -42,11 +49,11 @@ module Gitlab
private
def config
- Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"}
+ Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
end
def github_options
- OmniAuth::Strategies::GitHub.default_options[:client_options].to_h.symbolize_keys
+ config["args"]["client_options"].deep_symbolize_keys
end
end
end
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 7d58e53991a..7d679eaec6a 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -28,13 +28,26 @@ module Gitlab
end
def line_code
- if on_diff?
- Gitlab::Diff::LineCode.generate(raw_data.path, raw_data.position, 0)
- end
+ return unless on_diff?
+
+ parsed_lines = Gitlab::Diff::Parser.new.parse(diff_hunk.lines)
+ generate_line_code(parsed_lines.to_a.last)
+ end
+
+ def generate_line_code(line)
+ Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
def on_diff?
- raw_data.path && raw_data.position
+ diff_hunk.present?
+ end
+
+ def diff_hunk
+ raw_data.diff_hunk
+ end
+
+ def file_path
+ raw_data.path
end
def note
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 0b1ed510229..0f9e3ee14ee 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -16,7 +16,8 @@ module Gitlab
end
def execute
- import_issues && import_pull_requests && import_wiki
+ import_labels && import_milestones && import_issues &&
+ import_pull_requests && import_wiki
end
private
@@ -25,6 +26,26 @@ module Gitlab
@import_data_credentials ||= project.import_data.credentials if project.import_data
end
+ def import_labels
+ client.labels(project.import_source).each do |raw_data|
+ Label.create!(LabelFormatter.new(project, raw_data).attributes)
+ end
+
+ true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
+ end
+
+ def import_milestones
+ client.list_milestones(project.import_source, state: :all).each do |raw_data|
+ Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
+ end
+
+ true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
+ end
+
def import_issues
client.list_issues(project.import_source, state: :all,
sort: :created,
@@ -33,6 +54,7 @@ module Gitlab
if gh_issue.valid?
issue = Issue.create!(gh_issue.attributes)
+ apply_labels(gh_issue.number, issue)
if gh_issue.has_comments?
import_comments(gh_issue.number, issue)
@@ -55,6 +77,7 @@ module Gitlab
merge_request = MergeRequest.new(pull_request.attributes)
if merge_request.save
+ apply_labels(pull_request.number, merge_request)
import_comments(pull_request.number, merge_request)
import_comments_on_diff(pull_request.number, merge_request)
end
@@ -66,6 +89,18 @@ module Gitlab
raise Projects::ImportService::Error, e.message
end
+ def apply_labels(number, issuable)
+ issue = client.issue(project.import_source, number)
+
+ if issue.labels.count > 0
+ label_ids = issue.labels.map do |raw|
+ Label.find_by(LabelFormatter.new(project, raw).attributes).try(:id)
+ end
+
+ issuable.update_attribute(:label_ids, label_ids)
+ end
+ end
+
def import_comments(issue_number, noteable)
comments = client.issue_comments(project.import_source, issue_number)
create_comments(comments, noteable)
diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb
index 1e3ba44f27c..c8173913b4e 100644
--- a/lib/gitlab/github_import/issue_formatter.rb
+++ b/lib/gitlab/github_import/issue_formatter.rb
@@ -3,7 +3,9 @@ module Gitlab
class IssueFormatter < BaseFormatter
def attributes
{
+ iid: number,
project: project,
+ milestone: milestone,
title: raw_data.title,
description: description,
state: state,
@@ -54,6 +56,12 @@ module Gitlab
@formatter.author_line(author) + body
end
+ def milestone
+ if raw_data.milestone.present?
+ project.milestones.find_by(iid: raw_data.milestone.number)
+ end
+ end
+
def state
raw_data.state == 'closed' ? 'closed' : 'opened'
end
diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb
new file mode 100644
index 00000000000..c2b9d40b511
--- /dev/null
+++ b/lib/gitlab/github_import/label_formatter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module GithubImport
+ class LabelFormatter < BaseFormatter
+ def attributes
+ {
+ project: project,
+ title: title,
+ color: color
+ }
+ end
+
+ private
+
+ def color
+ "##{raw_data.color}"
+ end
+
+ def title
+ raw_data.name
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/milestone_formatter.rb b/lib/gitlab/github_import/milestone_formatter.rb
new file mode 100644
index 00000000000..e91a7e328cf
--- /dev/null
+++ b/lib/gitlab/github_import/milestone_formatter.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module GithubImport
+ class MilestoneFormatter < BaseFormatter
+ def attributes
+ {
+ iid: number,
+ project: project,
+ title: title,
+ description: description,
+ due_date: due_date,
+ state: state,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+ end
+
+ private
+
+ def number
+ raw_data.number
+ end
+
+ def title
+ raw_data.title
+ end
+
+ def description
+ raw_data.description
+ end
+
+ def due_date
+ raw_data.due_on
+ end
+
+ def state
+ raw_data.state == 'closed' ? 'closed' : 'active'
+ end
+
+ def created_at
+ raw_data.created_at
+ end
+
+ def updated_at
+ state == 'closed' ? raw_data.closed_at : raw_data.updated_at
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index 4e507b090e8..d21b942ad4b 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -3,6 +3,7 @@ module Gitlab
class PullRequestFormatter < BaseFormatter
def attributes
{
+ iid: number,
title: raw_data.title,
description: description,
source_project: source_project,
@@ -10,6 +11,7 @@ module Gitlab
target_project: target_project,
target_branch: target_branch.name,
state: state,
+ milestone: milestone,
author_id: author_id,
assignee_id: assignee_id,
created_at: raw_data.created_at,
@@ -57,6 +59,12 @@ module Gitlab
formatter.author_line(author) + body
end
+ def milestone
+ if raw_data.milestone.present?
+ project.milestones.find_by(iid: raw_data.milestone.number)
+ end
+ end
+
def source_project
project
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 5ebaad6ca6e..ab900b641c4 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -6,6 +6,7 @@ module Gitlab
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
+ gon.shortcuts_path = help_shortcuts_path
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if current_user
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index cac76442321..280120b0f9e 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,7 +1,8 @@
module Gitlab
class Highlight
- def self.highlight(blob_name, blob_content, nowrap: true)
- new(blob_name, blob_content, nowrap: nowrap).highlight(blob_content, continue: false)
+ def self.highlight(blob_name, blob_content, nowrap: true, plain: false)
+ new(blob_name, blob_content, nowrap: nowrap).
+ highlight(blob_content, continue: false, plain: plain)
end
def self.highlight_lines(repository, ref, file_name)
@@ -17,8 +18,12 @@ module Gitlab
@lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
end
- def highlight(text, continue: true)
- @formatter.format(@lexer.lex(text, continue: continue)).html_safe
+ def highlight(text, continue: true, plain: false)
+ if plain
+ @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+ else
+ @formatter.format(@lexer.lex(text, continue: continue)).html_safe
+ end
rescue
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 484970c5a10..49f702f91f6 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -14,7 +14,8 @@ module Gitlab
method_call_threshold: current_application_settings[:metrics_method_call_threshold],
host: current_application_settings[:metrics_host],
port: current_application_settings[:metrics_port],
- sample_interval: current_application_settings[:metrics_sample_interval] || 15
+ sample_interval: current_application_settings[:metrics_sample_interval] || 15,
+ packet_size: current_application_settings[:metrics_packet_size] || 1
}
end
@@ -41,9 +42,9 @@ module Gitlab
prepared = prepare_metrics(metrics)
pool.with do |connection|
- prepared.each do |metric|
+ prepared.each_slice(settings[:packet_size]) do |slice|
begin
- connection.write_points([metric])
+ connection.write_points(slice)
rescue StandardError
end
end
@@ -114,6 +115,15 @@ module Gitlab
trans.add_tag(name, value) if trans
end
+ # Sets the action of the current transaction (if any)
+ #
+ # action - The name of the action.
+ def self.action=(action)
+ trans = current_transaction
+
+ trans.action = action if trans
+ end
+
# When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe.
if enabled?
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index face1921d2e..708ef79f304 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -11,6 +11,8 @@ module Gitlab
module Instrumentation
SERIES = 'method_calls'
+ PROXY_IVAR = :@__gitlab_instrumentation_proxy
+
def self.configure
yield self
end
@@ -91,6 +93,18 @@ module Gitlab
end
end
+ # Returns true if a module is instrumented.
+ #
+ # mod - The module to check
+ def self.instrumented?(mod)
+ mod.instance_variable_defined?(PROXY_IVAR)
+ end
+
+ # Returns the proxy module (if any) of `mod`.
+ def self.proxy_module(mod)
+ mod.instance_variable_get(PROXY_IVAR)
+ end
+
# Instruments a method.
#
# type - The type (:class or :instance) of method to instrument.
@@ -99,9 +113,8 @@ module Gitlab
def self.instrument(type, mod, name)
return unless Metrics.enabled?
- name = name.to_sym
- alias_name = :"_original_#{name}"
- target = type == :instance ? mod : mod.singleton_class
+ name = name.to_sym
+ target = type == :instance ? mod : mod.singleton_class
if type == :instance
target = mod
@@ -113,6 +126,12 @@ module Gitlab
method = mod.method(name)
end
+ unless instrumented?(target)
+ target.instance_variable_set(PROXY_IVAR, Module.new)
+ end
+
+ proxy_module = self.proxy_module(target)
+
# Some code out there (e.g. the "state_machine" Gem) checks the arity of
# a method to make sure it only passes arguments when the method expects
# any. If we were to always overwrite a method to take an `*args`
@@ -125,17 +144,13 @@ module Gitlab
args_signature = '*args, &block'
end
- send_signature = "__send__(#{alias_name.inspect}, #{args_signature})"
-
- target.class_eval <<-EOF, __FILE__, __LINE__ + 1
- alias_method #{alias_name.inspect}, #{name.inspect}
-
+ proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature})
trans = Gitlab::Metrics::Instrumentation.transaction
if trans
start = Time.now
- retval = #{send_signature}
+ retval = super
duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold
@@ -148,10 +163,12 @@ module Gitlab
retval
else
- #{send_signature}
+ super
end
end
EOF
+
+ target.prepend(proxy_module)
end
# Small layer of indirection to make it easier to stub out the current
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index 8008b3bc895..96cad941d5c 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -9,6 +9,7 @@ module Gitlab
return unless current_transaction
current_transaction.increment(:sql_duration, event.duration)
+ current_transaction.increment(:sql_count, 1)
end
private
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 97d1edab9c1..c8f12577112 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -36,11 +36,12 @@ module Gitlab
commit.hook_attrs(with_changed_files: true)
end
- type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
+ type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
# Hash to be passed as post_receive_data
data = {
object_kind: type,
+ event_name: type,
before: oldrev,
after: newrev,
ref: ref,
@@ -65,7 +66,7 @@ module Gitlab
# This method provide a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
- commits = project.repository.commits(project.default_branch, nil, 3)
+ commits = project.repository.commits(project.default_branch, limit: 3)
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
build(project, user, commits.last.id, commits.first.id, ref, commits)
end
diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb
new file mode 100644
index 00000000000..b98589dff89
--- /dev/null
+++ b/lib/gitlab/sanitizers/svg.rb
@@ -0,0 +1,37 @@
+require_relative "svg/whitelist"
+
+module Gitlab
+ module Sanitizers
+ module SVG
+ def self.clean(data)
+ Loofah.xml_document(data).scrub!(Scrubber.new).to_s
+ end
+
+ class Scrubber < Loofah::Scrubber
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data-with-the-data-*-attributes
+ DATA_ATTR_PATTERN = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u
+
+ def scrub(node)
+ unless ALLOWED_ELEMENTS.include?(node.name)
+ node.unlink
+ else
+ node.attributes.each do |attr_name, attr|
+ valid_attributes = ALLOWED_ATTRIBUTES[node.name]
+
+ unless valid_attributes && valid_attributes.include?(attr_name)
+ if ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) &&
+ attr_name.start_with?('data-')
+ # Arbitrary data attributes are allowed. Verify that the attribute
+ # is a valid data attribute.
+ attr.unlink unless attr_name =~ DATA_ATTR_PATTERN
+ else
+ attr.unlink
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sanitizers/svg/whitelist.rb b/lib/gitlab/sanitizers/svg/whitelist.rb
new file mode 100644
index 00000000000..917e795b29e
--- /dev/null
+++ b/lib/gitlab/sanitizers/svg/whitelist.rb
@@ -0,0 +1,107 @@
+# Generated from:
+# SVG element list: https://www.w3.org/TR/SVG/eltindex.html
+# SVG Attribute list: https://www.w3.org/TR/SVG/attindex.html
+module Gitlab
+ module Sanitizers
+ module SVG
+ ALLOWED_ELEMENTS = %w[
+ a altGlyph altGlyphDef altGlyphItem animate
+ animateColor animateMotion animateTransform circle clipPath color-profile
+ cursor defs desc ellipse feBlend feColorMatrix feComponentTransfer
+ feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap
+ feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur
+ feImage feMerge feMergeNode feMorphology feOffset fePointLight
+ feSpecularLighting feSpotLight feTile feTurbulence filter font font-face
+ font-face-format font-face-name font-face-src font-face-uri foreignObject
+ g glyph glyphRef hkern image line linearGradient marker mask metadata
+ missing-glyph mpath path pattern polygon polyline radialGradient rect
+ script set stop style svg switch symbol text textPath title tref tspan use
+ view vkern].freeze
+
+ ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS = %w[svg].freeze
+
+ ALLOWED_ATTRIBUTES = {
+ 'a' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage target text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'altGlyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'altGlyphDef' => %w[id xml:base xml:lang xml:space],
+ 'altGlyphItem' => %w[id xml:base xml:lang xml:space],
+ 'animate' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateColor' => %w[accumulate additive alignment-baseline attributeName attributeType baseline-shift begin by calcMode clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dur enable-background end externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight from glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning keySplines keyTimes letter-spacing lighting-color marker-end marker-mid marker-start mask max min onbegin onend onload onrepeat opacity overflow pointer-events repeatCount repeatDur requiredExtensions requiredFeatures restart shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width systemLanguage text-anchor text-decoration text-rendering to unicode-bidi values visibility word-spacing writing-mode xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateMotion' => %w[accumulate additive begin by calcMode dur end externalResourcesRequired fill from id keyPoints keySplines keyTimes max min onbegin onend onload onrepeat origin path repeatCount repeatDur requiredExtensions requiredFeatures restart rotate systemLanguage to values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'animateTransform' => %w[accumulate additive attributeName attributeType begin by calcMode dur end externalResourcesRequired fill from id keySplines keyTimes max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to type values xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'circle' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events r requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'clipPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule clipPathUnits color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'color-profile' => %w[id local name rendering-intent xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'cursor' => %w[externalResourcesRequired id requiredExtensions requiredFeatures systemLanguage x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'defs' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'desc' => %w[class id style xml:base xml:lang xml:space],
+ 'ellipse' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'feBlend' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask mode opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feColorMatrix' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi values visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feComponentTransfer' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feComposite' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 k1 k2 k3 k4 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feConvolveMatrix' => %w[alignment-baseline baseline-shift bias class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display divisor dominant-baseline edgeMode enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelMatrix kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity order overflow pointer-events preserveAlpha result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style targetX targetY text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feDiffuseLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor diffuseConstant direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feDisplacementMap' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in in2 kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result scale shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xChannelSelector xml:base xml:lang xml:space y yChannelSelector],
+ 'feDistantLight' => %w[azimuth elevation id xml:base xml:lang xml:space],
+ 'feFlood' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feFuncA' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncB' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncG' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feFuncR' => %w[amplitude exponent id intercept offset slope tableValues type xml:base xml:lang xml:space],
+ 'feGaussianBlur' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stdDeviation stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feImage' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events preserveAspectRatio result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'feMerge' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feMergeNode' => %w[id xml:base xml:lang xml:space],
+ 'feMorphology' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity operator overflow pointer-events radius result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feOffset' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'fePointLight' => %w[id x xml:base xml:lang xml:space y z],
+ 'feSpecularLighting' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kernelUnitLength kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering specularConstant specularExponent stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feSpotLight' => %w[id limitingConeAngle pointsAtX pointsAtY pointsAtZ specularExponent x xml:base xml:lang xml:space y z],
+ 'feTile' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering in kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events result shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'feTurbulence' => %w[alignment-baseline baseFrequency baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask numOctaves opacity overflow pointer-events result seed shape-rendering stitchTiles stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering type unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'filter' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events primitiveUnits shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'font' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x horiz-origin-y id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'font-face' => %w[accent-height alphabetic ascent bbox cap-height descent font-family font-size font-stretch font-style font-variant font-weight hanging id ideographic mathematical overline-position overline-thickness panose-1 slope stemh stemv strikethrough-position strikethrough-thickness underline-position underline-thickness unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical widths x-height xml:base xml:lang xml:space],
+ 'font-face-format' => %w[id string xml:base xml:lang xml:space],
+ 'font-face-name' => %w[id name xml:base xml:lang xml:space],
+ 'font-face-src' => %w[id xml:base xml:lang xml:space],
+ 'font-face-uri' => %w[id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'foreignObject' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'g' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'glyph' => %w[alignment-baseline arabic-form baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning lang letter-spacing lighting-color marker-end marker-mid marker-start mask opacity orientation overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'glyphRef' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format glyph-orientation-horizontal glyph-orientation-vertical glyphRef id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'hkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space],
+ 'image' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'line' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode x1 x2 xml:base xml:lang xml:space y1 y2],
+ 'linearGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode x1 x2 xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y1 y2],
+ 'marker' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask opacity orient overflow pointer-events preserveAspectRatio refX refY shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'mask' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask maskContentUnits maskUnits opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'metadata' => %w[id xml:base xml:lang xml:space],
+ 'missing-glyph' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi vert-adv-y vert-origin-x vert-origin-y visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'mpath' => %w[externalResourcesRequired id xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'path' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor d direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pathLength pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'pattern' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow patternContentUnits patternTransform patternUnits pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi viewBox visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'polygon' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'polyline' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events points requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'radialGradient' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor cx cy direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight fx fy glyph-orientation-horizontal glyph-orientation-vertical gradientTransform gradientUnits id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask opacity overflow pointer-events r shape-rendering spreadMethod stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space],
+ 'rect' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rx ry shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'script' => %w[externalResourcesRequired id type xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'set' => %w[attributeName attributeType begin dur end externalResourcesRequired fill id max min onbegin onend onload onrepeat repeatCount repeatDur requiredExtensions requiredFeatures restart systemLanguage to xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space],
+ 'stop' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask offset opacity overflow pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'style' => %w[id media title type xml:base xml:lang xml:space],
+ 'svg' => %w[alignment-baseline baseProfile baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onabort onactivate onclick onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onresize onscroll onunload onzoom opacity overflow pointer-events preserveAspectRatio requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering unicode-bidi version viewBox visibility width word-spacing writing-mode x xml:base xml:lang xml:space xmlns y zoomAndPan],
+ 'switch' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'symbol' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events preserveAspectRatio shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style text-anchor text-decoration text-rendering unicode-bidi viewBox visibility word-spacing writing-mode xml:base xml:lang xml:space],
+ 'text' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength transform unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'textPath' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask method onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering spacing startOffset stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space],
+ 'title' => %w[class id style xml:base xml:lang xml:space],
+ 'tref' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xlink:arcrole xlink:href xlink:role xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'tspan' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline dx dy enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical id image-rendering kerning lengthAdjust letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures rotate shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering textLength unicode-bidi visibility word-spacing writing-mode x xml:base xml:lang xml:space y],
+ 'use' => %w[alignment-baseline baseline-shift class clip clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering cursor direction display dominant-baseline enable-background externalResourcesRequired fill fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical height id image-rendering kerning letter-spacing lighting-color marker-end marker-mid marker-start mask onactivate onclick onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup opacity overflow pointer-events requiredExtensions requiredFeatures shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style systemLanguage text-anchor text-decoration text-rendering transform unicode-bidi visibility width word-spacing writing-mode x xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y],
+ 'view' => %w[externalResourcesRequired id preserveAspectRatio viewBox viewTarget xml:base xml:lang xml:space zoomAndPan],
+ 'vkern' => %w[g1 g2 id k u1 u2 xml:base xml:lang xml:space]
+ }.freeze
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 37232743325..ae85b294d31 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -29,8 +29,8 @@ module Gitlab
"in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
- Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
- Process.kill('SIGUSR1', Process.pid)
+ Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
+ Process.kill('SIGTERM', Process.pid)
Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
"#{SHUTDOWN_SIGNAL} to PID #{Process.pid}"
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index f1943222edf..2bbbd3074e8 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -20,6 +20,8 @@ module Gitlab
merge_request_url(object)
when Note
note_url
+ when WikiPage
+ wiki_page_url
else
raise NotImplementedError.new("No URL builder defined for #{object.class}")
end
@@ -58,5 +60,9 @@ module Gitlab
project_snippet_url(snippet, anchor: dom_id(object))
end
end
+
+ def wiki_page_url
+ "#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}"
+ end
end
end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index d95e7023d2e..31b00ff128a 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -173,7 +173,7 @@ check_stale_pids(){
fi
fi
if [ "$hpid" != "0" ] && [ "$gitlab_workhorse_status" != "0" ]; then
- echo "Removing stale gitlab-workhorse pid. This is most likely caused by gitlab-workhorse crashing the last time it ran."
+ echo "Removing stale GitLab Workhorse pid. This is most likely caused by GitLab Workhorse crashing the last time it ran."
if ! rm "$gitlab_workhorse_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
@@ -208,7 +208,7 @@ start_gitlab() {
echo "Starting GitLab Sidekiq"
fi
if [ "$gitlab_workhorse_status" != "0" ]; then
- echo "Starting gitlab-workhorse"
+ echo "Starting GitLab Workhorse"
fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
echo "Starting GitLab MailRoom"
@@ -232,7 +232,7 @@ start_gitlab() {
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
- echo "The gitlab-workhorse is already running with pid $spid, not restarting"
+ echo "The GitLab Workhorse is already running with pid $spid, not restarting"
else
# No need to remove a socket, gitlab-workhorse does this itself.
# Because gitlab-workhorse has multiple executables we need to fix
@@ -271,7 +271,7 @@ stop_gitlab() {
RAILS_ENV=$RAILS_ENV bin/background_jobs stop
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
- echo "Shutting down gitlab-workhorse"
+ echo "Shutting down GitLab Workhorse"
kill -- $(cat $gitlab_workhorse_pid_path)
fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
@@ -320,9 +320,9 @@ print_status() {
printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
fi
if [ "$gitlab_workhorse_status" = "0" ]; then
- echo "The gitlab-workhorse with pid $hpid is running."
+ echo "The GitLab Workhorse with pid $hpid is running."
else
- printf "The gitlab-workhorse is \033[31mnot running\033[0m.\n"
+ printf "The GitLab Workhorse is \033[31mnot running\033[0m.\n"
fi
if [ "$mail_room_enabled" = true ]; then
if [ "$mail_room_status" = "0" ]; then
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 1324e4cd267..d521de28e8a 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -61,7 +61,8 @@ server {
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
- location ~ ^/(404|422|500|502)\.html$ {
+ error_page 503 /503.html;
+ location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index af6ea9ed706..bf014b56cf6 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -105,7 +105,8 @@ server {
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
- location ~ ^/(404|422|500|502)\.html$ {
+ error_page 503 /503.html;
+ location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4921c6e0bcf..e473b756023 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -29,7 +29,12 @@ namespace :gitlab do
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
connection.execute('TRUNCATE schema_migrations')
- tables.each { |t| connection.execute("DROP TABLE #{t}") }
+
+ # Drop tables with cascade to avoid dependent table errors
+ # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
+ # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
+ # Add `IF EXISTS` because cascade could have already deleted a table.
+ tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{t} CASCADE") }
end
end
end
diff --git a/public/503.html b/public/503.html
new file mode 100644
index 00000000000..6ab1185658d
--- /dev/null
+++ b/public/503.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>GitLab is not responding (503)</title>
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ 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;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+ </style>
+</head>
+<body>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo"/><br />
+ 503
+ </h1>
+ <h3>Whoops, GitLab is currently unavailable.</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/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 4a7ee7dbb64..247383aa46c 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -11,7 +11,7 @@ retry() {
return 1
}
-if [ -f /.dockerinit ]; then
+if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
mkdir -p vendor
# Install phantomjs package
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
deleted file mode 100644
index d7a7ba1c5b6..00000000000
--- a/spec/controllers/admin/impersonation_controller_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ImpersonationController do
- let(:admin) { create(:admin) }
-
- before do
- sign_in(admin)
- end
-
- describe 'CREATE #impersonation when blocked' do
- let(:blocked_user) { create(:user, state: :blocked) }
-
- it 'does not allow impersonation' do
- post :create, id: blocked_user.username
-
- expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
- end
- end
-end
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
new file mode 100644
index 00000000000..eb82476b179
--- /dev/null
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationsController do
+ let(:impersonator) { create(:admin) }
+ let(:user) { create(:user) }
+
+ describe "DELETE destroy" do
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ delete :destroy
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when not impersonating" do
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when impersonating" do
+ before do
+ session[:impersonator_id] = impersonator.id
+ end
+
+ context "when the impersonator is not admin (anymore)" do
+ before do
+ impersonator.admin = false
+ impersonator.save
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is admin" do
+ context "when the impersonator is blocked" do
+ before do
+ impersonator.block!
+ end
+
+ it "responds with status 404" do
+ delete :destroy
+
+ expect(response.status).to eq(404)
+ end
+
+ it "doesn't sign us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(user)
+ end
+ end
+
+ context "when the impersonator is not blocked" do
+ it "redirects to the impersonated user's page" do
+ delete :destroy
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it "signs us in as the impersonator" do
+ delete :destroy
+
+ expect(warden.user).to eq(impersonator)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 9ef8ba1b097..ce2a62ae1fd 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
describe Admin::UsersController do
let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
before do
- sign_in(create(:admin))
+ sign_in(admin)
end
describe 'DELETE #user with projects' do
@@ -112,4 +113,50 @@ describe Admin::UsersController do
patch :disable_two_factor, id: user.to_param
end
end
+
+ describe "POST impersonate" do
+ context "when the user is blocked" do
+ before do
+ user.block!
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
+ end
+
+ it "doesn't sign us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(admin)
+ end
+ end
+
+ context "when the user is not blocked" do
+ it "stores the impersonator in the session" do
+ post :impersonate, id: user.username
+
+ expect(session[:impersonator_id]).to eq(admin.id)
+ end
+
+ it "signs us in as the user" do
+ post :impersonate, id: user.username
+
+ expect(warden.user).to eq(user)
+ end
+
+ it "redirects to root" do
+ post :impersonate, id: user.username
+
+ expect(response).to redirect_to(root_path)
+ end
+
+ it "shows a notice" do
+ post :impersonate, id: user.username
+
+ expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
+ end
+ end
+ end
end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index f09e4fcb154..cf5c606c723 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -4,6 +4,8 @@ describe Projects::CommitController do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:commit) { project.commit("master") }
+ let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
+ let(:master_pickable_commit) { project.commit(master_pickable_sha) }
before do
sign_in(user)
@@ -192,4 +194,53 @@ describe Projects::CommitController do
end
end
end
+
+ describe '#cherry_pick' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: master_pickable_commit.id)
+
+ expect(response).not_to be_success
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when the cherry-pick was successful' do
+ it 'should redirect to the commits page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
+ end
+ end
+
+ context 'when the cherry_pick failed' do
+ before do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Cherry-picking a commit that has been already cherry-picked.
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
new file mode 100644
index 00000000000..a5986598715
--- /dev/null
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Groups::GroupMembersController do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ context "index" do
+ before do
+ group.add_owner(user)
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'renders index with group members' do
+ get :index, group_id: group.path
+
+ expect(response.status).to eq(200)
+ expect(response).to render_template(:index)
+ end
+ end
+end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index bbf8adef534..bcc713dce2a 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -22,6 +22,8 @@ describe Import::GithubController do
token = "asdasd12345"
allow_any_instance_of(Gitlab::GithubImport::Client).
to receive(:get_token).and_return(token)
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:github_options).and_return({})
stub_omniauth_provider('github')
get :callback
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
new file mode 100644
index 00000000000..40bd83af861
--- /dev/null
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Projects::GroupLinksController do
+ let(:project) { create(:project, :private) }
+ let(:group) { create(:group, :private) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe '#create' do
+ shared_context 'link project to group' do
+ before do
+ post(:create, namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ link_group_id: group.id,
+ link_group_access: ProjectGroupLink.default_access)
+ end
+ end
+
+ context 'when user has access to group he want to link project to' do
+ before { group.add_developer(user) }
+ include_context 'link project to group'
+
+ it 'links project with selected group' do
+ expect(group.shared_projects).to include project
+ end
+
+ it 'redirects to project group links page'do
+ expect(response).to redirect_to(
+ namespace_project_group_links_path(project.namespace, project)
+ )
+ end
+ end
+
+ context 'when user doers not have access to group he want to link to' do
+ include_context 'link project to group'
+
+ it 'renders 404' do
+ expect(response.status).to eq 404
+ end
+
+ it 'does not share project with that group' do
+ expect(group.shared_projects).to_not include project
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f7cb7ca8a40..30d296fdad0 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -40,6 +40,45 @@ describe Projects::IssuesController do
end
end
+ describe 'PUT #update' do
+ context 'when moving issue to another private project' do
+ let(:another_project) { create(:project, :private) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ context 'when user has access to move issue' do
+ before { another_project.team << [user, :reporter] }
+
+ it 'moves issue to another project' do
+ move_issue
+
+ expect(response).to have_http_status :found
+ expect(another_project.issues).to_not be_empty
+ end
+ end
+
+ context 'when user does not have access to move issue' do
+ it 'responds with 404' do
+ move_issue
+
+ expect(response).to have_http_status :not_found
+ end
+ end
+
+ def move_issue
+ put :update,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.iid,
+ issue: { title: 'New title' },
+ move_to_project_id: another_project.id
+ end
+ end
+ end
+
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c54e83339a1..c0a1f45195f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -300,14 +300,6 @@ describe Projects::MergeRequestsController do
expect(response.cookies['diff_view']).to eq('parallel')
end
-
- it 'assigns :view param based on cookie' do
- request.cookies['diff_view'] = 'parallel'
-
- go
-
- expect(controller.params[:view]).to eq 'parallel'
- end
end
describe 'GET commits' do
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index d47e4ab9a4f..ed64e7cf9af 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -46,4 +46,20 @@ describe Projects::ProjectMembersController do
end
end
end
+
+ describe '#index' do
+ let(:project) { create(:project, :private) }
+
+ context 'when user is member' do
+ let(:member) { create(:user) }
+
+ before do
+ project.team << [member, :guest]
+ sign_in(member)
+ get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
+ end
+
+ it { expect(response.status).to eq(200) }
+ end
+ end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 7337ff58be1..8045c8b940d 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -33,7 +33,30 @@ describe UsersController do
it 'renders the show template' do
get :show, username: user.username
- expect(response).to be_success
+ expect(response.status).to eq(200)
+ expect(response).to render_template('show')
+ end
+ end
+ end
+
+ context 'when public visibility level is restricted' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ context 'when logged out' do
+ it 'renders 404' do
+ get :show, username: user.username
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when logged in' do
+ before { sign_in(user) }
+
+ it 'renders show' do
+ get :show, username: user.username
+ expect(response.status).to eq(200)
expect(response).to render_template('show')
end
end
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index d0e8c778518..8f6422a7825 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index c80e7366551..efe9803b1a7 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string(255)
-# font :string(255)
-#
-
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index 19a54946fe0..b16c1272e68 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index 2939d4307c5..3580174e873 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string(255)
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :label_link do
label
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index ea2be8928d5..eb489099854 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: labels
-#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
-#
-
FactoryGirl.define do
factory :label do
sequence(:title) { |n| "label#{n}" }
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 327858ce435..a81645acd2b 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects
-#
-# id :integer not null, primary key
-# oid :string(255) not null
-# size :integer not null
-# created_at :datetime
-# updated_at :datetime
-# file :string(255)
-#
-
include ActionDispatch::TestProcess
FactoryGirl.define do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 50b45843c99..1ed0355c8e4 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: lfs_objects_projects
-#
-# id :integer not null, primary key
-# lfs_object_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index e281e2f227b..c6a08d78b78 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,32 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
-# merge_params :text
-# merge_when_build_succeeds :boolean default(FALSE), not null
-# merge_user_id :integer
-# merge_commit_sha :string
-#
-
FactoryGirl.define do
factory :merge_request do
title
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 2bfc5effd78..e65204cbe7b 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string(255)
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-# is_award :boolean default(FALSE), not null
-#
-
require_relative '../support/repo_helpers'
include ActionDispatch::TestProcess
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index 7700b15d538..ccf02d0719b 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: oauth_access_tokens
-#
-# id :integer not null, primary key
-# resource_owner_id :integer
-# application_id :integer
-# token :string not null
-# refresh_token :string
-# expires_in :integer
-# revoked_at :datetime
-# created_at :datetime not null
-# scopes :string
-#
-
FactoryGirl.define do
factory :oauth_access_token do
resource_owner
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index 94dd935a039..3195fb3ddcc 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,5 +1,9 @@
FactoryGirl.define do
factory :project_hook do
url { FFaker::Internet.uri('http') }
+
+ trait :token do
+ token { SecureRandom.hex(10) }
+ end
end
end
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
new file mode 100644
index 00000000000..a3403fd76ae
--- /dev/null
+++ b/spec/factories/project_wikis.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :project_wiki do
+ project factory: :empty_project
+ user factory: :user
+ initialize_with { new(project, user) }
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index c14b99606ba..da8d97c9f82 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,43 +1,3 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# description :text
-# created_at :datetime
-# updated_at :datetime
-# creator_id :integer
-# issues_enabled :boolean default(TRUE), not null
-# wall_enabled :boolean default(TRUE), not null
-# merge_requests_enabled :boolean default(TRUE), not null
-# wiki_enabled :boolean default(TRUE), not null
-# namespace_id :integer
-# issues_tracker :string(255) default("gitlab"), not null
-# issues_tracker_id :string(255)
-# snippets_enabled :boolean default(TRUE), not null
-# last_activity_at :datetime
-# import_url :string(255)
-# visibility_level :integer default(0), not null
-# archived :boolean default(FALSE), not null
-# avatar :string(255)
-# import_status :string(255)
-# repository_size :float default(0.0)
-# star_count :integer default(0), not null
-# import_type :string(255)
-# import_source :string(255)
-# commit_count :integer default(0)
-# import_error :text
-# ci_id :integer
-# builds_enabled :boolean default(TRUE), not null
-# shared_runners_enabled :boolean default(TRUE), not null
-# runners_token :string
-# build_coverage_regex :string
-# build_allow_git_fetch :boolean default(TRUE), not null
-# build_timeout :integer default(3600), not null
-#
-
FactoryGirl.define do
# Project without repository
#
@@ -61,6 +21,12 @@ FactoryGirl.define do
trait :private do
visibility_level Gitlab::VisibilityLevel::PRIVATE
end
+
+ trait :empty_repo do
+ after(:create) do |project|
+ project.create_repository
+ end
+ end
end
# Project with empty repository
@@ -68,9 +34,7 @@ FactoryGirl.define do
# This is a case when you just created a project
# but not pushed any code there yet
factory :project_empty_repo, parent: :empty_project do
- after :create do |project|
- project.create_repository
- end
+ empty_repo
end
# Project with test repository
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 7f331c37256..74497dc82c0 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string(255)
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
FactoryGirl.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index 7ae06c27840..e3681ae93a5 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
FactoryGirl.define do
factory :todo do
project
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
new file mode 100644
index 00000000000..938ccf2306b
--- /dev/null
+++ b/spec/factories/wiki_pages.rb
@@ -0,0 +1,9 @@
+require 'ostruct'
+
+FactoryGirl.define do
+ factory :wiki_page do
+ page = OpenStruct.new(url_path: 'some-name')
+ association :wiki, factory: :project_wiki, strategy: :build
+ initialize_with { new(wiki, page, true) }
+ end
+end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 6da3a857b3f..090a941958f 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -86,6 +86,20 @@ describe "Builds" do
end
end
end
+
+ context 'Build raw trace' do
+ before do
+ @build.run!
+ @build.trace = 'BUILD TRACE'
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it do
+ page.within('.build-controls') do
+ expect(page).to have_link 'Raw'
+ end
+ end
+ end
end
describe "POST /:project/builds/:id/cancel" do
@@ -120,4 +134,20 @@ describe "Builds" do
it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) }
end
+
+ describe "GET /:project/builds/:id/raw" do
+ before do
+ Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ @build.run!
+ @build.trace = 'BUILD TRACE'
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it 'sends the right headers' do
+ page.within('.build-controls') { click_link 'Raw' }
+
+ expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace)
+ end
+ end
end
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
new file mode 100644
index 00000000000..24e83d44010
--- /dev/null
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe 'Dashboard > label filter', feature: true, js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+ let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) }
+ let(:label) { create(:label, title: 'bug', color: '#ff0000') }
+ let(:label2) { create(:label, title: 'bug') }
+
+ before do
+ project.labels << label
+ project2.labels << label2
+
+ login_as(user)
+ visit issues_dashboard_path
+ end
+
+ context 'duplicate labels' do
+ it 'should remove duplicate labels' do
+ page.within('.labels-filter') do
+ click_button 'Label'
+ end
+
+ page.within('.dropdown-menu-labels') do
+ expect(page).to have_selector('.dropdown-content a', text: 'bug', count: 1)
+ end
+ end
+ end
+end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
new file mode 100644
index 00000000000..cf86e2c85e9
--- /dev/null
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe "Dashboard > User filters projects", feature: true do
+
+ describe 'filtering personal projects' do
+ before do
+ user = create(:user)
+ project = create(:project, name: "Victorialand", namespace: user.namespace)
+ project.team << [user, :master]
+
+ user2 = create(:user)
+ project2 = create(:project, name: "Treasure", namespace: user2.namespace)
+ project2.team << [user, :developer]
+
+ login_as(user)
+ visit dashboard_projects_path
+ end
+
+ it 'filters by projects "Owned by me"' do
+ click_link "Owned by me"
+
+ expect(page).to have_css('.is-active', text: 'Owned by me')
+ expect(page).to have_content('Victorialand')
+ expect(page).not_to have_content('Treasure')
+ end
+ end
+end
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
new file mode 100644
index 00000000000..7f654684143
--- /dev/null
+++ b/spec/features/issues/filter_by_labels_spec.rb
@@ -0,0 +1,167 @@
+require 'rails_helper'
+
+feature 'Issue filtering by Labels', feature: true do
+ include WaitForAjax
+
+ let(:project) { create(:project, :public) }
+ let!(:user) { create(:user)}
+ let!(:label) { create(:label, project: project) }
+
+ before do
+ bug = create(:label, project: project, title: 'bug')
+ feature = create(:label, project: project, title: 'feature')
+ enhancement = create(:label, project: project, title: 'enhancement')
+
+ issue1 = create(:issue, title: "Bugfix1", project: project)
+ issue1.labels << bug
+
+ issue2 = create(:issue, title: "Bugfix2", project: project)
+ issue2.labels << bug
+ issue2.labels << enhancement
+
+ issue3 = create(:issue, title: "Feature1", project: project)
+ issue3.labels << feature
+
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ context 'filter by label bug', js: true do
+ before do
+ page.find('.js-label-select').click
+ wait_for_ajax
+ execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ wait_for_ajax
+ end
+
+ it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do
+ expect(page).to have_content "Bugfix1"
+ expect(page).to have_content "Bugfix2"
+ end
+
+ it 'should not show "Feature1" in issues list' do
+ expect(page).not_to have_content "Feature1"
+ end
+
+ it 'should show label "bug" in filtered-labels' do
+ expect(find('.filtered-labels')).to have_content "bug"
+ end
+
+ it 'should not show label "feature" and "enhancement" in filtered-labels' do
+ expect(find('.filtered-labels')).not_to have_content "feature"
+ expect(find('.filtered-labels')).not_to have_content "enhancement"
+ end
+ end
+
+ context 'filter by label feature', js: true do
+ before do
+ page.find('.js-label-select').click
+ wait_for_ajax
+ execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ wait_for_ajax
+ end
+
+ it 'should show issue "Feature1" in issues list' do
+ expect(page).to have_content "Feature1"
+ end
+
+ it 'should not show "Bugfix1" and "Bugfix2" in issues list' do
+ expect(page).not_to have_content "Bugfix2"
+ expect(page).not_to have_content "Bugfix1"
+ end
+
+ it 'should show label "feature" in filtered-labels' do
+ expect(find('.filtered-labels')).to have_content "feature"
+ end
+
+ it 'should not show label "bug" and "enhancement" in filtered-labels' do
+ expect(find('.filtered-labels')).not_to have_content "bug"
+ expect(find('.filtered-labels')).not_to have_content "enhancement"
+ end
+ end
+
+ context 'filter by label enhancement', js: true do
+ before do
+ page.find('.js-label-select').click
+ wait_for_ajax
+ execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ wait_for_ajax
+ end
+
+ it 'should show issue "Bugfix2" in issues list' do
+ expect(page).to have_content "Bugfix2"
+ end
+
+ it 'should not show "Feature1" and "Bugfix1" in issues list' do
+ expect(page).not_to have_content "Feature1"
+ expect(page).not_to have_content "Bugfix1"
+ end
+
+ it 'should show label "enhancement" in filtered-labels' do
+ expect(find('.filtered-labels')).to have_content "enhancement"
+ end
+
+ it 'should not show label "feature" and "bug" in filtered-labels' do
+ expect(find('.filtered-labels')).not_to have_content "bug"
+ expect(find('.filtered-labels')).not_to have_content "feature"
+ end
+ end
+
+ context 'filter by label enhancement or feature', js: true do
+ before do
+ page.find('.js-label-select').click
+ wait_for_ajax
+ execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
+ execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()")
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ wait_for_ajax
+ end
+
+ it 'should not show "Bugfix1" or "Feature1" in issues list' do
+ expect(page).not_to have_content "Bugfix1"
+ expect(page).not_to have_content "Feature1"
+ end
+
+ it 'should show label "enhancement" and "feature" in filtered-labels' do
+ expect(find('.filtered-labels')).to have_content "enhancement"
+ expect(find('.filtered-labels')).to have_content "feature"
+ end
+
+ it 'should not show label "bug" in filtered-labels' do
+ expect(find('.filtered-labels')).not_to have_content "bug"
+ end
+ end
+
+ context 'filter by label enhancement and bug in issues list', js: true do
+ before do
+ page.find('.js-label-select').click
+ wait_for_ajax
+ execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()")
+ execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ wait_for_ajax
+ end
+
+ it 'should show issue "Bugfix2" in issues list' do
+ expect(page).to have_content "Bugfix2"
+ end
+
+ it 'should not show "Feature1"' do
+ expect(page).not_to have_content "Feature1"
+ end
+
+ it 'should show label "bug" and "enhancement" in filtered-labels' do
+ expect(find('.filtered-labels')).to have_content "bug"
+ expect(find('.filtered-labels')).to have_content "enhancement"
+ end
+
+ it 'should not show label "feature" in filtered-labels' do
+ expect(find('.filtered-labels')).not_to have_content "feature"
+ end
+ end
+end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 69b22232f10..192e3619375 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -84,14 +84,20 @@ describe 'Filter issues', feature: true do
it 'should filter by any label' do
find('.dropdown-menu-labels a', text: 'Any Label').click
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ sleep 2
+
page.within '.labels-filter' do
expect(page).to have_content 'Any Label'
end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Label')
+ expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Any Label')
end
it 'should filter by no label' do
find('.dropdown-menu-labels a', text: 'No Label').click
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
+ sleep 2
+
page.within '.labels-filter' do
expect(page).to have_content 'No Label'
end
@@ -121,6 +127,7 @@ describe 'Filter issues', feature: true do
find('.js-label-select').click
find('.dropdown-menu-labels .dropdown-content a', text: label.title).click
+ page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
sleep 2
end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
new file mode 100644
index 00000000000..5739bc64dfb
--- /dev/null
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -0,0 +1,79 @@
+require 'rails_helper'
+
+feature 'Issue Sidebar', feature: true do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let!(:user) { create(:user)}
+
+ before do
+ create(:label, project: project, title: 'bug')
+ login_as(user)
+ end
+
+ context 'as a allowed user' do
+ before do
+ project.team << [user, :developer]
+ visit_issue(project, issue)
+ end
+
+ describe 'when clicking on edit labels', js: true do
+ it 'dropdown has an option to create a new label' do
+ find('.block.labels .edit-link').click
+
+ page.within('.block.labels') do
+ expect(page).to have_content 'Create new'
+ end
+ end
+ end
+
+ context 'creating a new label', js: true do
+ it 'option to crate a new label is present' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+
+ expect(page).to have_content 'Create new'
+ end
+ end
+
+ it 'dropdown switches to "create label" section' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+ click_link 'Create new'
+
+ expect(page).to have_content 'Create new label'
+ end
+ end
+
+ it 'new label is added' do
+ page.within('.block.labels') do
+ find('.edit-link').click
+ sleep 1
+ click_link 'Create new'
+
+ fill_in 'new_label_name', with: 'wontfix'
+ page.find(".suggest-colors a", match: :first).click
+ click_button 'Create'
+
+ page.within('.dropdown-page-one') do
+ expect(page).to have_content 'wontfix'
+ end
+ end
+ end
+ end
+ end
+
+ context 'as a guest' do
+ before do
+ project.team << [user, :guest]
+ visit_issue(project, issue)
+ end
+
+ it 'does not have a option to edit labels' do
+ expect(page).not_to have_selector('.block.labels .edit-link')
+ end
+ end
+
+ def visit_issue(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 6fda0c31866..84c8e20ebaa 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -42,11 +42,9 @@ feature 'issue move to another project' do
expect(current_url).to include project_path(new_project)
- page.within('.issue') do
- expect(page).to have_content("Text with #{cross_reference}!1")
- expect(page).to have_content("Moved from #{cross_reference}#1")
- expect(page).to have_content(issue.title)
- end
+ expect(page).to have_content("Text with #{cross_reference}!1")
+ expect(page).to have_content("Moved from #{cross_reference}#1")
+ expect(page).to have_content(issue.title)
end
context 'projects user does not have permission to move issue to exist' do
@@ -74,7 +72,7 @@ feature 'issue move to another project' do
def edit_issue(issue)
visit issue_path(issue)
- page.within('.issuable-header') { click_link 'Edit' }
+ page.within('.issuable-actions') { first(:link, 'Edit').click }
end
def issue_path(issue)
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index 9219b767547..16e188d2a8a 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -11,10 +11,10 @@ feature 'Start new branch from an issue', feature: true do
login_as(user)
end
- it 'shown the new branch button', js: false do
+ it 'shows the new branch button', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
- expect(page).to have_link "New Branch"
+ expect(page).to have_css('#new-branch .available')
end
context "when there is a referenced merge request" do
@@ -34,16 +34,17 @@ feature 'Start new branch from an issue', feature: true do
end
it "hides the new branch button", js: true do
- expect(page).not_to have_link "New Branch"
+ expect(page).not_to have_css('#new-branch .available')
expect(page).to have_content /1 Related Merge Request/
end
end
end
context "for visiters" do
- it 'no button is shown', js: false do
+ it 'no button is shown', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
- expect(page).not_to have_link "New Branch"
+
+ expect(page).not_to have_css('#new-branch')
end
end
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 3eb903a93fe..b03dd0f666d 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -48,7 +48,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
click_update_issues_button
page.within('.issue .controls') do
- expect(find('.author_link')["data-original-title"]).to have_content(user.name)
+ expect(find('.author_link')["title"]).to have_content(user.name)
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 7da87c2d174..dfb554c05ca 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -112,7 +112,7 @@ describe 'Issues', feature: true do
end
describe 'filter issue' do
- titles = ['foo','bar','baz']
+ titles = %w[foo bar baz]
titles.each_with_index do |title, index|
let!(title.to_sym) do
create(:issue, title: title,
@@ -153,8 +153,107 @@ describe 'Issues', feature: true do
expect(first_issue).to include('baz')
end
+ describe 'sorting by due date' do
+ before do
+ foo.update(due_date: 1.day.from_now)
+ bar.update(due_date: 6.days.from_now)
+ end
+
+ it 'sorts by recently due date' do
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_soon)
+
+ expect(first_issue).to include('foo')
+ end
+
+ it 'sorts by least recently due date' do
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+
+ expect(first_issue).to include('bar')
+ end
+
+ it 'sorts by least recently due date by excluding nil due dates' do
+ bar.update(due_date: nil)
+
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+
+ expect(first_issue).to include('foo')
+ end
+
+ context 'with a filter on labels' do
+ let(:label) { create(:label, project: project) }
+ before { create(:label_link, label: label, target: foo) }
+
+ it 'sorts by least recently due date by excluding nil due dates' do
+ bar.update(due_date: nil)
+
+ visit namespace_project_issues_path(project.namespace, project, label_names: [label.name], sort: sort_value_due_date_later)
+
+ expect(first_issue).to include('foo')
+ end
+ end
+ end
+
+ describe 'filtering by due date' do
+ before do
+ foo.update(due_date: 1.day.from_now)
+ bar.update(due_date: 6.days.from_now)
+ end
+
+ it 'filters by none' do
+ visit namespace_project_issues_path(project.namespace, project, due_date: Issue::NoDueDate.name)
+
+ expect(page).not_to have_content('foo')
+ expect(page).not_to have_content('bar')
+ expect(page).to have_content('baz')
+ end
+
+ it 'filters by any' do
+ visit namespace_project_issues_path(project.namespace, project, due_date: Issue::AnyDueDate.name)
+
+ expect(page).to have_content('foo')
+ expect(page).to have_content('bar')
+ expect(page).to have_content('baz')
+ end
+
+ it 'filters by due this week' do
+ foo.update(due_date: Date.today.beginning_of_week + 2.days)
+ bar.update(due_date: Date.today.end_of_week)
+ baz.update(due_date: Date.today - 8.days)
+
+ visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisWeek.name)
+
+ expect(page).to have_content('foo')
+ expect(page).to have_content('bar')
+ expect(page).not_to have_content('baz')
+ end
+
+ it 'filters by due this month' do
+ foo.update(due_date: Date.today.beginning_of_month + 2.days)
+ bar.update(due_date: Date.today.end_of_month)
+ baz.update(due_date: Date.today - 50.days)
+
+ visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisMonth.name)
+
+ expect(page).to have_content('foo')
+ expect(page).to have_content('bar')
+ expect(page).not_to have_content('baz')
+ end
+
+ it 'filters by overdue' do
+ foo.update(due_date: Date.today + 2.days)
+ bar.update(due_date: Date.today + 20.days)
+ baz.update(due_date: Date.yesterday)
+
+ visit namespace_project_issues_path(project.namespace, project, due_date: Issue::Overdue.name)
+
+ expect(page).not_to have_content('foo')
+ expect(page).not_to have_content('bar')
+ expect(page).to have_content('baz')
+ end
+ end
+
describe 'sorting by milestone' do
- before :each do
+ before do
foo.milestone = newer_due_milestone
foo.save
bar.milestone = later_due_milestone
@@ -165,19 +264,21 @@ describe 'Issues', feature: true do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
expect(first_issue).to include('foo')
+ expect(last_issue).to include('baz')
end
it 'sorts by least recently due milestone' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
expect(first_issue).to include('bar')
+ expect(last_issue).to include('baz')
end
end
describe 'combine filter and sort' do
let(:user2) { create(:user) }
- before :each do
+ before do
foo.assignee = user2
foo.save
bar.assignee = user2
@@ -218,13 +319,34 @@ describe 'Issues', feature: true do
expect(issue.reload.assignee).to be_nil
end
+
+ it 'allows user to select an assignee', js: true do
+ issue2 = create(:issue, project: project, author: @user)
+ visit namespace_project_issue_path(project.namespace, project, issue2)
+
+ page.within('.assignee') do
+ expect(page).to have_content "No assignee"
+ end
+
+ page.within '.assignee' do
+ click_link 'Edit'
+ end
+
+ page.within '.dropdown-menu-user' do
+ click_link @user.name
+ end
+
+ page.within('.assignee') do
+ expect(page).to have_content @user.name
+ end
+ end
end
context 'by unauthorized user' do
let(:guest) { create(:user) }
- before :each do
+ before do
project.team << [[guest], :guest]
end
@@ -267,7 +389,7 @@ describe 'Issues', feature: true do
context 'by unauthorized user' do
let(:guest) { create(:user) }
- before :each do
+ before do
project.team << [guest, :guest]
issue.milestone = milestone
issue.save
@@ -285,13 +407,30 @@ describe 'Issues', feature: true do
describe 'removing assignee' do
let(:user2) { create(:user) }
- before :each do
+ before do
issue.assignee = user2
issue.save
end
end
end
+ describe 'new issue' do
+ context 'dropzone upload file', js: true do
+ before do
+ visit new_namespace_project_issue_path(project.namespace, project)
+ end
+
+ it 'should upload file when dragging into textarea' do
+ drop_in_dropzone test_image_file
+
+ # Wait for the file to upload
+ sleep 1
+
+ expect(page.find_field("issue_description").value).to have_content 'banana_sample'
+ end
+ end
+ end
+
def first_issue
page.all('ul.issues-list > li').first.text
end
@@ -299,4 +438,25 @@ describe 'Issues', feature: true do
def last_issue
page.all('ul.issues-list > li').last.text
end
+
+ def drop_in_dropzone(file_path)
+ # Generate a fake input selector
+ page.execute_script <<-JS
+ var fakeFileInput = window.$('<input/>').attr(
+ {id: 'fakeFileInput', type: 'file'}
+ ).appendTo('body');
+ JS
+ # Attach the file to the fake input selector with Capybara
+ attach_file("fakeFileInput", file_path)
+ # Add the file to a fileList array and trigger the fake drop event
+ page.execute_script <<-JS
+ var fileList = [$('#fakeFileInput')[0].files[0]];
+ var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
+ $('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
+ JS
+ end
+
+ def test_image_file
+ File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
+ end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 4433ef2d6f1..8c38dd5b122 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -37,7 +37,7 @@ feature 'Login', feature: true do
end
def enter_code(code)
- fill_in 'Two-factor authentication code', with: code
+ fill_in 'Two-factor Authentication code', with: code
click_button 'Verify code'
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 3d0d0e59fd7..0148c87084a 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -165,7 +165,12 @@ describe 'GitLab Markdown', feature: true do
describe 'ExternalLinkFilter' do
it 'adds nofollow to external link' do
link = doc.at_css('a:contains("Google")')
- expect(link.attr('rel')).to match 'nofollow'
+ expect(link.attr('rel')).to include('nofollow')
+ end
+
+ it 'adds noreferrer to external link' do
+ link = doc.at_css('a:contains("Google")')
+ expect(link.attr('rel')).to include('noreferrer')
end
it 'ignores internal link' do
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb
new file mode 100644
index 00000000000..82bc5226d07
--- /dev/null
+++ b/spec/features/merge_requests/cherry_pick_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe 'Cherry-pick Merge Requests' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
+
+ before do
+ login_as user
+ project.team << [user, :master]
+ end
+
+ context "Viewing a merged merge request" do
+ before do
+ service = MergeRequests::MergeService.new(project, user)
+
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ end
+ end
+
+ # Fast-forward merge, or merged before GitLab 8.5.
+ context "Without a merge commit" do
+ before do
+ merge_request.merge_commit_sha = nil
+ merge_request.save
+ end
+
+ it "doesn't show a Cherry-pick button" do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).not_to have_link "Cherry-pick"
+ end
+ end
+
+ context "With a merge commit" do
+ it "shows a Cherry-pick button" do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).to have_link "Cherry-pick"
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 00b60bd0e75..e296078bad8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -30,4 +30,14 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
+
+ context 'when target project cannot be viewed by the current user' do
+ it 'does not leak the private project name & namespace' do
+ private_project = create(:project, :private)
+
+ visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+
+ expect(page).not_to have_content private_project.to_reference
+ end
+ end
end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index c57ab5f3b03..e3ecd60a5f3 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -2,8 +2,14 @@ require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
let(:project) { create(:project, :public) }
+ let!(:user) { create(:user)}
let(:milestone) { create(:milestone, project: project) }
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
scenario 'filters by no Milestone', js: true do
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
diff --git a/spec/features/merge_requests/toggle_whitespace_changes.rb b/spec/features/merge_requests/toggle_whitespace_changes.rb
new file mode 100644
index 00000000000..0f98737b700
--- /dev/null
+++ b/spec/features/merge_requests/toggle_whitespace_changes.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+feature 'Toggle Whitespace Changes', js: true, feature: true do
+ before do
+ login_as :admin
+ merge_request = create(:merge_request)
+ project = merge_request.source_project
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'has a button to toggle whitespace changes' do
+ expect(page).to have_content 'Hide whitespace changes'
+ end
+
+ describe 'clicking "Hide whitespace changes" button' do
+ it 'toggles the "Hide whitespace changes" button' do
+ click_link 'Hide whitespace changes'
+
+ expect(page).to have_content 'Show whitespace changes'
+ end
+ end
+end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
new file mode 100644
index 00000000000..c2c7acff3e8
--- /dev/null
+++ b/spec/features/milestone_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+feature 'Milestone', feature: true do
+ include WaitForAjax
+
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project, title: 8.7) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ feature 'Create a milestone' do
+ scenario 'should show an informative message for a new issue' do
+ visit new_namespace_project_milestone_path(project.namespace, project)
+ page.within '.milestone-form' do
+ fill_in "milestone_title", with: '8.7'
+ end
+ find('input[name="commit"]').click
+
+ expect(find('.alert-success')).to have_content('Assign some issues to this milestone.')
+ end
+ end
+
+ feature 'Open a milestone with closed issues' do
+ scenario 'should show an informative message' do
+ create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
+ visit namespace_project_milestone_path(project.namespace, project, milestone)
+
+ expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
+ end
+ end
+end
diff --git a/spec/features/project/shortcuts_spec.rb b/spec/features/project/shortcuts_spec.rb
new file mode 100644
index 00000000000..2595c4181e5
--- /dev/null
+++ b/spec/features/project/shortcuts_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Project shortcuts', feature: true do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ describe 'On a project', js: true do
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ describe 'pressing "i"' do
+ it 'redirects to new issue page' do
+ find('body').native.send_key('i')
+ expect(page).to have_content('New Issue')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
new file mode 100644
index 00000000000..40ba0bdc115
--- /dev/null
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+feature 'project commit builds' do
+ given(:project) { create(:project) }
+
+ background do
+ user = create(:user)
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ context 'when no builds triggered yet' do
+ background do
+ create(:ci_commit, project: project,
+ sha: project.commit.sha,
+ ref: 'master')
+ end
+
+ scenario 'user views commit builds page' do
+ visit builds_namespace_project_commit_path(project.namespace,
+ project, project.commit.sha)
+
+
+ expect(page).to have_content('Builds')
+ end
+ end
+end
diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb
new file mode 100644
index 00000000000..0559b02f321
--- /dev/null
+++ b/spec/features/projects/commits/cherry_pick_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe 'Cherry-pick Commits' do
+ let(:project) { create(:project) }
+ let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
+
+
+ before do
+ login_as :user
+ project.team << [@user, :master]
+ visit namespace_project_commits_path(project.namespace, project, project.repository.root_ref, { limit: 5 })
+ end
+
+ context "I cherry-pick a commit" do
+ it do
+ visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ find("a[href='#modal-cherry-pick-commit']").click
+ page.within('#modal-cherry-pick-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Cherry-pick'
+ end
+ expect(page).to have_content('The commit has been successfully cherry-picked.')
+ end
+ end
+
+ context "I cherry-pick a merge commit" do
+ it do
+ visit namespace_project_commit_path(project.namespace, project, master_pickable_merge.id)
+ find("a[href='#modal-cherry-pick-commit']").click
+ page.within('#modal-cherry-pick-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Cherry-pick'
+ end
+ expect(page).to have_content('The commit has been successfully cherry-picked.')
+ end
+ end
+
+ context "I cherry-pick a commit that was previously cherry-picked" do
+ it do
+ visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ find("a[href='#modal-cherry-pick-commit']").click
+ page.within('#modal-cherry-pick-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Cherry-pick'
+ end
+ visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ find("a[href='#modal-cherry-pick-commit']").click
+ page.within('#modal-cherry-pick-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Cherry-pick'
+ end
+ expect(page).to have_content('Sorry, we cannot cherry-pick this commit automatically.')
+ end
+ end
+
+ context "I cherry-pick a commit in a new merge request" do
+ it do
+ visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ find("a[href='#modal-cherry-pick-commit']").click
+ page.within('#modal-cherry-pick-commit') do
+ click_button 'Cherry-pick'
+ end
+ expect(page).to have_content('The commit has been successfully cherry-picked. You can now submit a merge request to get this change into the original branch.')
+ end
+ end
+end
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
new file mode 100644
index 00000000000..0c51fe72ca4
--- /dev/null
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -0,0 +1,63 @@
+require 'rails_helper'
+
+feature 'Developer views empty project instructions', feature: true do
+ let(:project) { create(:empty_project, :empty_repo) }
+ let(:developer) { create(:user) }
+
+ background do
+ project.team << [developer, :developer]
+
+ login_as(developer)
+ end
+
+ context 'without an SSH key' do
+ scenario 'defaults to HTTP' do
+ visit_project
+
+ expect_instructions_for('http')
+ end
+
+ scenario 'switches to SSH', js: true do
+ visit_project
+
+ select_protocol('SSH')
+
+ expect_instructions_for('ssh')
+ end
+ end
+
+ context 'with an SSH key' do
+ background do
+ create(:personal_key, user: developer)
+ end
+
+ scenario 'defaults to SSH' do
+ visit_project
+
+ expect_instructions_for('ssh')
+ end
+
+ scenario 'switches to HTTP', js: true do
+ visit_project
+
+ select_protocol('HTTP')
+
+ expect_instructions_for('http')
+ end
+ end
+
+ def visit_project
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ def select_protocol(protocol)
+ find('#clone-dropdown').click
+ find(".#{protocol.downcase}-selector").click
+ end
+
+ def expect_instructions_for(protocol)
+ msg = :"#{protocol.downcase}_url_to_repo"
+
+ expect(page).to have_content("git clone #{project.send(msg)}")
+ end
+end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
new file mode 100644
index 00000000000..3d6ffbc4c6b
--- /dev/null
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+feature 'project owner creates a license file', feature: true, js: true do
+ include Select2Helper
+
+ let(:project_master) { create(:user) }
+ let(:project) { create(:project) }
+ background do
+ project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master')
+ project.team << [project_master, :master]
+ login_as(project_master)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'project master creates a license file manually from a template' do
+ visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ find('.add-to-tree').click
+ click_link 'New file'
+
+ fill_in :file_name, with: 'LICENSE'
+
+ expect(page).to have_selector('.license-selector')
+
+ select2('mit', from: '#license_type')
+
+ file_content = find('.file-content')
+ expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+
+ fill_in :commit_message, with: 'Add a LICENSE file', visible: true
+ click_button 'Commit Changes'
+
+ expect(current_path).to eq(
+ namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ end
+
+ scenario 'project master creates a license file from the "Add license" link' do
+ click_link 'Add License'
+
+ expect(current_path).to eq(
+ namespace_project_new_blob_path(project.namespace, project, 'master'))
+ expect(find('#file_name').value).to eq('LICENSE')
+ expect(page).to have_selector('.license-selector')
+
+ select2('mit', from: '#license_type')
+
+ file_content = find('.file-content')
+ expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+
+ fill_in :commit_message, with: 'Add a LICENSE file', visible: true
+ click_button 'Commit Changes'
+
+ expect(current_path).to eq(
+ namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ end
+end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
new file mode 100644
index 00000000000..3268e240200
--- /dev/null
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do
+ include Select2Helper
+
+ let(:project_master) { create(:user) }
+ let(:project) { create(:empty_project) }
+ background do
+ project.team << [project_master, :master]
+ login_as(project_master)
+ end
+
+ scenario 'project master creates a license file from a template' do
+ visit namespace_project_path(project.namespace, project)
+ click_link 'Create empty bare repository'
+ click_on 'LICENSE'
+
+ expect(current_path).to eq(
+ namespace_project_new_blob_path(project.namespace, project, 'master'))
+ expect(find('#file_name').value).to eq('LICENSE')
+ expect(page).to have_selector('.license-selector')
+
+ select2('mit', from: '#license_type')
+
+ file_content = find('.file-content')
+ expect(file_content).to have_content('The MIT License (MIT)')
+ expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+
+ fill_in :commit_message, with: 'Add a LICENSE file', visible: true
+ # Remove pre-receive hook so we can push without auth
+ FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
+ click_button 'Commit Changes'
+
+ expect(current_path).to eq(
+ namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ expect(page).to have_content('The MIT License (MIT)')
+ expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
+ end
+end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
new file mode 100644
index 00000000000..c5e3d143d91
--- /dev/null
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Anonymous user sees members', feature: true do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public) }
+
+ background do
+ project.team << [user, :master]
+ create(:project_group_link, project: project, group: group)
+ end
+
+ scenario "anonymous user visits the project's members page and sees the list of members" do
+ visit namespace_project_project_members_path(project.namespace, project)
+
+ expect(current_path).to eq(
+ namespace_project_project_members_path(project.namespace, project))
+ expect(page).to have_content(user.name)
+ end
+end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
new file mode 100644
index 00000000000..7e6eef65873
--- /dev/null
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User creates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ context 'when wiki is empty' do
+ scenario 'directly from the wiki home page' do
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'when wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ end
+
+ scenario 'via the "new wiki page" page', js: true do
+ click_link 'New Page'
+
+ fill_in :new_wiki_path, with: 'foo'
+ click_button 'Create Page'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Create page'
+
+ expect(page).to have_content('Foo')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
new file mode 100644
index 00000000000..ef82d2375dd
--- /dev/null
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User updates wiki page', feature: true do
+ let(:user) { create(:user) }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit namespace_project_path(project.namespace, project)
+ WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ click_link 'Wiki'
+ end
+
+ context 'in the user namespace' do
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+
+ context 'in a group namespace' do
+ let(:project) { create(:project, namespace: create(:group, :public)) }
+
+ scenario 'the home page' do
+ click_link 'Edit'
+
+ fill_in :wiki_content, with: 'My awesome wiki!'
+ click_button 'Save changes'
+
+ expect(page).to have_content('Home')
+ expect(page).to have_content("last edited by #{user.name}")
+ expect(page).to have_content('My awesome wiki!')
+ end
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 782c0bfe666..9dd0378d165 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -104,6 +104,33 @@ feature 'Project', feature: true do
end
end
+ describe 'project title' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:project2) { create(:project, namespace: user.namespace, path: 'test') }
+ let(:issue) { create(:issue, project: project) }
+
+ context 'on issues page', js: true do
+ before do
+ login_with(user)
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ project2.team.add_user(user, Gitlab::Access::MASTER)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'click toggle and show dropdown' do
+ find('.js-projects-dropdown-toggle').click
+ expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 2)
+
+ page.within '.dropdown-menu-projects' do
+ click_link project.name_with_namespace
+ end
+
+ expect(page).to have_content project.name
+ end
+ end
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index e8886e7edf9..8edeb8d18af 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -80,6 +80,22 @@ describe "Runners" do
end
end
+ describe "shared runners description" do
+ let(:shared_runners_text) { 'custom **shared** runners description' }
+ let(:shared_runners_html) { 'custom shared runners description' }
+
+ before do
+ stub_application_setting(shared_runners_text: shared_runners_text)
+ project = FactoryGirl.create :empty_project, shared_runners_enabled: false
+ project.team << [user, :master]
+ visit runners_path(project)
+ end
+
+ it "sees shared runners description" do
+ expect(page.find(".shared-runners-description")).to have_content(shared_runners_html)
+ end
+ end
+
describe "show page" do
before do
@project = FactoryGirl.create :empty_project
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 79d5bf4cf06..8625ea6bc10 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_denied_for :external }
end
describe "GET /:project_path/blob" do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 0a89193eb67..544270b4037 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 40daac89d40..4def4f99bc0 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
- it { is_expected.to be_denied_for developer }
- it { is_expected.to be_denied_for reporter }
- it { is_expected.to be_denied_for guest }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :external }
- it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for :external }
end
describe "GET /:project_path/builds" do
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
new file mode 100644
index 00000000000..58aabd913eb
--- /dev/null
+++ b/spec/features/signup_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+feature 'Signup', feature: true do
+ describe 'signup with no errors' do
+ it 'creates the user account and sends a confirmation email' do
+ user = build(:user)
+
+ visit root_path
+
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: user.password
+ click_button "Sign up"
+
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content("Please check your email to confirm your account")
+ end
+ end
+
+ describe 'signup with errors' do
+ it "displays the errors" do
+ existing_user = create(:user)
+ user = build(:user)
+
+ visit root_path
+
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
+ click_button "Sign up"
+
+ expect(current_path).to eq user_registration_path
+ expect(page).to have_content("error prohibited this user from being saved")
+ expect(page).to have_content("Email has already been taken")
+ end
+
+ it 'does not redisplay the password' do
+ existing_user = create(:user)
+ user = build(:user)
+
+ visit root_path
+
+ fill_in 'new_user_name', with: user.name
+ fill_in 'new_user_username', with: user.username
+ fill_in 'new_user_email', with: existing_user.email
+ fill_in 'new_user_password', with: user.password
+ click_button "Sign up"
+
+ expect(current_path).to eq user_registration_path
+ expect(page.body).not_to match(/#{user.password}/)
+ end
+ end
+end
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
new file mode 100644
index 00000000000..08a97085a9c
--- /dev/null
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+feature 'Master creates tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'with an invalid name displays an error' do
+ create_tag_in_form(tag: 'v 1.0', ref: 'master')
+
+ expect(page).to have_content 'Tag name invalid'
+ end
+
+ scenario 'with an invalid reference displays an error' do
+ create_tag_in_form(tag: 'v2.0', ref: 'foo')
+
+ expect(page).to have_content 'Target foo is invalid'
+ end
+
+ scenario 'that already exists displays an error' do
+ create_tag_in_form(tag: 'v1.1.0', ref: 'master')
+
+ expect(page).to have_content 'Tag v1.1.0 already exists'
+ end
+
+ scenario 'with multiline message displays the message in a <pre> block' do
+ create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v3.0'))
+ expect(page).to have_content 'v3.0'
+ page.within 'pre.body' do
+ expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
+ end
+ end
+
+ scenario 'with multiline release notes parses the release note as Markdown' do
+ create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v4.0'))
+ expect(page).to have_content 'v4.0'
+ page.within '.description' do
+ expect(page).to have_content 'Awesome release notes'
+ expect(page).to have_selector('ul li', count: 2)
+ end
+ end
+
+ def create_tag_in_form(tag:, ref:, message: nil, desc: nil)
+ click_link 'New tag'
+ fill_in 'tag_name', with: tag
+ fill_in 'ref', with: ref
+ fill_in 'message', with: message unless message.nil?
+ fill_in 'release_description', with: desc unless desc.nil?
+ click_button 'Create tag'
+ end
+end
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
new file mode 100644
index 00000000000..f0990118e3c
--- /dev/null
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+feature 'Master deletes tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ context 'from the tags list page' do
+ scenario 'deletes the tag' do
+ expect(page).to have_content 'v1.1.0'
+
+ page.within('.content') do
+ first('.btn-remove').click
+ end
+
+ expect(current_path).to eq(
+ namespace_project_tags_path(project.namespace, project))
+ expect(page).not_to have_content 'v1.1.0'
+ end
+
+ end
+
+ context 'from a specific tag page' do
+ scenario 'deletes the tag' do
+ click_on 'v1.0.0'
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Delete tag'
+
+ expect(current_path).to eq(
+ namespace_project_tags_path(project.namespace, project))
+ expect(page).not_to have_content 'v1.0.0'
+ end
+ end
+end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
new file mode 100644
index 00000000000..c926e9841f3
--- /dev/null
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+feature 'Master updates tag', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ context 'from the tags list page' do
+ scenario 'updates the release notes' do
+ page.within(first('.controls')) do
+ click_link 'Edit release notes'
+ end
+
+ fill_in 'release_description', with: 'Awesome release notes'
+ click_button 'Save changes'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ expect(page).to have_content 'v1.1.0'
+ expect(page).to have_content 'Awesome release notes'
+ end
+ end
+
+ context 'from a specific tag page' do
+ scenario 'updates the release notes' do
+ click_on 'v1.1.0'
+ click_link 'Edit release notes'
+ fill_in 'release_description', with: 'Awesome release notes'
+ click_button 'Save changes'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ expect(page).to have_content 'v1.1.0'
+ expect(page).to have_content 'Awesome release notes'
+ end
+ end
+end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
new file mode 100644
index 00000000000..29d2c244720
--- /dev/null
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+feature 'Master views tags', feature: true do
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_with(user)
+ end
+
+ context 'when project has no tags' do
+ let(:project) { create(:project_empty_repo) }
+ before do
+ visit namespace_project_path(project.namespace, project)
+ click_on 'README'
+ fill_in :commit_message, with: 'Add a README file', visible: true
+ # Remove pre-receive hook so we can push without auth
+ FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
+ click_button 'Commit Changes'
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'displays a specific message' do
+ expect(page).to have_content 'Repository has no tags yet.'
+ end
+ end
+
+ context 'when project has tags' do
+ let(:project) { create(:project, namespace: user.namespace) }
+ before do
+ visit namespace_project_tags_path(project.namespace, project)
+ end
+
+ scenario 'views the tags list page' do
+ expect(page).to have_content 'v1.0.0'
+ end
+
+ scenario 'views a specific tag page' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ expect(page).to have_content 'v1.0.0'
+ expect(page).to have_content 'This tag has no release notes.'
+ end
+
+ describe 'links on the tag page' do
+ scenario 'has a button to browse files' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Browse files'
+
+ expect(current_path).to eq(
+ namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+ end
+
+ scenario 'has a button to browse commits' do
+ click_on 'v1.0.0'
+
+ expect(current_path).to eq(
+ namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+ click_on 'Browse commits'
+
+ expect(current_path).to eq(
+ namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+ end
+ end
+ end
+end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
new file mode 100644
index 00000000000..3354f529295
--- /dev/null
+++ b/spec/features/todos/todos_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+describe 'Dashboard Todos', feature: true do
+ let(:user) { create(:user) }
+ let(:author) { create(:user) }
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue) }
+
+ describe 'GET /dashboard/todos' do
+ context 'User does not have todos' do
+ before do
+ login_as(user)
+ visit dashboard_todos_path
+ end
+ it 'shows "All done" message' do
+ expect(page).to have_content "You're all done!"
+ end
+ end
+
+ context 'User has a todo', js: true do
+ before do
+ create(:todo, :mentioned, user: user, project: project, target: issue, author: author)
+ login_as(user)
+ visit dashboard_todos_path
+ end
+
+ it 'todo is present' do
+ expect(page).to have_selector('.todos-list .todo', count: 1)
+ end
+
+ describe 'deleting the todo' do
+ before do
+ first('.done-todo').click
+ end
+
+ it 'is removed from the list' do
+ expect(page).not_to have_selector('.todos-list .todo')
+ end
+
+ it 'shows "All done" message' do
+ expect(page).to have_content("You're all done!")
+ end
+ end
+ end
+
+ context 'User has multiple pages of Todos' do
+ before do
+ allow(Todo).to receive(:default_per_page).and_return(1)
+
+ # Create just enough records to cause us to paginate
+ create_list(:todo, 2, :mentioned, user: user, project: project, target: issue, author: author)
+
+ login_as(user)
+ end
+
+ it 'is paginated' do
+ visit dashboard_todos_path
+
+ expect(page).to have_selector('.gl-pagination')
+ end
+
+ it 'is has the right number of pages' do
+ visit dashboard_todos_path
+
+ expect(page).to have_selector('.gl-pagination .page', count: 2)
+ end
+
+ describe 'completing last todo from last page', js: true do
+ it 'redirects to the previous page' do
+ visit dashboard_todos_path(page: 2)
+ expect(page).to have_css("#todo_#{Todo.last.id}")
+
+ click_link('Done')
+
+ expect(current_path).to eq dashboard_todos_path
+ expect(page).to have_css("#todo_#{Todo.first.id}")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c1248162031..cf116040394 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -5,10 +5,10 @@ feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
- fill_in 'user_name', with: 'Name Surname'
- fill_in 'user_username', with: 'Great'
- fill_in 'user_email', with: 'name@mail.com'
- fill_in 'user_password_sign_up', with: 'password1234'
+ fill_in 'new_user_name', with: 'Name Surname'
+ fill_in 'new_user_username', with: 'Great'
+ fill_in 'new_user_email', with: 'name@mail.com'
+ fill_in 'new_user_password', with: 'password1234'
expect { click_button 'Sign up' }.to change { User.count }.by(1)
end
@@ -31,10 +31,10 @@ feature 'Users', feature: true do
scenario 'Should show one error if email is already taken' do
visit new_user_session_path
- fill_in 'user_name', with: 'Another user name'
- fill_in 'user_username', with: 'anotheruser'
- fill_in 'user_email', with: user.email
- fill_in 'user_password_sign_up', with: '12341234'
+ fill_in 'new_user_name', with: 'Another user name'
+ fill_in 'new_user_username', with: 'anotheruser'
+ fill_in 'new_user_email', with: user.email
+ fill_in 'new_user_password', with: '12341234'
expect { click_button 'Sign up' }.to change { User.count }.by(0)
expect(page).to have_text('Email has already been taken')
expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}'
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index b1648055462..bc607a29751 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -62,6 +62,22 @@ describe IssuesFinder do
expect(issues).to eq([issue2])
end
+ it 'returns unique issues when filtering by multiple labels' do
+ label2 = create(:label, project: project2)
+
+ create(:label_link, label: label2, target: issue2)
+
+ params = {
+ scope: 'all',
+ label_name: [label.title, label2.title].join(','),
+ state: 'opened'
+ }
+
+ issues = IssuesFinder.new(user, params).execute
+
+ expect(issues).to eq([issue2])
+ end
+
it 'should filter by no label name' do
params = { scope: "all", label_name: Label::None.title, state: 'opened' }
issues = IssuesFinder.new(user, params).execute
diff --git a/spec/fixtures/sanitized.svg b/spec/fixtures/sanitized.svg
new file mode 100644
index 00000000000..8f84b8f5e20
--- /dev/null
+++ b/spec/fixtures/sanitized.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682">
+
+ <defs>
+ <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style>
+ </defs>
+ <title>stacked_wm</title>
+ <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/>
+ <g id="g12">
+ <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/>
+ </g>
+ <g id="g24">
+ <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/>
+ </g>
+ <g id="g28">
+ <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/>
+ </g>
+ <g id="g32">
+ <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/>
+ </g>
+ <g id="g36">
+ <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/>
+ </g>
+ <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/>
+ <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/>
+ <g id="g44">
+ <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/>
+ </g>
+ <g id="g48">
+ <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/>
+ </g>
+ <g id="g56">
+ <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/>
+ </g>
+ <g id="g64">
+ <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/>
+ </g>
+ <g id="g72">
+ <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/>
+ </g>
+ <g id="g76">
+ <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/>
+ </g>
+ <g id="g80">
+ <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/>
+ </g>
+ <g id="g84">
+ <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/>
+ </g>
+</svg>
diff --git a/spec/fixtures/unsanitized.svg b/spec/fixtures/unsanitized.svg
new file mode 100644
index 00000000000..3957557334b
--- /dev/null
+++ b/spec/fixtures/unsanitized.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 622 682" filterMe="test">
+ <iframe src="http://www.google.com"></iframe>
+ <defs>
+ <style>.cls-1{fill:#30353e;}.cls-2{fill:#8c929d;}.cls-3{fill:#fc6d26;}.cls-4{fill:#e24329;}.cls-5{fill:#fca326;}</style>
+ </defs>
+ <title>stacked_wm</title>
+ <path id="bg" class="cls-1" d="M622,681H0V-1H622V681h0Z"/>
+ <g id="g12">
+ <path id="path14" class="cls-2" d="M316.89,497.72h-19l0.06,141.74H375V621.93h-58l-0.06-124.22h0Z"/>
+ </g>
+ <g id="g24">
+ <path id="path26" class="cls-2" d="M448.32,614.57a32.46,32.46,0,0,1-23.59,10c-14.5,0-20.35-7.14-20.35-16.45,0-14.07,9.74-20.77,30.52-20.77a86.46,86.46,0,0,1,13.42,1.08v26.19h0Zm-19.7-85.91a63.45,63.45,0,0,0-40.5,14.53l6.73,11.66c7.79-4.54,17.32-9.09,31-9.09,15.58,0,22.51,8,22.51,21.42v6.93a81.48,81.48,0,0,0-13.2-1.08c-33.33,0-50.22,11.69-50.22,36.14,0,21.86,13.42,32.89,33.76,32.89,13.71,0,26.84-6.28,31.38-16.45l3.46,13.85h13.42V567c0-22.94-10-38.3-38.31-38.3h0Z"/>
+ </g>
+ <g id="g28">
+ <path id="path30" class="cls-2" d="M528.4,625.18c-7.14,0-13.42-.87-18.18-3V556.58c6.49-5.41,14.5-9.31,24.68-9.31,18.4,0,25.54,13,25.54,34,0,29.86-11.47,43.93-32,43.93m8-96.52a34.88,34.88,0,0,0-26.19,11.58V522l-0.06-24.24H491.54L491.6,636c9.31,3.9,22.08,6.06,35.93,6.06,35.5,0,52.6-22.72,52.6-61.89,0-30.95-15.8-51.51-43.73-51.51"/>
+ </g>
+ <g id="g32">
+ <path id="path34" class="cls-2" d="M109.84,513.08c16.88,0,27.7,5.63,34.85,11.25l8.19-14.18c-11.16-9.78-26.16-15-42.17-15-40.47,0-68.83,24.67-68.83,74.44,0,52.15,30.59,72.5,65.58,72.5a111,111,0,0,0,42.21-8.22l-0.4-55.72V560.58H97.32v17.53h33.12l0.4,42.31c-4.33,2.16-11.9,3.9-22.08,3.9-28.14,0-47-17.7-47-55,0-37.87,19.48-56.26,48.05-56.26"/>
+ </g>
+ <g id="g36">
+ <path id="path38" class="cls-2" d="M243.79,497.72H225.17l0.06,23.8v82.23c0,22.94,10,38.3,38.31,38.3A64.16,64.16,0,0,0,275,641V624.31a57,57,0,0,1-8.66.65c-15.58,0-22.51-8-22.51-21.42v-56.7H275V531.26H243.85l-0.06-33.54h0Z"/>
+ </g>
+ <path id="path40" class="cls-2" d="M177.94,639.46h18.61V531.26H177.94v108.2h0Z"/>
+ <path id="path42" class="cls-2" d="M177.94,516.33h18.61V497.72H177.94v18.61h0Z"/>
+ <g id="g44">
+ <path id="path46" class="cls-3" d="M525.05,266.23l-24-74L453.36,45.6a8.19,8.19,0,0,0-15.58,0L390.12,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24l-24,74a16.38,16.38,0,0,0,6,18.31L311,435.71,519.1,284.54a16.38,16.38,0,0,0,6-18.31"/>
+ </g>
+ <g id="g48">
+ <path id="path50" class="cls-4" d="M311,435.71h0l79.12-243.47H231.88L311,435.71h0Z"/>
+ </g>
+ <g id="g56">
+ <path id="path58" class="cls-3" d="M311,435.71L231.88,192.24H121L311,435.71h0Z"/>
+ </g>
+ <g id="g64">
+ <path id="path66" class="cls-5" d="M121,192.24h0l-24,74a16.37,16.37,0,0,0,6,18.31L311,435.7,121,192.24h0Z"/>
+ </g>
+ <g id="g72">
+ <path id="path74" class="cls-4" d="M121,192.24H231.88L184.22,45.6a8.19,8.19,0,0,0-15.58,0L121,192.24h0Z"/>
+ </g>
+ <g id="g76">
+ <path id="path78" class="cls-3" d="M311,435.71l79.12-243.47H501L311,435.71h0Z"/>
+ </g>
+ <g id="g80">
+ <path id="path82" class="cls-5" d="M501,192.24h0l24,74a16.37,16.37,0,0,1-6,18.31L311,435.7,501,192.24h0Z"/>
+ </g>
+ <g id="g84">
+ <path id="path86" class="cls-4" d="M501,192.24H390.12L437.78,45.6a8.19,8.19,0,0,1,15.58,0L501,192.24h0Z"/>
+ </g>
+</svg>
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 87849230dbe..6d1c02db297 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -67,4 +67,16 @@ describe BlobHelper do
expect(result).to eq(expected)
end
end
+
+ describe "#sanitize_svg" do
+ let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') }
+ let(:data) { open(input_svg_path).read }
+ let(:expected_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') }
+ let(:expected) { open(expected_svg_path).read }
+
+ it 'should retain essential elements' do
+ blob = OpenStruct.new(data: data)
+ expect(sanitize_svg(blob).data).to eq(expected)
+ end
+ end
end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 4f8d9c67262..f942695b6f0 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -6,8 +6,8 @@ describe CiStatusHelper do
let(:success_commit) { double("Ci::Commit", status: 'success') }
let(:failed_commit) { double("Ci::Commit", status: 'failed') }
- describe 'ci_status_icon' do
- it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') }
- it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') }
+ describe 'ci_icon_for_status' do
+ it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
+ it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
end
end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
new file mode 100644
index 00000000000..727c25ff529
--- /dev/null
+++ b/spec/helpers/commits_helper_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+describe CommitsHelper do
+ describe 'commit_author_link' do
+ it 'escapes the author email' do
+ commit = double(
+ author: nil,
+ author_name: 'Persistent XSS',
+ author_email: 'my@email.com" onmouseover="alert(1)'
+ )
+
+ expect(helper.commit_author_link(commit)).
+ not_to include('onmouseover="alert(1)"')
+ end
+ end
+
+ describe 'commit_committer_link' do
+ it 'escapes the committer email' do
+ commit = double(
+ committer: nil,
+ committer_name: 'Persistent XSS',
+ committer_email: 'my@email.com" onmouseover="alert(1)'
+ )
+
+ expect(helper.commit_committer_link(commit)).
+ not_to include('onmouseover="alert(1)"')
+ end
+ end
+end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 982c113e84b..b7810185d16 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -11,6 +11,26 @@ describe DiffHelper do
let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ describe 'diff_view' do
+ it 'returns a valid value when cookie is set' do
+ helper.request.cookies[:diff_view] = 'parallel'
+
+ expect(helper.diff_view).to eq 'parallel'
+ end
+
+ it 'returns a default value when cookie is invalid' do
+ helper.request.cookies[:diff_view] = 'invalid'
+
+ expect(helper.diff_view).to eq 'inline'
+ end
+
+ it 'returns a default value when cookie is nil' do
+ expect(helper.request.cookies).to be_empty
+
+ expect(helper.diff_view).to eq 'inline'
+ end
+ end
+
describe 'diff_hard_limit_enabled?' do
it 'should return true if param is provided' do
allow(controller).to receive(:params) { { force_show_diff: true } }
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
new file mode 100644
index 00000000000..3391234e9f5
--- /dev/null
+++ b/spec/helpers/import_helper_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+describe ImportHelper do
+ describe '#github_project_link' do
+ context 'when provider does not specify a custom URL' do
+ it 'uses default GitHub URL' do
+ allow(Gitlab.config.omniauth).to receive(:providers).
+ and_return([Settingslogic.new('name' => 'github')])
+
+ expect(helper.github_project_link('octocat/Hello-World')).
+ to include('href="https://github.com/octocat/Hello-World"')
+ end
+ end
+
+ context 'when provider specify a custom URL' do
+ it 'uses custom URL' do
+ allow(Gitlab.config.omniauth).to receive(:providers).
+ and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
+
+ expect(helper.github_project_link('octocat/Hello-World')).
+ to include('href="https://github.company.com/octocat/Hello-World"')
+ end
+ end
+ end
+end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 2d4d9c18c9d..eae61a54dfc 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -30,6 +30,18 @@ describe IssuesHelper do
expect(url_for_project_issues).to eq ""
end
+ it 'returns an empty string if project_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project)).to eq ''
+ end
+
+ it 'returns an empty string if project_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_project_issues(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -68,6 +80,18 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_issue(issue.iid, project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
@@ -105,6 +129,18 @@ describe IssuesHelper do
expect(url_for_new_issue).to eq ""
end
+ it 'returns an empty string if issue_url is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project)).to eq ''
+ end
+
+ it 'returns an empty string if issue_path is invalid' do
+ expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
+
+ expect(url_for_new_issue(project, only_path: true)).to eq ''
+ end
+
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 39042ff7e91..501f150cfda 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -11,13 +11,13 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}"><span class="[\w\s\-]*has-tooltip".*</span></a>}
+ expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name%5B%5D=#{label.name}"><span class="[\w\s\-]*has-tooltip".*</span></a>}
end
end
context 'without @project set' do
it "uses the label's project" do
- expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
+ expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
@@ -25,7 +25,7 @@ describe LabelsHelper do
let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
it 'links to merge requests page' do
- expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
+ expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
@@ -33,7 +33,7 @@ describe LabelsHelper do
['issue', :issue, 'merge_request', :merge_request].each do |type|
context "set to #{type}" do
it 'links to correct page' do
- expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
+ expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>}
end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index c258cfebd73..ac5af8740dc 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -88,21 +88,56 @@ describe ProjectsHelper do
end
describe 'default_clone_protocol' do
- describe 'using HTTP' do
+ context 'when user is not logged in and gitlab protocol is HTTP' do
it 'returns HTTP' do
- expect(helper).to receive(:current_user).and_return(nil)
+ allow(helper).to receive(:current_user).and_return(nil)
expect(helper.send(:default_clone_protocol)).to eq('http')
end
end
- describe 'using HTTPS' do
+ context 'when user is not logged in and gitlab protocol is HTTPS' do
it 'returns HTTPS' do
- allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https')
- expect(helper).to receive(:current_user).and_return(nil)
+ stub_config_setting(protocol: 'https')
+ allow(helper).to receive(:current_user).and_return(nil)
expect(helper.send(:default_clone_protocol)).to eq('https')
end
end
end
+
+ describe '#license_short_name' do
+ let(:project) { create(:project) }
+
+ context 'when project.repository has a license_key' do
+ it 'returns the nickname of the license if present' do
+ allow(project.repository).to receive(:license_key).and_return('agpl-3.0')
+
+ expect(helper.license_short_name(project)).to eq('GNU AGPLv3')
+ end
+
+ it 'returns the name of the license if nickname is not present' do
+ allow(project.repository).to receive(:license_key).and_return('mit')
+
+ expect(helper.license_short_name(project)).to eq('MIT License')
+ end
+ end
+
+ context 'when project.repository has no license_key but a license_blob' do
+ it 'returns LICENSE' do
+ allow(project.repository).to receive(:license_key).and_return(nil)
+
+ expect(helper.license_short_name(project)).to eq('LICENSE')
+ end
+ end
+ end
+
+ describe '#sanitized_import_error' do
+ it 'removes the repo path' do
+ repo = File.join(Gitlab.config.gitlab_shell.repos_path, '/namespace/test.git')
+ import_error = "Could not clone #{repo}\n"
+
+ expect(sanitize_repo_path(import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git')
+ end
+ end
end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
new file mode 100644
index 00000000000..4bb149f25ff
--- /dev/null
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe 'trusted_proxies', lib: true do
+ context 'with default config' do
+ before do
+ set_trusted_proxies([])
+ end
+
+ it 'preserves private IPs as remote_ip' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '10.1.5.89')
+ expect(request.remote_ip).to eq('10.1.5.89')
+ end
+
+ it 'filters out localhost from remote_ip' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '1.1.1.1, 10.1.5.89, 127.0.0.1')
+ expect(request.remote_ip).to eq('10.1.5.89')
+ end
+ end
+
+ context 'with private IP ranges added' do
+ before do
+ set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ])
+ end
+
+ it 'filters out private and local IPs from remote_ip' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 10.1.5.89, 127.0.0.1')
+ expect(request.remote_ip).to eq('1.1.1.1')
+ end
+ end
+
+ context 'with proxy IP added' do
+ before do
+ set_trusted_proxies([ "60.98.25.47" ])
+ end
+
+ it 'filters out proxy IP from remote_ip' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 60.98.25.47, 127.0.0.1')
+ expect(request.remote_ip).to eq('1.1.1.1')
+ end
+ end
+
+ def stub_request(headers = {})
+ ActionDispatch::RemoteIp.new(Proc.new { }, false, Rails.application.config.action_dispatch.trusted_proxies).call(headers)
+ ActionDispatch::Request.new(headers)
+ end
+
+ def set_trusted_proxies(proxies = [])
+ stub_config_setting('trusted_proxies' => proxies)
+ load File.join(__dir__, '../../config/initializers/trusted_proxies.rb')
+ end
+end
diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee
new file mode 100644
index 00000000000..92b7eeb1116
--- /dev/null
+++ b/spec/javascripts/merge_request_widget_spec.js.coffee
@@ -0,0 +1,55 @@
+#= require merge_request_widget
+
+describe 'MergeRequestWidget', ->
+
+ beforeEach ->
+ window.notifyPermissions = () ->
+ window.notify = () ->
+ @opts = {
+ ci_status_url:"http://sampledomain.local/ci/getstatus",
+ ci_status:"",
+ ci_message: {
+ normal: "Build {{status}} for \"{{title}}\"",
+ preparing: "{{status}} build for \"{{title}}\""
+ },
+ ci_title: {
+ preparing: "{{status}} build",
+ normal: "Build {{status}}"
+ },
+ gitlab_icon:"gitlab_logo.png",
+ builds_path:"http://sampledomain.local/sampleBuildsPath"
+ }
+ @class = new MergeRequestWidget(@opts)
+ @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98}
+
+ describe 'getCIStatus', ->
+ beforeEach ->
+ spyOn(jQuery, 'getJSON').and.callFake (req, cb) =>
+ cb(@ciStatusData)
+
+ it 'should call showCIStatus even if a notification should not be displayed', ->
+ spy = spyOn(@class, 'showCIStatus').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
+
+ it 'should call showCIStatus when a notification should be displayed', ->
+ spy = spyOn(@class, 'showCIStatus').and.stub()
+ @class.getCIStatus(true)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
+
+ it 'should call showCICoverage when the coverage rate is set', ->
+ spy = spyOn(@class, 'showCICoverage').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage)
+
+ it 'should not call showCICoverage when the coverage rate is not set', ->
+ @ciStatusData.coverage = null
+ spy = spyOn(@class, 'showCICoverage').and.stub()
+ @class.getCIStatus(false)
+ expect(spy).not.toHaveBeenCalled()
+
+ it 'should not display a notification on the first check after the widget has been created', ->
+ spy = spyOn(window, 'notify')
+ @class = new MergeRequestWidget(@opts)
+ @class.getCIStatus(true)
+ expect(spy).not.toHaveBeenCalled()
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index e3a8e15330e..f4c5c621bd0 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -24,6 +24,14 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do
doc = filter(act)
expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to eq 'nofollow'
+ expect(doc.at_css('a')['rel']).to include 'nofollow'
+ end
+
+ it 'adds rel="noreferrer" 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 include 'noreferrer'
end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 94468abcbb3..b0a38e7c251 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
describe 'cross project label references' do
- let(:another_project) { create(:empty_project, :public) }
- let(:project_name) { another_project.name_with_namespace }
- let(:label) { create(:label, project: another_project, color: '#00ff00') }
- let(:reference) { label.to_reference(project) }
+ context 'valid project referenced' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:label) { create(:label, project: another_project, color: '#00ff00') }
+ let(:reference) { label.to_reference(project) }
- let!(:result) { reference_filter("See #{reference}") }
+ let!(:result) { reference_filter("See #{reference}") }
- it 'points to referenced project issues page' do
- expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: label.name)
- end
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
- it 'has valid color' do
- expect(result.css('a span').first.attr('style'))
- .to match /background-color: #00ff00/
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ end
end
- it 'contains cross project content' do
- expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ context 'project that does not exist referenced' do
+ let(:result) { reference_filter('aaa/bbb~ccc') }
+
+ it 'does not link reference' do
+ expect(result.to_html).to eq 'aaa/bbb~ccc'
+ end
end
end
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index ebf3d7489b5..5beb61dac5c 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -43,7 +43,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
milestone.update_attribute(:title, %{"></a>whatever<a title="})
doc = reference_filter("milestone #{reference}")
- expect(doc.text).to eq "milestone #{milestone.title}"
+ expect(doc.text).to eq "milestone \">whatever"
end
it 'includes default classes' do
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 27ce312b11c..b38e3b17e64 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -22,6 +22,12 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'sanitizes mixed-cased javascript in attributes' do
+ act = %q(<a href="javaScript:alert('foo')">Text</a>)
+ exp = '<a>Text</a>'
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'allows whitelisted HTML tags from the user' do
exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
expect(filter(act).to_html).to eq exp
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index dcb8a3451bd..c7ab3185378 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -286,6 +286,81 @@ module Ci
end
end
+
+ describe "Scripts handling" do
+ let(:config_data) { YAML.dump(config) }
+ let(:config_processor) { GitlabCiYamlProcessor.new(config_data, path) }
+
+ subject { config_processor.builds_for_stage_and_ref("test", "master").first }
+
+ describe "before_script" do
+ context "in global context" do
+ let(:config) do
+ {
+ before_script: ["global script"],
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("global script\nscript")
+ end
+ end
+
+ context "overwritten in local context" do
+ let(:config) do
+ {
+ before_script: ["global script"],
+ test: { before_script: ["local script"], script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("local script\nscript")
+ end
+ end
+ end
+
+ describe "script" do
+ let(:config) do
+ {
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return commands with scripts concencaced" do
+ expect(subject[:commands]).to eq("script")
+ end
+ end
+
+ describe "after_script" do
+ context "in global context" do
+ let(:config) do
+ {
+ after_script: ["after_script"],
+ test: { script: ["script"] }
+ }
+ end
+
+ it "return after_script in options" do
+ expect(subject[:options][:after_script]).to eq(["after_script"])
+ end
+ end
+
+ context "overwritten in local context" do
+ let(:config) do
+ {
+ after_script: ["local after_script"],
+ test: { after_script: ["local after_script"], script: ["script"] }
+ }
+ end
+
+ it "return after_script in options" do
+ expect(subject[:options][:after_script]).to eq(["local after_script"])
+ end
+ end
+ end
+ end
describe "Image and service handling" do
it "returns image and service when defined" do
@@ -345,20 +420,76 @@ module Ci
end
end
- describe "Variables" do
- it "returns variables when defined" do
- variables = {
- var1: "value1",
- var2: "value2",
- }
- config = YAML.dump({
- variables: variables,
- before_script: ["pwd"],
- rspec: { script: "rspec" }
- })
+ describe 'Variables' do
+ context 'when global variables are defined' do
+ it 'returns global variables' do
+ variables = {
+ VAR1: 'value1',
+ VAR2: 'value2',
+ }
- config_processor = GitlabCiYamlProcessor.new(config, path)
- expect(config_processor.variables).to eq(variables)
+ config = YAML.dump({
+ variables: variables,
+ before_script: ['pwd'],
+ rspec: { script: 'rspec' }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.global_variables).to eq(variables)
+ end
+ end
+
+ context 'when job variables are defined' do
+ context 'when syntax is correct' do
+ it 'returns job variables' do
+ variables = {
+ KEY1: 'value1',
+ SOME_KEY_2: 'value2'
+ }
+
+ config = YAML.dump(
+ { before_script: ['pwd'],
+ rspec: {
+ variables: variables,
+ script: 'rspec' }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.job_variables(:rspec)).to eq variables
+ end
+ end
+
+ context 'when syntax is incorrect' do
+ it 'raises error' do
+ variables = [:KEY1, 'value1', :KEY2, 'value2']
+
+ config = YAML.dump(
+ { before_script: ['pwd'],
+ rspec: {
+ variables: variables,
+ script: 'rspec' }
+ })
+
+ expect { GitlabCiYamlProcessor.new(config, path) }
+ .to raise_error(GitlabCiYamlProcessor::ValidationError,
+ /job: variables should be a map/)
+ end
+ end
+ end
+
+ context 'when job variables are not defined' do
+ it 'returns empty array' do
+ config = YAML.dump({
+ before_script: ['pwd'],
+ rspec: { script: 'rspec' }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config, path)
+
+ expect(config_processor.job_variables(:rspec)).to eq []
+ end
end
end
@@ -517,70 +648,131 @@ module Ci
end
describe "Hidden jobs" do
- let(:config) do
- YAML.dump({
- '.hidden_job' => { script: 'test' },
- 'normal_job' => { script: 'test' }
- })
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("test", "master") }
+
+ shared_examples 'hidden_job_handling' do
+ it "doesn't create jobs that start with dot" do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :normal_job,
+ only: nil,
+ commands: "test",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
end
- let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ context 'when hidden job have a script definition' do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1', script: 'test' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
- subject { config_processor.builds_for_stage_and_ref("test", "master") }
+ it_behaves_like 'hidden_job_handling'
+ end
- it "doesn't create jobs that starts with dot" do
- expect(subject.size).to eq(1)
- expect(subject.first).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :normal_job,
- only: nil,
- commands: "\ntest",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
+ context "when hidden job doesn't have a script definition" do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { image: 'ruby:2.1' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ it_behaves_like 'hidden_job_handling'
end
end
describe "YAML Alias/Anchor" do
- it "is correctly supported for jobs" do
- config = <<EOT
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+ subject { config_processor.builds_for_stage_and_ref("build", "master") }
+
+ shared_examples 'job_templates_handling' do
+ it "is correctly supported for jobs" do
+ expect(subject.size).to eq(2)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "build",
+ stage_idx: 0,
+ name: :job1,
+ only: nil,
+ commands: "execute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ expect(subject.second).to eq({
+ except: nil,
+ stage: "build",
+ stage_idx: 0,
+ name: :job2,
+ only: nil,
+ commands: "execute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
+ context 'when template is a job' do
+ let(:config) do
+ <<EOT
job1: &JOBTMPL
+ stage: build
script: execute-script-for-job
job2: *JOBTMPL
EOT
+ end
- config_processor = GitlabCiYamlProcessor.new(config)
+ it_behaves_like 'job_templates_handling'
+ end
- expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(2)
- expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :job1,
- only: nil,
- commands: "\nexecute-script-for-job",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
- expect(config_processor.builds_for_stage_and_ref("test", "master").second).to eq({
- except: nil,
- stage: "test",
- stage_idx: 1,
- name: :job2,
- only: nil,
- commands: "\nexecute-script-for-job",
- tag_list: [],
- options: {},
- when: "on_success",
- allow_failure: false
- })
+ context 'when template is a hidden job' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+ script: execute-script-for-job
+
+job1: *JOBTMPL
+
+job2: *JOBTMPL
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
+ end
+
+ context 'when job adds its own keys to a template definition' do
+ let(:config) do
+ <<EOT
+.template: &JOBTMPL
+ stage: build
+
+job1:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+
+job2:
+ <<: *JOBTMPL
+ script: execute-script-for-job
+EOT
+ end
+
+ it_behaves_like 'job_templates_handling'
end
end
@@ -607,6 +799,27 @@ EOT
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings")
end
+ it "returns errors if job before_script parameter is not an array of strings" do
+ config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
+ expect do
+ GitlabCiYamlProcessor.new(config, path)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings")
+ end
+
+ it "returns errors if after_script parameter is invalid" do
+ config = YAML.dump({ after_script: "bundle update", rspec: { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config, path)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "after_script should be an array of strings")
+ end
+
+ it "returns errors if job after_script parameter is not an array of strings" do
+ config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
+ expect do
+ GitlabCiYamlProcessor.new(config, path)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: after_script should be an array of strings")
+ end
+
it "returns errors if image parameter is invalid" do
config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do
@@ -730,14 +943,14 @@ EOT
config = YAML.dump({ variables: "test", rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end
- it "returns errors if variables is not a map of key-valued strings" do
+ it "returns errors if variables is not a map of key-value strings" do
config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end
it "returns errors if job when is not on_success, on_failure or always" do
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
index 9858935180a..53f5d6c5c80 100644
--- a/spec/lib/gitlab/akismet_helper_spec.rb
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::AkismetHelper, type: :helper do
describe '#is_spam?' do
it 'returns true for spam' do
environment = {
- 'REMOTE_ADDR' => '127.0.0.1',
+ 'action_dispatch.remote_ip' => '127.0.0.1',
'HTTP_USER_AGENT' => 'Test User Agent'
}
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index 329792bb685..b6f7a2e7ec4 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do
end
context 'build exists' do
- let(:ci_commit) { create(:ci_commit, project: project, sha: sha) }
+ let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) }
let!(:build) { create(:ci_build, commit: ci_commit) }
@@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do
describe '#data' do
let(:data) { badge.data }
- it 'contains infromation about success' do
+ it 'contains information about success' do
expect(status_node(data, 'success')).to be_truthy
end
end
@@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do
describe '#data' do
let(:data) { badge.data }
- it 'contains infromation about failure' do
+ it 'contains information about failure' do
expect(status_node(data, 'failed')).to be_truthy
end
end
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index aa0699f2ebf..af839f42421 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -34,18 +34,32 @@ describe Gitlab::BitbucketImport::Client, lib: true do
it 'retrieves issues over a number of pages' do
stub_request(:get,
"https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=0").
- to_return(status: 200,
- body: first_sample_data.to_json,
- headers: {})
+ to_return(status: 200,
+ body: first_sample_data.to_json,
+ headers: {})
stub_request(:get,
"https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=50").
- to_return(status: 200,
- body: second_sample_data.to_json,
- headers: {})
+ to_return(status: 200,
+ body: second_sample_data.to_json,
+ headers: {})
issues = client.issues(project_id)
expect(issues.count).to eq(95)
end
end
+
+ context 'project import' do
+ it 'calls .from_project with no errors' do
+ project = create(:empty_project)
+ project.create_or_update_import_data(credentials:
+ { user: "git",
+ password: nil,
+ bb_session: { bitbucket_access_token: "test",
+ bitbucket_access_token_secret: "test" } })
+ project.import_url = "ssh://git@bitbucket.org/test/test.git"
+
+ expect { described_class.from_project(project) }.to_not raise_error
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 49d8cdf4314..7c21cbe96d9 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -2,15 +2,49 @@ require 'spec_helper'
describe Gitlab::GithubImport::Client, lib: true do
let(:token) { '123456' }
- let(:client) { Gitlab::GithubImport::Client.new(token) }
+ let(:github_provider) { Settingslogic.new('app_id' => 'asd123', 'app_secret' => 'asd123', 'name' => 'github', 'args' => { 'client_options' => {} }) }
+
+ subject(:client) { described_class.new(token) }
before do
- Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github")
+ allow(Gitlab.config.omniauth).to receive(:providers).and_return([github_provider])
end
- it 'all OAuth2 client options are symbols' do
+ it 'convert OAuth2 client options to symbols' do
client.client.options.keys.each do |key|
expect(key).to be_kind_of(Symbol)
end
end
+
+ it 'does not crash (e.g. Settingslogic::MissingSetting) when verify_ssl config is not present' do
+ expect { client.api }.not_to raise_error
+ end
+
+ context 'allow SSL verification to be configurable on API' do
+ before do
+ github_provider['verify_ssl'] = false
+ end
+
+ it 'uses supplied value' do
+ expect(client.client.options[:connection_opts][:ssl]).to eq({ verify: false })
+ expect(client.api.connection_options[:ssl]).to eq({ verify: false })
+ end
+ end
+
+ context 'when provider does not specity an API endpoint' do
+ it 'uses GitHub root API endpoint' do
+ expect(client.api.api_endpoint).to eq 'https://api.github.com/'
+ end
+ end
+
+ context 'when provider specify a custom API endpoint' do
+ before do
+ github_provider['args']['client_options']['site'] = 'https://github.company.com/'
+ end
+
+ it 'uses the custom API endpoint' do
+ expect(OmniAuth::Strategies::GitHub).not_to receive(:default_options)
+ expect(client.api.api_endpoint).to eq 'https://github.company.com/'
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index a324a82e69f..55e86d4ceac 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -2,23 +2,25 @@ require 'spec_helper'
describe Gitlab::GithubImport::CommentFormatter, lib: true do
let(:project) { create(:project) }
- let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
- let(:base_data) do
+ let(:base) do
{
body: "I'm having a problem with this.",
user: octocat,
+ commit_id: nil,
+ diff_hunk: nil,
created_at: created_at,
updated_at: updated_at
}
end
- subject(:comment) { described_class.new(project, raw_data)}
+ subject(:comment) { described_class.new(project, raw)}
describe '#attributes' do
context 'when do not reference a portion of the diff' do
- let(:raw_data) { OpenStruct.new(base_data) }
+ let(:raw) { double(base) }
it 'returns formatted attributes' do
expected = {
@@ -36,24 +38,23 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
end
context 'when on a portion of the diff' do
- let(:diff_data) do
+ let(:diff) do
{
body: 'Great stuff',
commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
- diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...',
- path: 'file1.txt',
- position: 1
+ diff_hunk: "@@ -1,5 +1,9 @@\n class User\n def name\n- 'John Doe'\n+ 'Jane Doe'",
+ path: 'file1.txt'
}
end
- let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) }
+ let(:raw) { double(base.merge(diff)) }
it 'returns formatted attributes' do
expected = {
project: project,
note: "*Created by: octocat*\n\nGreat stuff",
commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
- line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1',
+ line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_4_3',
author_id: project.creator_id,
created_at: created_at,
updated_at: updated_at
@@ -64,15 +65,10 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
end
context 'when author is a GitLab user' do
- let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+ let(:raw) { double(base.merge(user: octocat)) }
- it 'returns project#creator_id as author_id when is not a GitLab user' do
- expect(comment.attributes.fetch(:author_id)).to eq project.creator_id
- end
-
- it 'returns GitLab user id as author_id when is a GitLab user' do
+ it 'returns GitLab user id as author_id' do
gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
-
expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
end
end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index fd05428b322..0e7ffbe9b8e 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -2,13 +2,14 @@ require 'spec_helper'
describe Gitlab::GithubImport::IssueFormatter, lib: true do
let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
- let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do
{
number: 1347,
+ milestone: nil,
state: 'open',
title: 'Found a bug',
body: "I'm having a problem with this.",
@@ -26,11 +27,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#attributes' do
context 'when issue is open' do
- let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+ let(:raw_data) { double(base_data.merge(state: 'open')) }
it 'returns formatted attributes' do
expected = {
+ iid: 1347,
project: project,
+ milestone: nil,
title: 'Found a bug',
description: "*Created by: octocat*\n\nI'm having a problem with this.",
state: 'opened',
@@ -46,11 +49,13 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
context 'when issue is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
it 'returns formatted attributes' do
expected = {
+ iid: 1347,
project: project,
+ milestone: nil,
title: 'Found a bug',
description: "*Created by: octocat*\n\nI'm having a problem with this.",
state: 'closed',
@@ -65,7 +70,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when it is assigned to someone' do
- let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+ let(:raw_data) { double(base_data.merge(assignee: octocat)) }
it 'returns nil as assignee_id when is not a GitLab user' do
expect(issue.attributes.fetch(:assignee_id)).to be_nil
@@ -78,8 +83,23 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
end
+ context 'when it has a milestone' do
+ let(:milestone) { double(number: 45) }
+ let(:raw_data) { double(base_data.merge(milestone: milestone)) }
+
+ it 'returns nil when milestone does not exist' do
+ expect(issue.attributes.fetch(:milestone)).to be_nil
+ end
+
+ it 'returns milestone when it exists' do
+ milestone = create(:milestone, project: project, iid: 45)
+
+ expect(issue.attributes.fetch(:milestone)).to eq milestone
+ end
+ end
+
context 'when author is a GitLab user' do
- let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+ let(:raw_data) { double(base_data.merge(user: octocat)) }
it 'returns project#creator_id as author_id when is not a GitLab user' do
expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
@@ -95,7 +115,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#has_comments?' do
context 'when number of comments is greater than zero' do
- let(:raw_data) { OpenStruct.new(base_data.merge(comments: 1)) }
+ let(:raw_data) { double(base_data.merge(comments: 1)) }
it 'returns true' do
expect(issue.has_comments?).to eq true
@@ -103,7 +123,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when number of comments is equal to zero' do
- let(:raw_data) { OpenStruct.new(base_data.merge(comments: 0)) }
+ let(:raw_data) { double(base_data.merge(comments: 0)) }
it 'returns false' do
expect(issue.has_comments?).to eq false
@@ -112,7 +132,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
describe '#number' do
- let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+ let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns pull request number' do
expect(issue.number).to eq 1347
@@ -121,7 +141,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
describe '#valid?' do
context 'when mention a pull request' do
- let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: OpenStruct.new)) }
+ let(:raw_data) { double(base_data.merge(pull_request: double)) }
it 'returns false' do
expect(issue.valid?).to eq false
@@ -129,7 +149,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when does not mention a pull request' do
- let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: nil)) }
+ let(:raw_data) { double(base_data.merge(pull_request: nil)) }
it 'returns true' do
expect(issue.valid?).to eq true
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
new file mode 100644
index 00000000000..e94440a7fb0
--- /dev/null
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::LabelFormatter, lib: true do
+
+ describe '#attributes' do
+ it 'returns formatted attributes' do
+ project = create(:project)
+ raw = double(name: 'improvements', color: 'e6e6e6')
+
+ formatter = described_class.new(project, raw)
+
+ expect(formatter.attributes).to eq({
+ project: project,
+ title: 'improvements',
+ color: '#e6e6e6'
+ })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
new file mode 100644
index 00000000000..5a421e50581
--- /dev/null
+++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
+ let(:project) { create(:empty_project) }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:base_data) do
+ {
+ number: 1347,
+ state: 'open',
+ title: '1.0',
+ description: 'Version 1.0',
+ due_on: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil
+ }
+ end
+
+ subject(:formatter) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when milestone is open' do
+ let(:raw_data) { double(base_data.merge(state: 'open')) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ iid: 1347,
+ project: project,
+ title: '1.0',
+ description: 'Version 1.0',
+ state: 'active',
+ due_date: nil,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(formatter.attributes).to eq(expected)
+ end
+ end
+
+ context 'when milestone is closed' do
+ let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ iid: 1347,
+ project: project,
+ title: '1.0',
+ description: 'Version 1.0',
+ state: 'closed',
+ due_date: nil,
+ created_at: created_at,
+ updated_at: closed_at
+ }
+
+ expect(formatter.attributes).to eq(expected)
+ end
+ end
+
+ context 'when milestone has a due date' do
+ let(:due_date) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { double(base_data.merge(due_on: due_date)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ iid: 1347,
+ project: project,
+ title: '1.0',
+ description: 'Version 1.0',
+ state: 'active',
+ due_date: due_date,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(formatter.attributes).to eq(expected)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index e49dcb42342..e59c0ca110e 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -2,17 +2,18 @@ require 'spec_helper'
describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) }
- let(:repository) { OpenStruct.new(id: 1, fork: false) }
+ let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository }
- let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) }
+ let(:source_branch) { double(ref: 'feature', repo: source_repo) }
let(:target_repo) { repository }
- let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) }
- let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:target_branch) { double(ref: 'master', repo: target_repo) }
+ let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do
{
number: 1347,
+ milestone: nil,
state: 'open',
title: 'New feature',
body: 'Please pull these awesome changes',
@@ -31,10 +32,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
describe '#attributes' do
context 'when pull request is open' do
- let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+ let(:raw_data) { double(base_data.merge(state: 'open')) }
it 'returns formatted attributes' do
expected = {
+ iid: 1347,
title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
@@ -42,6 +44,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project,
target_branch: 'master',
state: 'opened',
+ milestone: nil,
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
@@ -54,10 +57,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when pull request is closed' do
let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) }
it 'returns formatted attributes' do
expected = {
+ iid: 1347,
title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
@@ -65,6 +69,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project,
target_branch: 'master',
state: 'closed',
+ milestone: nil,
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
@@ -77,10 +82,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when pull request is merged' do
let(:merged_at) { DateTime.strptime('2011-01-28T13:01:12Z') }
- let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', merged_at: merged_at)) }
+ let(:raw_data) { double(base_data.merge(state: 'closed', merged_at: merged_at)) }
it 'returns formatted attributes' do
expected = {
+ iid: 1347,
title: 'New feature',
description: "*Created by: octocat*\n\nPlease pull these awesome changes",
source_project: project,
@@ -88,6 +94,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
target_project: project,
target_branch: 'master',
state: 'merged',
+ milestone: nil,
author_id: project.creator_id,
assignee_id: nil,
created_at: created_at,
@@ -99,7 +106,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when it is assigned to someone' do
- let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+ let(:raw_data) { double(base_data.merge(assignee: octocat)) }
it 'returns nil as assignee_id when is not a GitLab user' do
expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
@@ -113,7 +120,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when author is a GitLab user' do
- let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+ let(:raw_data) { double(base_data.merge(user: octocat)) }
it 'returns project#creator_id as author_id when is not a GitLab user' do
expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
@@ -125,10 +132,25 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
end
end
+
+ context 'when it has a milestone' do
+ let(:milestone) { double(number: 45) }
+ let(:raw_data) { double(base_data.merge(milestone: milestone)) }
+
+ it 'returns nil when milestone does not exists' do
+ expect(pull_request.attributes.fetch(:milestone)).to be_nil
+ end
+
+ it 'returns milestone when is exists' do
+ milestone = create(:milestone, project: project, iid: 45)
+
+ expect(pull_request.attributes.fetch(:milestone)).to eq milestone
+ end
+ end
end
describe '#number' do
- let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+ let(:raw_data) { double(base_data.merge(number: 1347)) }
it 'returns pull request number' do
expect(pull_request.number).to eq 1347
@@ -136,11 +158,11 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
describe '#valid?' do
- let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') }
+ let(:invalid_branch) { double(ref: 'invalid-branch').as_null_object }
context 'when source, and target repositories are the same' do
context 'and source and target branches exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
+ let(:raw_data) { double(base_data.merge(head: source_branch, base: target_branch)) }
it 'returns true' do
expect(pull_request.valid?).to eq true
@@ -148,7 +170,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'and source branch doesn not exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+ let(:raw_data) { double(base_data.merge(head: invalid_branch, base: target_branch)) }
it 'returns false' do
expect(pull_request.valid?).to eq false
@@ -156,7 +178,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'and target branch doesn not exists' do
- let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+ let(:raw_data) { double(base_data.merge(head: source_branch, base: invalid_branch)) }
it 'returns false' do
expect(pull_request.valid?).to eq false
@@ -165,8 +187,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when source repo is a fork' do
- let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
- let(:raw_data) { OpenStruct.new(base_data) }
+ let(:source_repo) { double(id: 2, fork: true) }
+ let(:raw_data) { double(base_data) }
it 'returns false' do
expect(pull_request.valid?).to eq false
@@ -174,8 +196,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when target repo is a fork' do
- let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
- let(:raw_data) { OpenStruct.new(base_data) }
+ let(:target_repo) { double(id: 2, fork: true) }
+ let(:raw_data) { double(base_data) }
it 'returns false' do
expect(pull_request.valid?).to eq false
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index ad4290c43bb..5c885a7a982 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do
described_class.instrument_method(@dummy, :foo)
end
- it 'renames the original method' do
- expect(@dummy).to respond_to(:_original_foo)
+ it 'instruments the Class' do
+ target = @dummy.singleton_class
+
+ expect(described_class.instrumented?(target)).to eq(true)
+ end
+
+ it 'defines a proxy method' do
+ mod = described_class.proxy_module(@dummy.singleton_class)
+
+ expect(mod.method_defined?(:foo)).to eq(true)
end
it 'calls the instrumented method with the correct arguments' do
@@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do
expect(dummy.method(:test).arity).to eq(0)
end
+
+ describe 'when a module is instrumented multiple times' do
+ it 'calls the instrumented method with the correct arguments' do
+ described_class.instrument_method(@dummy, :foo)
+
+ expect(@dummy.foo).to eq('foo')
+ end
+ end
end
describe 'with metrics disabled' do
@@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do
it 'does not instrument the method' do
described_class.instrument_method(@dummy, :foo)
- expect(@dummy).to_not respond_to(:_original_foo)
+ target = @dummy.singleton_class
+
+ expect(described_class.instrumented?(target)).to eq(false)
end
end
end
@@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do
instrument_instance_method(@dummy, :bar)
end
- it 'renames the original method' do
- expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+ it 'instruments instances of the Class' do
+ expect(described_class.instrumented?(@dummy)).to eq(true)
+ end
+
+ it 'defines a proxy method' do
+ mod = described_class.proxy_module(@dummy)
+
+ expect(mod.method_defined?(:bar)).to eq(true)
end
it 'calls the instrumented method with the correct arguments' do
@@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do
described_class.
instrument_instance_method(@dummy, :bar)
- expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+ expect(described_class.instrumented?(@dummy)).to eq(false)
end
end
end
@@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do
it 'recursively instruments a class hierarchy' do
described_class.instrument_class_hierarchy(@dummy)
- expect(@child1).to respond_to(:_original_child1_foo)
- expect(@child2).to respond_to(:_original_child2_foo)
+ expect(described_class.instrumented?(@child1.singleton_class)).to eq(true)
+ expect(described_class.instrumented?(@child2.singleton_class)).to eq(true)
- expect(@child1.method_defined?(:_original_child1_bar)).to eq(true)
- expect(@child2.method_defined?(:_original_child2_bar)).to eq(true)
+ expect(described_class.instrumented?(@child1)).to eq(true)
+ expect(described_class.instrumented?(@child2)).to eq(true)
end
it 'does not instrument the root module' do
described_class.instrument_class_hierarchy(@dummy)
- expect(@dummy).to_not respond_to(:_original_foo)
- expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+ expect(described_class.instrumented?(@dummy)).to eq(false)
end
end
@@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public class methods' do
described_class.instrument_methods(@dummy)
- expect(@dummy).to respond_to(:_original_foo)
+ expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
end
it 'only instruments methods directly defined in the module' do
@@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public instance methods' do
described_class.instrument_instance_methods(@dummy)
- expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+ expect(described_class.instrumented?(@dummy)).to eq(true)
end
it 'only instruments methods directly defined in the module' do
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 7bc070a4d09..e3293a01207 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
expect(transaction).to receive(:increment).
with(:sql_duration, 0.2)
+ expect(transaction).to receive(:increment).
+ with(:sql_count, 1)
+
subscriber.sql(event)
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 10177c0e8dd..96f7eabbca6 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -123,4 +123,28 @@ describe Gitlab::Metrics do
end
end
end
+
+ describe '.action=' do
+ context 'without a transaction' do
+ it 'does nothing' do
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ not_to receive(:action=)
+
+ Gitlab::Metrics.action = 'foo'
+ end
+ end
+
+ context 'with a transaction' do
+ it 'sets the action of a transaction' do
+ trans = Gitlab::Metrics::Transaction.new
+
+ expect(Gitlab::Metrics).to receive(:current_transaction).
+ and_return(trans)
+
+ expect(trans).to receive(:action=).with('foo')
+
+ Gitlab::Metrics.action = 'foo'
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 961022b9d12..7fc34139eff 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) }
it { expect(data[:total_commits_count]).to eq(3) }
- it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
- it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
+ it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) }
+ it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) }
it { expect(data[:commits].first[:removed]).to eq([]) }
- include_examples 'project hook data'
+ include_examples 'project hook data with deprecateds'
include_examples 'deprecated repository hook data'
end
@@ -34,9 +34,18 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') }
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
+ it { expect(data[:user_id]).to eq(user.id) }
+ it { expect(data[:user_name]).to eq(user.name) }
+ it { expect(data[:user_email]).to eq(user.email) }
+ it { expect(data[:user_avatar]).to eq(user.avatar_url) }
+ it { expect(data[:project_id]).to eq(project.id) }
+ it { expect(data[:project]).to be_a(Hash) }
it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero }
+ include_examples 'project hook data with deprecateds'
+ include_examples 'deprecated repository hook data'
+
it 'does not raise an error when given nil commits' do
expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
not_to raise_error
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 6ffc0d6e658..bf11472407a 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -106,5 +106,14 @@ describe Gitlab::UrlBuilder, lib: true do
end
end
end
+
+ context 'when passing a WikiPage' do
+ it 'returns a proper URL' do
+ wiki_page = build(:wiki_page)
+ url = described_class.build(wiki_page)
+
+ expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_base_path}/#{wiki_page.slug}"
+ end
+ end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 631b5094f42..495c5cbac00 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -213,7 +213,7 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
end
it 'contains a link to the new merge request' do
@@ -268,7 +268,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
end
it 'contains the name of the previous assignee' do
@@ -302,7 +302,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
end
it 'contains the names of the added labels' do
@@ -331,7 +331,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/i
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/i
end
it 'contains the new status' do
@@ -364,7 +364,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
end
it 'contains the new status' do
@@ -502,7 +502,7 @@ describe Notify do
it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
- is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(#{merge_request.to_reference}\)/
end
it 'contains a link to the merge request note' do
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 583bf15176f..00613c7b671 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -15,7 +15,7 @@ describe RepositoryCheckMailer do
it 'mentions the number of failed checks' do
mail = described_class.notify(3)
- expect(mail).to have_subject '3 projects failed their last repository check'
+ expect(mail).to have_subject 'GitLab Admin | 3 projects failed their last repository check'
end
end
end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index ac12ab6c757..305f8bc88cc 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: abuse_reports
-#
-# id :integer not null, primary key
-# reporter_id :integer
-# user_id :integer
-# message :text
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 520cf1b75de..1ce22feed5c 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1,49 +1,3 @@
-# == Schema Information
-#
-# Table name: application_settings
-#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string
-# require_two_factor_authentication :boolean default(FALSE)
-# two_factor_grace_period :integer default(48)
-# metrics_enabled :boolean default(FALSE)
-# metrics_host :string default("localhost")
-# metrics_username :string
-# metrics_password :string
-# metrics_pool_size :integer default(16)
-# metrics_timeout :integer default(10)
-# metrics_method_call_threshold :integer default(10)
-# recaptcha_enabled :boolean default(FALSE)
-# recaptcha_site_key :string
-# recaptcha_private_key :string
-# metrics_port :integer default(8089)
-# sentry_enabled :boolean default(FALSE)
-# sentry_dsn :string
-#
-
require 'spec_helper'
describe ApplicationSetting, models: true do
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index f6f84db57e6..6ad8bfef4f2 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: broadcast_messages
-#
-# id :integer not null, primary key
-# message :text not null
-# starts_at :datetime
-# ends_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# color :string(255)
-# font :string(255)
-#
-
require 'spec_helper'
describe BroadcastMessage, models: true do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index b7457808040..b5d356aa066 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -238,6 +238,22 @@ describe Ci::Build, models: true do
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end
+
+ context 'when job variables are defined' do
+ ##
+ # Job-level variables are defined in gitlab_ci.yml fixture
+ #
+ context 'when job variables are unique' do
+ let(:build) { create(:ci_build, name: 'staging') }
+
+ it 'includes job variables' do
+ expect(subject).to include(
+ { key: :KEY1, value: 'value1', public: true },
+ { key: :KEY2, value: 'value2', public: true }
+ )
+ end
+ end
+ end
end
end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index 412842337ba..dc071ad1c90 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_commits
-#
-# id :integer not null, primary key
-# project_id :integer
-# ref :string(255)
-# sha :string(255)
-# before_sha :string(255)
-# push_data :text
-# created_at :datetime
-# updated_at :datetime
-# tag :boolean default(FALSE)
-# yaml_errors :text
-# committed_at :datetime
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Commit, models: true do
@@ -27,6 +9,8 @@ describe Ci::Commit, models: true do
it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
+ it { is_expected.to validate_presence_of :status }
+ it { is_expected.to delegate_method(:stages).to(:statuses) }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
@@ -52,57 +36,9 @@ describe Ci::Commit, models: true do
it { expect(commit.sha).to start_with(subject) }
end
- describe :stage do
- subject { commit.stage }
-
- before do
- @second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
- @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
- end
-
- it 'returns first running stage' do
- is_expected.to eq('test')
- end
-
- context 'first build succeeded' do
- before do
- @first.success
- end
-
- it 'returns last running stage' do
- is_expected.to eq('deploy')
- end
- end
-
- context 'all builds succeeded' do
- before do
- @first.success
- @second.success
- end
-
- it 'returns nil' do
- is_expected.to be_nil
- end
- end
- end
-
describe :create_next_builds do
end
- describe :refs do
- subject { commit.refs }
-
- before do
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
- FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
- end
-
- it 'returns all refs' do
- is_expected.to contain_exactly('master', 'develop', nil)
- end
- end
-
describe :retried do
subject { commit.retried }
@@ -117,10 +53,10 @@ describe Ci::Commit, models: true do
end
describe :create_builds do
- let!(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false }
def create_builds(trigger_request = nil)
- commit.create_builds('master', false, nil, trigger_request)
+ commit.create_builds(nil, trigger_request)
end
def create_next_builds
@@ -143,67 +79,6 @@ describe Ci::Commit, models: true do
expect(create_next_builds).to be_falsey
end
- context 'for different ref' do
- def create_develop_builds
- commit.create_builds('develop', false, nil, nil)
- end
-
- it 'creates builds' do
- expect(create_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(2)
-
- expect(create_develop_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(4)
- expect(commit.refs.size).to eq(2)
- expect(commit.builds.pluck(:name).uniq.size).to eq(2)
- end
- end
-
- context 'for build triggers' do
- let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
- let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger }
-
- it 'creates builds' do
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
- end
-
- it 'rebuilds commit' do
- expect(create_builds).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
-
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(4)
- end
-
- it 'creates next builds' do
- expect(create_builds(trigger_request)).to be_truthy
- expect(commit.builds.count(:all)).to eq(2)
- commit.builds.update_all(status: "success")
-
- expect(create_next_builds).to be_truthy
- expect(commit.builds.count(:all)).to eq(4)
- end
-
- context 'for [ci skip]' do
- before do
- allow(commit).to receive(:git_commit_message) { 'message [ci skip]' }
- end
-
- it 'rebuilds commit' do
- expect(commit.status).to eq('skipped')
- expect(create_builds).to be_truthy
-
- # since everything in Ci::Commit is cached we need to fetch a new object
- new_commit = Ci::Commit.find_by_id(commit.id)
- expect(new_commit.status).to eq('pending')
- end
- end
- end
-
-
context 'custom stage with first job allowed to fail' do
let(:yaml) do
{
@@ -265,93 +140,123 @@ describe Ci::Commit, models: true do
stub_ci_commit_yaml_file(YAML.dump(yaml))
end
- it 'properly creates builds' do
- expect(create_builds).to be_truthy
- expect(commit.builds.pluck(:name)).to contain_exactly('build')
- expect(commit.builds.pluck(:status)).to contain_exactly('pending')
- commit.builds.running_or_pending.each(&:success)
+ context 'when builds are successful' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
- expect(commit.status).to eq('success')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ commit.reload
+ expect(commit.status).to eq('success')
+ end
end
- it 'properly creates builds when test fails' do
- expect(create_builds).to be_truthy
- expect(commit.builds.pluck(:name)).to contain_exactly('build')
- expect(commit.builds.pluck(:status)).to contain_exactly('pending')
- commit.builds.running_or_pending.each(&:success)
+ context 'when test job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
- commit.builds.running_or_pending.each(&:drop)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
- expect(commit.status).to eq('failed')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ commit.reload
+ expect(commit.status).to eq('failed')
+ end
end
- it 'properly creates builds when test and test_failure fails' do
- expect(create_builds).to be_truthy
- expect(commit.builds.pluck(:name)).to contain_exactly('build')
- expect(commit.builds.pluck(:status)).to contain_exactly('pending')
- commit.builds.running_or_pending.each(&:success)
+ context 'when test and test_failure jobs fail' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ commit.reload
+ expect(commit.status).to eq('failed')
+ end
+ end
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
- commit.builds.running_or_pending.each(&:drop)
+ context 'when deploy job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
- commit.builds.running_or_pending.each(&:drop)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
- expect(commit.status).to eq('failed')
- end
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
- it 'properly creates builds when deploy fails' do
- expect(create_builds).to be_truthy
- expect(commit.builds.pluck(:name)).to contain_exactly('build')
- expect(commit.builds.pluck(:status)).to contain_exactly('pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ commit.reload
+ expect(commit.status).to eq('failed')
+ end
+ end
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ context 'when build is canceled in the second stage' do
+ it 'does not schedule builds after build has been canceled' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
- commit.builds.running_or_pending.each(&:drop)
+ expect(commit.builds.running_or_pending).to_not be_empty
- expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
- commit.builds.running_or_pending.each(&:success)
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:cancel)
- expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
- expect(commit.status).to eq('failed')
+ expect(commit.builds.running_or_pending).to be_empty
+ expect(commit.reload.status).to eq('canceled')
+ end
end
end
end
@@ -402,4 +307,98 @@ describe Ci::Commit, models: true do
expect(commit.coverage).to be_nil
end
end
+
+ describe '#retryable?' do
+ subject { commit.retryable? }
+
+ context 'no failed builds' do
+ before do
+ FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success'
+ end
+
+ it 'be not retryable' do
+ is_expected.to be_falsey
+ end
+ end
+
+ context 'with failed builds' do
+ before do
+ FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running'
+ FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed'
+ end
+
+ it 'be retryable' do
+ is_expected.to be_truthy
+ end
+ end
+ end
+
+ describe '#stages' do
+ let(:commit2) { FactoryGirl.create :ci_commit, project: project }
+ subject { CommitStatus.where(commit: [commit, commit2]).stages }
+
+ before do
+ FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1
+ FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0
+ end
+
+ it 'return all stages' do
+ is_expected.to eq(%w(build test))
+ end
+ end
+
+ describe '#update_state' do
+ it 'execute update_state after touching object' do
+ expect(commit).to receive(:update_state).and_return(true)
+ commit.touch
+ end
+
+ context 'dependent objects' do
+ let(:commit_status) { build :commit_status, commit: commit }
+
+ it 'execute update_state after saving dependent object' do
+ expect(commit).to receive(:update_state).and_return(true)
+ commit_status.save
+ end
+ end
+
+ context 'update state' do
+ let(:current) { Time.now.change(usec: 0) }
+ let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 }
+
+ before do
+ build
+ end
+
+ [:status, :started_at, :finished_at, :duration].each do |param|
+ it "update #{param}" do
+ expect(commit.send(param)).to eq(build.send(param))
+ end
+ end
+ end
+ end
+
+ describe '#branch?' do
+ subject { commit.branch? }
+
+ context 'is not a tag' do
+ before do
+ commit.tag = false
+ end
+
+ it 'return true when tag is set to false' do
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'is not a tag' do
+ before do
+ commit.tag = true
+ end
+
+ it 'return false when tag is set to true' do
+ is_expected.to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb
index 000a732db77..95fc160b238 100644
--- a/spec/models/ci/runner_project_spec.rb
+++ b/spec/models/ci/runner_project_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_runner_projects
-#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::RunnerProject, models: true do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 25e9e5eca48..eaa94228922 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_runners
-#
-# id :integer not null, primary key
-# token :string(255)
-# created_at :datetime
-# updated_at :datetime
-# description :string(255)
-# contacted_at :datetime
-# active :boolean default(TRUE), not null
-# is_shared :boolean default(FALSE)
-# name :string(255)
-# version :string(255)
-# revision :string(255)
-# platform :string(255)
-# architecture :string(255)
-#
-
require 'spec_helper'
describe Ci::Runner, models: true do
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 159be939300..474b0b1621d 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_triggers
-#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Trigger, models: true do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 71e84091cb7..c712d211b0f 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,17 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_variables
-#
-# id :integer not null, primary key
-# project_id :integer
-# key :string(255)
-# value :text
-# encrypted_value :text
-# encrypted_value_salt :string(255)
-# encrypted_value_iv :string(255)
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe Ci::Variable, models: true do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 0e9111c8029..ad47e338a33 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -163,4 +163,12 @@ eos
it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
end
end
+
+ describe '#ci_commits' do
+ # TODO: kamil
+ end
+
+ describe '#status' do
+ # TODO: kamil
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 82c68ff6cb1..434e58cfd06 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,37 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe CommitStatus, models: true do
@@ -163,37 +129,73 @@ describe CommitStatus, models: true do
end
it 'return unique statuses' do
- is_expected.to eq([@commit2, @commit3, @commit4, @commit5])
+ is_expected.to eq([@commit4, @commit5])
end
end
- describe :for_ref do
- subject { CommitStatus.for_ref('bb').order(:id) }
+ describe :running_or_pending do
+ subject { CommitStatus.running_or_pending.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
+ @commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
+ @commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
end
- it 'return statuses with equal and nil ref set' do
- is_expected.to eq([@commit1])
+ it 'return statuses that are running or pending' do
+ is_expected.to eq([@commit1, @commit2])
end
end
- describe :running_or_pending do
- subject { CommitStatus.running_or_pending.order(:id) }
+ describe '#before_sha' do
+ subject { commit_status.before_sha }
+
+ context 'when no before_sha is set for ci::commit' do
+ before { commit.before_sha = nil }
+
+ it 'return blank sha' do
+ is_expected.to eq(Gitlab::Git::BLANK_SHA)
+ end
+ end
+
+ context 'for before_sha set for ci::commit' do
+ let(:value) { '1234' }
+ before { commit.before_sha = value }
+ it 'return the set value' do
+ is_expected.to eq(value)
+ end
+ end
+ end
+
+ describe '#stages' do
before do
- @commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
- @commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
- @commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
- @commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
- @commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running'
+ FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success'
end
- it 'return statuses that are running or pending' do
- is_expected.to eq([@commit1, @commit2])
+ context 'stages list' do
+ subject { CommitStatus.where(commit: commit).stages }
+
+ it 'return ordered list of stages' do
+ is_expected.to eq(%w(build test deploy))
+ end
+ end
+
+ context 'stages with statuses' do
+ subject { CommitStatus.where(commit: commit).stages_status }
+
+ it 'return list of stages with statuses' do
+ is_expected.to eq({
+ 'build' => 'failed',
+ 'test' => 'success',
+ 'deploy' => 'running'
+ })
+ end
end
end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 86ad9de883f..568bf4c9324 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -202,4 +202,47 @@ describe Issue, "Issuable" do
to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
end
end
+
+ # TODO ZJ
+ describe "votes" do
+ before do
+ create!(:award_emoji, :upvote, awardable: issue)
+ create!(:award_emoji, :downvote, awardable: issue)
+ end
+
+ it "returns correct values" do
+ expect(issue.upvotes).to eq(1)
+ expect(issue.downvotes).to eq(1)
+ end
+ end
+
+ describe ".with_label" do
+ let(:project) { create(:project, :public) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:feature) { create(:label, project: project, title: 'feature') }
+ let(:enhancement) { create(:label, project: project, title: 'enhancement') }
+ let(:issue1) { create(:issue, title: "Bugfix1", project: project) }
+ let(:issue2) { create(:issue, title: "Bugfix2", project: project) }
+ let(:issue3) { create(:issue, title: "Feature1", project: project) }
+
+ before(:each) do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << bug
+ issue2.labels << enhancement
+ issue3.labels << feature
+ end
+
+ it 'finds the correct issue containing just enhancement label' do
+ expect(Issue.with_label(enhancement.title)).to match_array([issue2])
+ end
+
+ it 'finds the correct issues containing the same label' do
+ expect(Issue.with_label(bug.title)).to match_array([issue1, issue2])
+ end
+
+ it 'finds the correct issues containing only both labels' do
+ expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2])
+ end
+ end
end
diff --git a/spec/lib/ci/status_spec.rb b/spec/models/concerns/statuseable_spec.rb
index 47f3df6e3ce..8e0a2a2cbde 100644
--- a/spec/lib/ci/status_spec.rb
+++ b/spec/models/concerns/statuseable_spec.rb
@@ -1,8 +1,17 @@
require 'spec_helper'
-describe Ci::Status do
- describe '.get_status' do
- subject { described_class.get_status(statuses) }
+describe Statuseable do
+ before do
+ @object = Object.new
+ @object.extend(Statuseable::ClassMethods)
+ end
+
+ describe '.status' do
+ before do
+ allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses))
+ end
+
+ subject { @object.status }
shared_examples 'build status summary' do
context 'all successful' do
@@ -52,9 +61,35 @@ describe Ci::Status do
let(:statuses) do
[create(type, status: :success), create(type, status: :canceled)]
end
+
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'one failed and one canceled' do
+ let(:statuses) do
+ [create(type, status: :failed), create(type, status: :canceled)]
+ end
+
it { is_expected.to eq 'failed' }
end
+ context 'one failed but allowed to fail and one canceled' do
+ let(:statuses) do
+ [create(type, status: :failed, allow_failure: true),
+ create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'one running one canceled' do
+ let(:statuses) do
+ [create(type, status: :running), create(type, status: :canceled)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
context 'all canceled' do
let(:statuses) do
[create(type, status: :canceled), create(type, status: :canceled)]
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 64ba778afea..6a90598a629 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe DeployKey, models: true do
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 8aedbfb8636..8a1e337c1a3 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: deploy_keys_projects
-#
-# id :integer not null, primary key
-# deploy_key_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe DeployKeysProject, models: true do
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index a20a6149649..5d0bd31db5a 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: emails
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# email :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe Email, models: true do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 89909c2bcd7..b0e76fec693 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: events
-#
-# id :integer not null, primary key
-# target_type :string(255)
-# target_id :integer
-# title :string(255)
-# data :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# action :integer
-# author_id :integer
-#
-
require 'spec_helper'
describe Event, models: true do
@@ -30,32 +14,29 @@ describe Event, models: true do
it { is_expected.to respond_to(:commits) }
end
+ describe 'Callbacks' do
+ describe 'after_create :reset_project_activity' do
+ let(:project) { create(:project) }
+
+ context "project's last activity was less than 5 minutes ago" do
+ it 'does not update project.last_activity_at if it has been touched less than 5 minutes ago' do
+ create_event(project, project.owner)
+ project.update_column(:last_activity_at, 5.minutes.ago)
+ project_last_activity_at = project.last_activity_at
+
+ create_event(project, project.owner)
+
+ expect(project.last_activity_at).to eq(project_last_activity_at)
+ end
+ end
+ end
+ end
+
describe "Push event" do
before do
project = create(:project)
@user = project.owner
-
- data = {
- before: Gitlab::Git::BLANK_SHA,
- after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
- ref: "refs/heads/master",
- user_id: @user.id,
- user_name: @user.name,
- repository: {
- name: project.name,
- url: "localhost/rubinius",
- description: "",
- homepage: "localhost/rubinius",
- private: true
- }
- }
-
- @event = Event.create(
- project: project,
- action: Event::PUSHED,
- data: data,
- author_id: @user.id
- )
+ @event = create_event(project, @user)
end
it { expect(@event.push?).to be_truthy }
@@ -143,4 +124,28 @@ describe Event, models: true do
it { is_expected.to eq([event2]) }
end
end
+
+ def create_event(project, user, attrs = {})
+ data = {
+ before: Gitlab::Git::BLANK_SHA,
+ after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
+ ref: "refs/heads/master",
+ user_id: user.id,
+ user_name: user.name,
+ repository: {
+ name: project.name,
+ url: "localhost/rubinius",
+ description: "",
+ homepage: "localhost/rubinius",
+ private: true
+ }
+ }
+
+ Event.create({
+ project: project,
+ action: Event::PUSHED,
+ data: data,
+ author_id: user.id
+ }.merge(attrs))
+ end
end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 9b144dd1ecc..4fc3b065592 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do
expect(issue.title).to eq "External Issue #{issue}"
end
end
+
+ describe '#reference_link_text' do
+ context 'if issue id has a prefix' do
+ it 'returns the issue ID' do
+ expect(issue.reference_link_text).to eq 'EXT-1234'
+ end
+ end
+
+ context 'if issue id is a number' do
+ let(:issue) { described_class.new('1234', project) }
+ it 'returns the issue ID prefixed by #' do
+ expect(issue.reference_link_text).to eq '#1234'
+ end
+ end
+ end
end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index d90fbfe1ea5..3b817608ce0 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -1,14 +1,3 @@
-# == Schema Information
-#
-# Table name: forked_project_links
-#
-# id :integer not null, primary key
-# forked_to_project_id :integer not null
-# forked_from_project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 5b0883d8702..0caf5869c24 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,37 +1,3 @@
-# == Schema Information
-#
-# Table name: ci_builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# coverage :float
-# commit_id :integer
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-# stage_idx :integer
-# tag :boolean
-# ref :string(255)
-# user_id :integer
-# type :string(255)
-# target_url :string(255)
-# description :string(255)
-# artifacts_file :text
-# gl_project_id :integer
-#
-
require 'spec_helper'
describe GenericCommitStatus, models: true do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 7bfca1e72c3..6fa16be7f04 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
-#
-
require 'spec_helper'
describe Group, models: true do
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 04bc2dcfb16..37a27d73aab 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -43,51 +43,65 @@ describe WebHook, models: true do
end
describe "execute" do
+ let(:project) { create(:project) }
+ let(:project_hook) { create(:project_hook) }
+
before(:each) do
- @project_hook = create(:project_hook)
- @project = create(:project)
- @project.hooks << [@project_hook]
+ project.hooks << [project_hook]
@data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
- WebMock.stub_request(:post, @project_hook.url)
+ WebMock.stub_request(:post, project_hook.url)
+ end
+
+ context 'when token is defined' do
+ let(:project_hook) { create(:project_hook, :token) }
+
+ it 'POSTs to the webhook URL' do
+ project_hook.execute(@data, 'push_hooks')
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: { 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => 'Push Hook',
+ 'X-Gitlab-Token' => project_hook.token }
+ ).once
+ end
end
it "POSTs to the webhook URL" do
- @project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, @project_hook.url).with(
- headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
+ 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
- @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' }
+ 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, 'push_hooks') }.to raise_error(RuntimeError)
+ expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
end
it "handles SSL exceptions" do
expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
it "handles 200 status code" do
- WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success")
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
it "handles 2xx status codes" do
- WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success")
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
- expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
end
end
end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 5afe042e154..1b987588f59 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: identities
-#
-# id :integer not null, primary key
-# extern_uid :string(255)
-# provider :string(255)
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
RSpec.describe Identity, models: true do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index fac516f9568..8ab00c70f9d 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1,23 +1,3 @@
-# == Schema Information
-#
-# Table name: issues
-#
-# id :integer not null, primary key
-# title :string(255)
-# assignee_id :integer
-# author_id :integer
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# position :integer default(0)
-# branch_name :string(255)
-# description :text
-# milestone_id :integer
-# state :string(255)
-# iid :integer
-# updated_by_id :integer
-#
-
require 'spec_helper'
describe Issue, models: true do
@@ -191,18 +171,36 @@ describe Issue, models: true do
end
describe '#related_branches' do
- it 'selects the right branches' do
+ let(:user) { build(:admin) }
+
+ before do
allow(subject.project.repository).to receive(:branch_names).
- and_return(['mpempe', "#{subject.iid}mepmep", subject.to_branch_name])
+ and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"])
+
+ # Without this stub, the `create(:merge_request)` above fails because it can't find
+ # the source branch. This seems like a reasonable compromise, in comparison with
+ # setting up a full repo here.
+ allow_any_instance_of(MergeRequest).to receive(:create_merge_request_diff)
+ end
+
+ it "selects the right branches when there are no referenced merge requests" do
+ expect(subject.related_branches(user)).to eq([subject.to_branch_name, "#{subject.iid}-branch"])
+ end
- expect(subject.related_branches).to eq([subject.to_branch_name])
+ it "selects the right branches when there is a referenced merge request" do
+ merge_request = create(:merge_request, { description: "Closes ##{subject.iid}",
+ source_project: subject.project,
+ source_branch: "#{subject.iid}-branch" })
+ merge_request.create_cross_references!(user)
+ expect(subject.referenced_merge_requests).to_not be_empty
+ expect(subject.related_branches(user)).to eq([subject.to_branch_name])
end
it 'excludes stable branches from the related branches' do
allow(subject.project.repository).to receive(:branch_names).
and_return(["#{subject.iid}-0-stable"])
- expect(subject.related_branches).to eq []
+ expect(subject.related_branches(user)).to eq []
end
end
@@ -217,11 +215,20 @@ describe Issue, models: true do
let(:subject) { create :issue }
end
- describe '#to_branch_name' do
- let(:issue) { create(:issue, title: 'a' * 30) }
+ describe "#to_branch_name" do
+ let(:issue) { create(:issue, title: 'testing-issue') }
it 'starts with the issue iid' do
- expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/
+ expect(issue.to_branch_name).to match /\A#{issue.iid}-[A-Za-z\-]+\z/
+ end
+
+ it "contains the issue title if not confidential" do
+ expect(issue.to_branch_name).to match /testing-issue\z/
+ end
+
+ it "does not contain the issue title if confidential" do
+ issue = create(:issue, title: 'testing-issue', confidential: true)
+ expect(issue.to_branch_name).to match /confidential-issue\z/
end
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index c962b83644a..26fbedbef2f 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: keys
-#
-# id :integer not null, primary key
-# user_id :integer
-# created_at :datetime
-# updated_at :datetime
-# key :text
-# title :string(255)
-# type :string(255)
-# fingerprint :string(255)
-# public :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Key, models: true do
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index dc7510b1de3..5e6f8ca1528 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: label_links
-#
-# id :integer not null, primary key
-# label_id :integer
-# target_id :integer
-# target_type :string(255)
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'spec_helper'
describe LabelLink, models: true do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 0614ca1e7c9..dad2628651b 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,16 +1,3 @@
-# == Schema Information
-#
-# Table name: labels
-#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
-#
-
require 'spec_helper'
describe Label, models: true do
@@ -55,6 +42,14 @@ describe Label, models: true do
end
end
+ describe "#title" do
+ let(:label) { create(:label, title: "<b>test</b>") }
+
+ it "sanitizes title" do
+ expect(label.title).to eq("test")
+ end
+ end
+
describe '#to_reference' do
context 'using id' do
it 'returns a String reference to the object' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 2d8f1cc1ad3..6e51730eecd 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1,22 +1,3 @@
-# == Schema Information
-#
-# Table name: members
-#
-# id :integer not null, primary key
-# access_level :integer not null
-# source_id :integer not null
-# source_type :string(255) not null
-# user_id :integer
-# notification_level :integer not null
-# type :string(255)
-# created_at :datetime
-# updated_at :datetime
-# created_by_id :integer
-# invite_email :string(255)
-# invite_token :string(255)
-# invite_accepted_at :datetime
-#
-
require 'spec_helper'
describe Member, models: true do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 6f5d912fe5d..c8578749b21 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,32 +1,3 @@
-# == Schema Information
-#
-# Table name: merge_requests
-#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
-# merge_params :text
-# merge_when_build_succeeds :boolean default(FALSE), not null
-# merge_user_id :integer
-# merge_commit_sha :string
-#
-
require 'spec_helper'
describe MergeRequest, models: true do
@@ -404,12 +375,12 @@ describe MergeRequest, models: true do
describe 'when the source project exists' do
it 'returns the latest commit' do
commit = double(:commit, id: '123abc')
- ci_commit = double(:ci_commit)
+ ci_commit = double(:ci_commit, ref: 'master')
allow(subject).to receive(:last_commit).and_return(commit)
expect(subject.source_project).to receive(:ci_commit).
- with('123abc').
+ with('123abc', 'master').
and_return(ci_commit)
expect(subject.ci_commit).to eq(ci_commit)
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 72a4ea70228..247a9fa9910 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: milestones
-#
-# id :integer not null, primary key
-# title :string(255) not null
-# project_id :integer not null
-# description :text
-# due_date :date
-# created_at :datetime
-# updated_at :datetime
-# state :string(255)
-# iid :integer
-#
-
require 'spec_helper'
describe Milestone, models: true do
@@ -34,6 +19,14 @@ describe Milestone, models: true do
let(:issue) { create(:issue) }
let(:user) { create(:user) }
+ describe "#title" do
+ let(:milestone) { create(:milestone, title: "<b>test</b>") }
+
+ it "sanitizes title" do
+ expect(milestone.title).to eq("test")
+ end
+ end
+
describe "unique milestone title per project" do
it "shouldn't accept the same title in a project twice" do
new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 3c3a580942a..4074f966299 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1,18 +1,3 @@
-# == Schema Information
-#
-# Table name: namespaces
-#
-# id :integer not null, primary key
-# name :string(255) not null
-# path :string(255) not null
-# owner_id :integer
-# created_at :datetime
-# updated_at :datetime
-# type :string(255)
-# description :string(255) default(""), not null
-# avatar :string(255)
-#
-
require 'spec_helper'
describe Namespace, models: true do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bb591e9cb53..b479f1c2f1a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: notes
-#
-# id :integer not null, primary key
-# note :text
-# noteable_type :string(255)
-# author_id :integer
-# created_at :datetime
-# updated_at :datetime
-# project_id :integer
-# attachment :string(255)
-# line_code :string(255)
-# commit_id :string(255)
-# noteable_id :integer
-# system :boolean default(FALSE), not null
-# st_diff :text
-# updated_by_id :integer
-# is_award :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Note, models: true do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 31b2c90122d..e771f35811e 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -27,86 +27,51 @@ describe BambooService, models: true do
end
describe 'Validations' do
- describe '#bamboo_url' do
- it 'does not validate the presence of bamboo_url if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
- end
-
- it 'validates the presence of bamboo_url if service is active' do
- bamboo_service = service
- bamboo_service.active = true
-
- expect(bamboo_service).to validate_presence_of(:bamboo_url)
- end
- end
+ subject { service }
- describe '#build_key' do
- it 'does not validate the presence of build_key if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(bamboo_service).not_to validate_presence_of(:build_key)
- end
+ it { is_expected.to validate_presence_of(:build_key) }
+ it { is_expected.to validate_presence_of(:bamboo_url) }
+ it_behaves_like 'issue tracker service URL attribute', :bamboo_url
- it 'validates the presence of build_key if service is active' do
- bamboo_service = service
- bamboo_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(bamboo_service).to validate_presence_of(:build_key)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(bamboo_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(bamboo_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(bamboo_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- bamboo_service = service
- bamboo_service.active = false
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = nil
-
- expect(bamboo_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- bamboo_service = service
- bamboo_service.active = true
- bamboo_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(bamboo_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_key) }
+ it { is_expected.not_to validate_presence_of(:bamboo_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 88cd624877a..60364df2015 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -26,6 +26,23 @@ describe BuildkiteService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:token) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe 'commits methods' do
before do
@project = Project.new
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
index 7c23c2efccd..236df8f047d 100644
--- a/spec/models/project_services/builds_email_service_spec.rb
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -1,76 +1,71 @@
require 'spec_helper'
describe BuildsEmailService do
- let(:build) { create(:ci_build) }
- let(:data) { Gitlab::BuildDataBuilder.build(build) }
- let!(:project) { create(:project, :public, ci_id: 1) }
- let(:service) { described_class.new(project: project, active: true) }
+ let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) }
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+
+ context 'when pusher is added' do
+ before { subject.add_pusher = true }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
describe '#execute' do
it 'sends email' do
- service.recipients = 'test@gitlab.com'
+ subject.recipients = 'test@gitlab.com'
data[:build_status] = 'failed'
+
expect(BuildEmailWorker).to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with succeeded build and notify_only_broken_builds on' do
- expect(service).to receive(:notify_only_broken_builds).and_return(true)
+ expect(subject).to receive(:notify_only_broken_builds).and_return(true)
data[:build_status] = 'success'
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with failed build and build_allow_failure is true' do
data[:build_status] = 'failed'
data[:build_allow_failure] = true
+
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
+
+ subject.execute(data)
end
it 'does not send email with unknown build status' do
data[:build_status] = 'foo'
- expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- it 'does not send email when recipients list is empty' do
- service.recipients = ' ,, '
- data[:build_status] = 'failed'
expect(BuildEmailWorker).not_to receive(:perform_async)
- service.execute(data)
- end
- end
-
- describe 'validations' do
-
- context 'when pusher is not added' do
- before { service.add_pusher = false }
-
- it 'does not allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be false
- end
-
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
- context 'when pusher is added' do
- before { service.add_pusher = true }
+ it 'does not send email when recipients list is empty' do
+ subject.recipients = ' ,, '
+ data[:build_status] = 'failed'
- it 'does allow empty recipient input' do
- service.recipients = ''
- expect(service.valid?).to be true
- end
+ expect(BuildEmailWorker).not_to receive(:perform_async)
- it 'does allow non-empty recipient input' do
- service.recipients = 'test@example.com'
- expect(service.valid?).to be true
- end
+ subject.execute(data)
end
end
end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
new file mode 100644
index 00000000000..3e6da42803b
--- /dev/null
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CampfireService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
new file mode 100644
index 00000000000..ff976f8ec59
--- /dev/null
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CustomIssueTrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index a2cf68a9e38..3a8e67438fc 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,25 +28,18 @@ describe DroneCiService, models: true do
describe 'validations' do
context 'active' do
- before { allow(subject).to receive(:activated?).and_return(true) }
+ before { subject.active = true }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
- it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
- it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
- it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+ it_behaves_like 'issue tracker service URL attribute', :drone_url
end
context 'inactive' do
- before { allow(subject).to receive(:activated?).and_return(false) }
+ before { subject.active = false }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:drone_url) }
- it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
- it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
- it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
end
end
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
new file mode 100644
index 00000000000..e6f78898c82
--- /dev/null
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe EmailsOnPushService do
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
+ end
+ end
+end
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index d37978720bf..5fe5ea7d2df 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -28,13 +28,18 @@ describe ExternalWikiService, models: true do
it { should have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:external_wiki_url) }
+ it_behaves_like 'issue tracker service URL attribute', :external_wiki_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
- it { should validate_presence_of :external_wiki_url }
+ it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index ff7fbcaa004..b7e627e6518 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -26,6 +26,20 @@ describe FlowdockService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index ecb3ccb1673..a08f1ac229f 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -26,6 +26,22 @@ describe GemnasiumService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:api_key) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 3518dbd1728..7a1f106d6e3 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -26,6 +26,20 @@ describe GitlabIssueTrackerService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ subject { described_class.new(project: create(:project), active: true) }
+
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ end
+
+ context 'when service is inactive' do
+ subject { described_class.new(project: create(:project), active: false) }
+
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ end
+ end
describe 'project and issue urls' do
let(:project) { create(:project) }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 91dd92b7c67..6fb5cad5011 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -26,6 +26,20 @@ describe HipchatService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
describe "Execute" do
let(:hipchat) { HipchatService.new }
let(:user) { create(:user, username: 'username') }
@@ -152,7 +166,7 @@ describe HipchatService, models: true do
obj_attr = merge_sample_data[:object_attributes]
expect(message).to eq("#{user.name} opened " \
- "<a href=\"#{obj_attr[:url]}\">merge request ##{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{obj_attr["iid"]}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>Awesome merge request</b>" \
"<pre>please fix</pre>")
@@ -202,7 +216,7 @@ describe HipchatService, models: true do
title = data[:merge_request]['title']
expect(message).to eq("#{user.name} commented on " \
- "<a href=\"#{obj_attr[:url]}\">merge request ##{merge_id}</a> in " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
"<a href=\"#{project.web_url}\">#{project_name}</a>: " \
"<b>#{title}</b>" \
"<pre>merge request note</pre>")
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b783b1a576e..4ee022a5171 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -29,14 +29,16 @@ describe IrkerService, models: true do
end
describe 'Validations' do
- before do
- subject.active = true
- subject.properties['recipients'] = _recipients
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:recipients) }
end
- context 'active' do
- let(:_recipients) { nil }
- it { should validate_presence_of :recipients }
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:recipients) }
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f8193170ae..5309cfb99ff 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -26,6 +26,30 @@ describe JiraService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :api_url
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -72,7 +96,7 @@ describe JiraService, models: true do
context "when a password was previously set" do
before do
- @jira_service = JiraService.create(
+ @jira_service = JiraService.create!(
project: create(:project),
properties: {
api_url: 'http://jira.example.com/rest/api/2',
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
new file mode 100644
index 00000000000..f37edd4d970
--- /dev/null
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe PivotaltrackerService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 96039f9491b..555d9757b47 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -27,14 +27,20 @@ describe PushoverService, models: true do
end
describe 'Validations' do
- context 'active' do
- before do
- subject.active = true
- end
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :api_key }
- it { is_expected.to validate_presence_of :user_key }
- it { is_expected.to validate_presence_of :priority }
+ it { is_expected.to validate_presence_of(:api_key) }
+ it { is_expected.to validate_presence_of(:user_key) }
+ it { is_expected.to validate_presence_of(:priority) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_key) }
+ it { is_expected.not_to validate_presence_of(:user_key) }
+ it { is_expected.not_to validate_presence_of(:priority) }
end
end
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
new file mode 100644
index 00000000000..7d14f6e8280
--- /dev/null
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe RedmineService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb
index dae8bd90922..224c7ceabe8 100644
--- a/spec/models/project_services/slack_service/merge_message_spec.rb
+++ b/spec/models/project_services/slack_service/merge_message_spec.rb
@@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do
context 'open' do
it 'returns a message regarding opening of merge requests' do
expect(subject.pretext).to eq(
- 'Test User opened <somewhere.com/merge_requests/100|merge request #100> '\
+ 'Test User opened <somewhere.com/merge_requests/100|merge request !100> '\
'in <somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty
end
@@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do
end
it 'returns a message regarding closing of merge requests' do
expect(subject.pretext).to eq(
- 'Test User closed <somewhere.com/merge_requests/100|merge request #100> '\
+ 'Test User closed <somewhere.com/merge_requests/100|merge request !100> '\
'in <somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty
end
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
index 06006b9a4f5..d37590cab75 100644
--- a/spec/models/project_services/slack_service/note_message_spec.rb
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on a merge request' do
message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq("Test User commented on " \
- "<url|merge request #30> in <somewhere.com|project_name>: " \
+ "<url|merge request !30> in <somewhere.com|project_name>: " \
"*merge request title*")
expected_attachments = [
{
diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb
new file mode 100644
index 00000000000..6ecab645b49
--- /dev/null
+++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe SlackService::WikiPageMessage, models: true do
+ subject { described_class.new(args) }
+
+ let(:args) do
+ {
+ user: {
+ name: 'Test User',
+ username: 'Test User'
+ },
+ project_name: 'project_name',
+ project_url: 'somewhere.com',
+ object_attributes: {
+ title: 'Wiki page title',
+ url: 'url',
+ content: 'Wiki page description'
+ }
+ }
+ end
+
+ describe '#pretext' do
+ context 'when :action == "create"' do
+ before { args[:object_attributes][:action] = 'create' }
+
+ it 'returns a message that a new wiki page was created' do
+ expect(subject.pretext).to eq(
+ 'Test User created <url|wiki page> in <somewhere.com|project_name>: '\
+ '*Wiki page title*')
+ end
+ end
+
+ context 'when :action == "update"' do
+ before { args[:object_attributes][:action] = 'update' }
+
+ it 'returns a message that a wiki page was updated' do
+ expect(subject.pretext).to eq(
+ 'Test User edited <url|wiki page> in <somewhere.com|project_name>: '\
+ '*Wiki page title*')
+ end
+ end
+ end
+
+ describe '#attachments' do
+ let(:color) { '#345' }
+
+ context 'when :action == "create"' do
+ before { args[:object_attributes][:action] = 'create' }
+
+
+ it 'it returns the attachment for a new wiki page' do
+ expect(subject.attachments).to eq([
+ {
+ text: "Wiki page description",
+ color: color,
+ }
+ ])
+ end
+ end
+
+ context 'when :action == "update"' do
+ before { args[:object_attributes][:action] = 'update' }
+
+ it 'it returns the attachment for an updated wiki page' do
+ expect(subject.attachments).to eq([
+ {
+ text: "Wiki page description",
+ color: color,
+ }
+ ])
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index a9e0afad90f..a97b7560137 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -26,13 +26,18 @@ describe SlackService, models: true do
it { is_expected.to have_one :service_hook }
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
- it { is_expected.to validate_presence_of :webhook }
+ it { is_expected.to validate_presence_of(:webhook) }
+ it_behaves_like 'issue tracker service URL attribute', :webhook
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:webhook) }
end
end
@@ -75,6 +80,17 @@ describe SlackService, models: true do
@merge_request = merge_service.execute
@merge_sample_data = merge_service.hook_data(@merge_request,
'open')
+
+ opts = {
+ title: "Awesome wiki_page",
+ content: "Some text describing some thing or another",
+ format: "md",
+ message: "user created page: Awesome wiki_page"
+ }
+
+ wiki_page_service = WikiPages::CreateService.new(project, user, opts)
+ @wiki_page = wiki_page_service.execute
+ @wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
end
it "should call Slack API for push events" do
@@ -95,6 +111,12 @@ describe SlackService, models: true do
expect(WebMock).to have_requested(:post, webhook_url).once
end
+ it "should call Slack API for wiki page events" do
+ slack.execute(@wiki_page_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
it 'should use the username as an option for slack when configured' do
allow(slack).to receive(:username).and_return(username)
expect(Slack::Notifier).to receive(:new).
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index bc7423cee69..ad24b895170 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -27,86 +27,51 @@ describe TeamcityService, models: true do
end
describe 'Validations' do
- describe '#teamcity_url' do
- it 'does not validate the presence of teamcity_url if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
- end
-
- it 'validates the presence of teamcity_url if service is active' do
- teamcity_service = service
- teamcity_service.active = true
-
- expect(teamcity_service).to validate_presence_of(:teamcity_url)
- end
- end
+ subject { service }
- describe '#build_type' do
- it 'does not validate the presence of build_type if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ context 'when service is active' do
+ before { subject.active = true }
- expect(teamcity_service).not_to validate_presence_of(:build_type)
- end
+ it { is_expected.to validate_presence_of(:build_type) }
+ it { is_expected.to validate_presence_of(:teamcity_url) }
+ it_behaves_like 'issue tracker service URL attribute', :teamcity_url
- it 'validates the presence of build_type if service is active' do
- teamcity_service = service
- teamcity_service.active = true
+ describe '#username' do
+ it 'does not validate the presence of username if password is nil' do
+ subject.password = nil
- expect(teamcity_service).to validate_presence_of(:build_type)
- end
- end
+ expect(subject).not_to validate_presence_of(:username)
+ end
- describe '#username' do
- it 'does not validate the presence of username if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
+ it 'validates the presence of username if password is present' do
+ subject.password = 'secret'
- expect(teamcity_service).not_to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:username)
+ end
end
- it 'does not validate the presence of username if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = nil
+ describe '#password' do
+ it 'does not validate the presence of password if username is nil' do
+ subject.username = nil
- expect(teamcity_service).not_to validate_presence_of(:username)
- end
+ expect(subject).not_to validate_presence_of(:password)
+ end
- it 'validates the presence of username if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.password = 'secret'
+ it 'validates the presence of password if username is present' do
+ subject.username = 'john'
- expect(teamcity_service).to validate_presence_of(:username)
+ expect(subject).to validate_presence_of(:password)
+ end
end
end
- describe '#password' do
- it 'does not validate the presence of password if service is not active' do
- teamcity_service = service
- teamcity_service.active = false
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'does not validate the presence of password if username is nil' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = nil
-
- expect(teamcity_service).not_to validate_presence_of(:password)
- end
-
- it 'validates the presence of password if service is active and username is present' do
- teamcity_service = service
- teamcity_service.active = true
- teamcity_service.username = 'john'
+ context 'when service is inactive' do
+ before { subject.active = false }
- expect(teamcity_service).to validate_presence_of(:password)
- end
+ it { is_expected.not_to validate_presence_of(:build_type) }
+ it { is_expected.not_to validate_presence_of(:teamcity_url) }
+ it { is_expected.not_to validate_presence_of(:username) }
+ it { is_expected.not_to validate_presence_of(:password) }
end
end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index e0feb606f78..d9d7c0b0aaa 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
require 'spec_helper'
describe ProjectSnippet, models: true do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f29c389e094..f6e5b132643 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,43 +1,3 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer not null, primary key
-# name :string(255)
-# path :string(255)
-# description :text
-# created_at :datetime
-# updated_at :datetime
-# creator_id :integer
-# issues_enabled :boolean default(TRUE), not null
-# wall_enabled :boolean default(TRUE), not null
-# merge_requests_enabled :boolean default(TRUE), not null
-# wiki_enabled :boolean default(TRUE), not null
-# namespace_id :integer
-# issues_tracker :string(255) default("gitlab"), not null
-# issues_tracker_id :string(255)
-# snippets_enabled :boolean default(TRUE), not null
-# last_activity_at :datetime
-# import_url :string(255)
-# visibility_level :integer default(0), not null
-# archived :boolean default(FALSE), not null
-# avatar :string(255)
-# import_status :string(255)
-# repository_size :float default(0.0)
-# star_count :integer default(0), not null
-# import_type :string(255)
-# import_source :string(255)
-# commit_count :integer default(0)
-# import_error :text
-# ci_id :integer
-# builds_enabled :boolean default(TRUE), not null
-# shared_runners_enabled :boolean default(TRUE), not null
-# runners_token :string
-# build_coverage_regex :string
-# build_allow_git_fetch :boolean default(TRUE), not null
-# build_timeout :integer default(3600), not null
-#
-
require 'spec_helper'
describe Project, models: true do
@@ -441,9 +401,22 @@ describe Project, models: true do
describe :ci_commit do
let(:project) { create :project }
- let(:commit) { create :ci_commit, project: project }
+ let(:commit) { create :ci_commit, project: project, ref: 'master' }
+
+ subject { project.ci_commit(commit.sha, 'master') }
+
+ it { is_expected.to eq(commit) }
- it { expect(project.ci_commit(commit.sha)).to eq(commit) }
+ context 'return latest' do
+ let(:commit2) { create :ci_commit, project: project, ref: 'master' }
+
+ before do
+ commit
+ commit2
+ end
+
+ it { is_expected.to eq(commit2) }
+ end
end
describe :builds_enabled do
@@ -706,11 +679,8 @@ describe Project, models: true do
with('foo.wiki', project).
and_return(wiki)
- expect(repo).to receive(:expire_cache)
- expect(repo).to receive(:expire_emptiness_caches)
-
- expect(wiki).to receive(:expire_cache)
- expect(wiki).to receive(:expire_emptiness_caches)
+ expect(repo).to receive(:before_delete)
+ expect(wiki).to receive(:before_delete)
project.expire_caches_before_rename('foo')
end
@@ -788,4 +758,18 @@ describe Project, models: true do
end
end
end
+
+ describe '#protected_branch?' do
+ let(:project) { create(:empty_project) }
+
+ it 'returns true when a branch is a protected branch' do
+ project.protected_branches.create!(name: 'foo')
+
+ expect(project.protected_branch?('foo')).to eq(true)
+ end
+
+ it 'returns false when a branch is not a protected branch' do
+ expect(project.protected_branch?('foo')).to eq(false)
+ end
+ end
end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index 7e956cf6779..b523834c6e9 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: protected_branches
-#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string(255) not null
-# created_at :datetime
-# updated_at :datetime
-# developers_can_push :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe ProtectedBranch, models: true do
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 72ecb442a36..527005b2b69 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,15 +1,3 @@
-# == Schema Information
-#
-# Table name: releases
-#
-# id :integer not null, primary key
-# tag :string(255)
-# description :text
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-#
-
require 'rails_helper'
RSpec.describe Release, type: :model do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c163001b7c1..34a13f9b5c9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -132,25 +132,92 @@ describe Repository, models: true do
it { expect(subject.basename).to eq('a/b/c') }
end
end
+ end
+
+ describe "#changelog" do
+ before do
+ repository.send(:cache).expire(:changelog)
+ end
+
+ it 'accepts changelog' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])
+
+ expect(repository.changelog.name).to eq('changelog')
+ end
+
+ it 'accepts news instead of changelog' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('news')])
+
+ expect(repository.changelog.name).to eq('news')
+ end
+ it 'accepts history instead of changelog' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('history')])
+
+ expect(repository.changelog.name).to eq('history')
+ end
+
+ it 'accepts changes instead of changelog' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changes')])
+
+ expect(repository.changelog.name).to eq('changes')
+ end
+
+ it 'is case-insensitive' do
+ expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('CHANGELOG')])
+
+ expect(repository.changelog.name).to eq('CHANGELOG')
+ end
end
- describe "#license" do
+ describe "#license_blob" do
before do
- repository.send(:cache).expire(:license)
+ repository.send(:cache).expire(:license_blob)
+ repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
end
- it 'test selection preference' do
- files = [TestBlob.new('file'), TestBlob.new('license'), TestBlob.new('copying')]
- expect(repository.tree).to receive(:blobs).and_return(files)
+ it 'looks in the root_ref only' do
+ repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown')
+ repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false)
+
+ expect(repository.license_blob).to be_nil
+ end
+
+ it 'detects license file with no recognizable open-source license content' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+
+ expect(repository.license_blob.name).to eq('LICENSE')
+ end
+
+ %w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
+ it "detects '#{filename}'" do
+ repository.commit_file(user, filename, Licensee::License.new('mit').content, "Add #{filename}", 'master', false)
+
+ expect(repository.license_blob.name).to eq(filename)
+ end
+ end
+ end
+
+ describe '#license_key' do
+ before do
+ repository.send(:cache).expire(:license_key)
+ repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
+ end
+
+ it 'returns nil when no license is detected' do
+ expect(repository.license_key).to be_nil
+ end
+
+ it 'detects license file with no recognizable open-source license content' do
+ repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
- expect(repository.license.name).to eq('license')
+ expect(repository.license_key).to be_nil
end
- it 'also accepts licence instead of license' do
- expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('licence')])
+ it 'returns the license key' do
+ repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
- expect(repository.license.name).to eq('licence')
+ expect(repository.license_key).to eq('mit')
end
end
@@ -494,7 +561,7 @@ describe Repository, models: true do
end
describe :skip_merged_commit do
- subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
+ subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
@@ -541,6 +608,41 @@ describe Repository, models: true do
end
end
+ describe '#cherry_pick' do
+ let(:conflict_commit) { repository.commit('c642fe9b8b9f28f9225d7ea953fe14e74748d53b') }
+ let(:pickable_commit) { repository.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:pickable_merge) { repository.commit('e56497bb5f03a90a51293fc6d516788730953899') }
+
+ context 'when there is a conflict' do
+ it 'should abort the operation' do
+ expect(repository.cherry_pick(user, conflict_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit was already cherry-picked' do
+ it 'should abort the operation' do
+ repository.cherry_pick(user, pickable_commit, 'master')
+
+ expect(repository.cherry_pick(user, pickable_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit can be cherry-picked' do
+ it 'should cherry-pick the changes' do
+ expect(repository.cherry_pick(user, pickable_commit, 'master')).to be_truthy
+ end
+ end
+
+ context 'cherry-picking a merge commit' do
+ it 'should cherry-pick the changes' do
+ expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).to be_nil
+
+ repository.cherry_pick(user, pickable_merge, 'master')
+ expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).not_to be_nil
+ end
+ end
+ end
+
describe '#before_delete' do
describe 'when a repository does not exist' do
before do
@@ -693,6 +795,16 @@ describe Repository, models: true do
end
+ describe "#copy_gitattributes" do
+ it 'returns true with a valid ref' do
+ expect(repository.copy_gitattributes('master')).to be_truthy
+ end
+
+ it 'returns false with an invalid ref' do
+ expect(repository.copy_gitattributes('invalid')).to be_falsey
+ end
+ end
+
describe "#main_language" do
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
@@ -746,13 +858,30 @@ describe Repository, models: true do
end
describe '#add_tag' do
- it 'adds a tag' do
- expect(repository).to receive(:before_push_tag)
+ context 'with a valid target' do
+ let(:user) { build_stubbed(:user) }
- expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
- with(repository.path_with_namespace, '8.5', 'master', 'foo')
+ it 'creates the tag using rugged' do
+ expect(repository.rugged.tags).to receive(:create).
+ with('8.5', repository.commit('master').id,
+ hash_including(message: 'foo',
+ tagger: hash_including(name: user.name, email: user.email))).
+ and_call_original
- repository.add_tag('8.5', 'master', 'foo')
+ repository.add_tag(user, '8.5', 'master', 'foo')
+ end
+
+ it 'returns a Gitlab::Git::Tag object' do
+ tag = repository.add_tag(user, '8.5', 'master', 'foo')
+
+ expect(tag).to be_a(Gitlab::Git::Tag)
+ end
+ end
+
+ context 'with an invalid target' do
+ it 'returns false' do
+ expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false
+ end
end
end
@@ -910,9 +1039,32 @@ describe Repository, models: true do
end
end
+ describe '.clean_old_archives' do
+ let(:path) { Gitlab.config.gitlab.repository_downloads_path }
+
+ context 'when the downloads directory does not exist' do
+ it 'does not remove any archives' do
+ expect(File).to receive(:directory?).with(path).and_return(false)
+
+ expect(Gitlab::Popen).not_to receive(:popen)
+
+ described_class.clean_old_archives
+ end
+ end
+
+ context 'when the downloads directory exists' do
+ it 'removes old archives' do
+ expect(File).to receive(:directory?).with(path).and_return(true)
+
+ expect(Gitlab::Popen).to receive(:popen)
+
+ described_class.clean_old_archives
+ end
+ end
+ end
+
def create_remote_branch(remote_name, branch_name, target)
rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
end
-
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 173628c08d0..8592e112c50 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -1,24 +1,3 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-# template :boolean default(FALSE)
-# push_events :boolean default(TRUE)
-# issues_events :boolean default(TRUE)
-# merge_requests_events :boolean default(TRUE)
-# tag_push_events :boolean default(TRUE)
-# note_events :boolean default(TRUE), not null
-# build_events :boolean default(FALSE), not null
-#
-
require 'spec_helper'
describe Service, models: true do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 5077ac7b62b..7a613e360d4 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -1,19 +1,3 @@
-# == Schema Information
-#
-# Table name: snippets
-#
-# id :integer not null, primary key
-# title :string(255)
-# content :text
-# author_id :integer not null
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# file_name :string(255)
-# type :string(255)
-# visibility_level :integer default(0), not null
-#
-
require 'spec_helper'
describe Snippet, models: true do
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index d9b86b9368f..623b82c01d8 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,21 +1,3 @@
-# == Schema Information
-#
-# Table name: todos
-#
-# id :integer not null, primary key
-# user_id :integer not null
-# project_id :integer not null
-# target_id :integer
-# target_type :string not null
-# author_id :integer
-# action :integer not null
-# state :string not null
-# created_at :datetime
-# updated_at :datetime
-# note_id :integer
-# commit_id :string
-#
-
require 'spec_helper'
describe Todo, models: true do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index de1a233dfff..b60725756cc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,66 +1,3 @@
-# == Schema Information
-#
-# Table name: users
-#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
-# unlock_token :string
-# otp_grace_period_started_at :datetime
-#
-
require 'spec_helper'
describe User, models: true do
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 967c34800d0..5ead735be48 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -59,7 +59,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/repository/commits/:sha/builds' do
before do
- project.ensure_ci_commit(commit.sha)
+ project.ensure_ci_commit(commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
end
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index 429a24109fd..f3785b19362 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
+ let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') }
+ let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') }
it_behaves_like 'a paginated resources' do
let(:request) { get api(get_url, reporter) }
@@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
- def create_status(opts = {})
- create(:commit_status, { commit: ci_commit }.merge(opts))
+ def create_status(commit, opts = {})
+ create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts))
end
- let!(:status1) { create_status(status: 'running') }
- let!(:status2) { create_status(name: 'coverage', status: 'pending') }
- let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) }
- let!(:status4) { create_status(name: 'coverage', status: 'success') }
- let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') }
- let!(:status6) { create_status(status: 'success') }
+ let!(:status1) { create_status(master, status: 'running') }
+ let!(:status2) { create_status(master, name: 'coverage', status: 'pending') }
+ let!(:status3) { create_status(develop, status: 'running', allow_failure: true) }
+ let!(:status4) { create_status(master, name: 'coverage', status: 'success') }
+ let!(:status5) { create_status(develop, name: 'coverage', status: 'success') }
+ let!(:status6) { create_status(master, status: 'success') }
context 'latest commit statuses' do
before { get api(get_url, reporter) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 7ff21175c1b..cb82ca7802d 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -32,6 +32,41 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context "since optional parameter" do
+ it "should return project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ since = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?since=#{since.utc.iso8601}", user)
+
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
+ end
+
+ context "until optional parameter" do
+ it "should return project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+
+ get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user)
+
+ expect(json_response.size).to eq(commits.size - 1)
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
+ end
+
+ context "invalid xmlschema date parameters" do
+ it "should return an invalid parameter error message" do
+ get api("/projects/#{project.id}/repository/commits?since=invalid-date", user)
+
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
+ end
+ end
end
describe "GET /projects:id/repository/commits/:sha" do
@@ -48,14 +83,14 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
- it "should return not_found for CI status" do
+ it "should return nil for commit without CI" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
- expect(json_response['status']).to eq('not_found')
+ expect(json_response['status']).to be_nil
end
it "should return status for CI" do
- ci_commit = project.ensure_ci_commit(project.repository.commit.sha)
+ ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master')
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
expect(json_response['status']).to eq(ci_commit.status)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index f88e39cad9e..9dd43f4fab3 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -39,6 +39,7 @@ describe API::API, api: true do
let!(:empty_milestone) do
create(:milestone, title: '2.0.0', project: project)
end
+ let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
before { project.team << [user, :reporter] }
@@ -232,8 +233,28 @@ describe API::API, api: true do
end
describe "GET /projects/:id/issues/:issue_id" do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(issue.id)
+ expect(json_response['iid']).to eq(issue.iid)
+ expect(json_response['project_id']).to eq(issue.project.id)
+ expect(json_response['title']).to eq(issue.title)
+ expect(json_response['description']).to eq(issue.description)
+ expect(json_response['state']).to eq(issue.state)
+ expect(json_response['created_at']).to be_present
+ expect(json_response['updated_at']).to be_present
+ expect(json_response['labels']).to eq(issue.label_names)
+ expect(json_response['milestone']).to be_a Hash
+ expect(json_response['assignee']).to be_a Hash
+ expect(json_response['author']).to be_a Hash
+ expect(json_response['user_notes_count']).to be(1)
+ end
+
it "should return a project issue by id" do
get api("/projects/#{project.id}/issues/#{issue.id}", user)
+
expect(response.status).to eq(200)
expect(json_response['title']).to eq(issue.title)
expect(json_response['iid']).to eq(issue.iid)
diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb
new file mode 100644
index 00000000000..c17dcb222a9
--- /dev/null
+++ b/spec/requests/api/licenses_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe API::Licenses, api: true do
+ include ApiHelpers
+
+ describe 'Entity' do
+ before { get api('/licenses/mit') }
+
+ it { expect(json_response['key']).to eq('mit') }
+ it { expect(json_response['name']).to eq('MIT License') }
+ it { expect(json_response['nickname']).to be_nil }
+ it { expect(json_response['popular']).to be true }
+ it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') }
+ it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') }
+ it { expect(json_response['description']).to include('A permissive license that is short and to the point.') }
+ it { expect(json_response['conditions']).to eq(%w[include-copyright]) }
+ it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) }
+ it { expect(json_response['limitations']).to eq(%w[no-liability]) }
+ it { expect(json_response['content']).to include('The MIT License (MIT)') }
+ end
+
+ describe 'GET /licenses' do
+ it 'returns a list of available license templates' do
+ get api('/licenses')
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(15)
+ expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
+ end
+
+ describe 'the popular parameter' do
+ context 'with popular=1' do
+ it 'returns a list of available popular license templates' do
+ get api('/licenses?popular=1')
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
+ end
+ end
+ end
+ end
+
+ describe 'GET /licenses/:key' do
+ context 'with :project and :fullname given' do
+ before do
+ get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
+ end
+
+ context 'for the mit license' do
+ let(:license_type) { 'mit' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('The MIT License (MIT)')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('Copyright (c) 2016 Anton')
+ end
+ end
+
+ context 'for the agpl-3.0 license' do
+ let(:license_type) { 'agpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ end
+ end
+
+ context 'for the gpl-3.0 license' do
+ let(:license_type) { 'gpl-3.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ end
+ end
+
+ context 'for the gpl-2.0 license' do
+ let(:license_type) { 'gpl-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('My Awesome Project')
+ expect(json_response['content']).to include('Copyright (C) 2016 Anton')
+ end
+ end
+
+ context 'for the apache-2.0 license' do
+ let(:license_type) { 'apache-2.0' }
+
+ it 'returns the license text' do
+ expect(json_response['content']).to include('Apache License')
+ end
+
+ it 'replaces placeholder values' do
+ expect(json_response['content']).to include('Copyright 2016 Anton')
+ end
+ end
+
+ context 'for an uknown license' do
+ let(:license_type) { 'muth-over9000' }
+
+ it 'returns a 404' do
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
+ context 'with no :fullname given' do
+ context 'with an authenticated user' do
+ let(:user) { create(:user) }
+
+ it 'replaces the copyright owner placeholder with the name of the current user' do
+ get api('/licenses/mit', user)
+
+ expect(json_response['content']).to include("Copyright (c) 2016 #{user.name}")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 1fa7e76894f..4b0111df149 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -113,6 +113,34 @@ describe API::API, api: true do
end
describe "GET /projects/:id/merge_requests/:merge_request_id" do
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(merge_request.id)
+ expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['project_id']).to eq(merge_request.project.id)
+ expect(json_response['title']).to eq(merge_request.title)
+ expect(json_response['description']).to eq(merge_request.description)
+ expect(json_response['state']).to eq(merge_request.state)
+ expect(json_response['created_at']).to be_present
+ expect(json_response['updated_at']).to be_present
+ expect(json_response['labels']).to eq(merge_request.label_names)
+ expect(json_response['milestone']).to be_nil
+ expect(json_response['assignee']).to be_a Hash
+ expect(json_response['author']).to be_a Hash
+ expect(json_response['target_branch']).to eq(merge_request.target_branch)
+ expect(json_response['source_branch']).to eq(merge_request.source_branch)
+ expect(json_response['upvotes']).to eq(0)
+ expect(json_response['downvotes']).to eq(0)
+ expect(json_response['source_project_id']).to eq(merge_request.source_project.id)
+ expect(json_response['target_project_id']).to eq(merge_request.target_project.id)
+ expect(json_response['work_in_progress']).to be_falsy
+ expect(json_response['merge_when_build_succeeds']).to be_falsy
+ expect(json_response['merge_status']).to eq('can_be_merged')
+ expect(json_response['user_notes_count']).to be(2)
+ end
+
it "should return merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 344f0fe0b7f..241995041bb 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -127,7 +127,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
- milestone.issues << create(:issue)
+ milestone.issues << create(:issue, project: project)
end
it 'should return project issues for a particular milestone' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
@@ -140,5 +140,34 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
expect(response.status).to eq(401)
end
+
+ describe 'confidential issues' do
+ let(:public_project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: public_project) }
+ let(:issue) { create(:issue, project: public_project) }
+ let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ before do
+ public_project.team << [user, :developer]
+ milestone.issues << issue << confidential_issue
+ end
+
+ it 'returns confidential issues to team members' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
+ end
+
+ it 'does not return confidential issues to regular users' do
+ get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
+ end
+ end
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index ec9eda0a2ed..49091fc0f49 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let!(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
let!(:snippet) { create(:project_snippet, project: project, author: user) }
@@ -45,7 +45,7 @@ describe API::API, api: true do
end
it "should return a 404 error when issue id not found" do
- get api("/projects/#{project.id}/issues/123/notes", user)
+ get api("/projects/#{project.id}/issues/12345/notes", user)
expect(response.status).to eq(404)
end
@@ -106,7 +106,7 @@ describe API::API, api: true do
end
it "should return a 404 error if issue note not found" do
- get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -134,7 +134,7 @@ describe API::API, api: true do
end
it "should return a 404 error if snippet note not found" do
- get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
expect(response.status).to eq(404)
end
end
@@ -191,6 +191,27 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ context 'when user does not have access to create noteable' do
+ let(:private_issue) { create(:issue, project: create(:project, :private)) }
+
+ ##
+ # We are posting to project user has access to, but we use issue id
+ # from a different project, see #15577
+ #
+ before do
+ post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
+ body: 'Hi!'
+ end
+
+ it 'responds with 500' do
+ expect(response.status).to eq 500
+ end
+
+ it 'does not create new note' do
+ expect(private_issue.notes.reload).to be_empty
+ end
+ end
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
@@ -211,7 +232,7 @@ describe API::API, api: true do
end
it 'should return a 404 error when note id not found' do
- put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+ put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
expect(response.status).to eq(404)
end
@@ -233,7 +254,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -248,7 +269,7 @@ describe API::API, api: true do
it 'should return a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
- "notes/123", user), body: "Hello!"
+ "notes/12345", user), body: "Hello!"
expect(response.status).to eq(404)
end
end
@@ -268,7 +289,7 @@ describe API::API, api: true do
end
it 'returns a 404 error when note id not found' do
- delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+ delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response.status).to eq(404)
end
@@ -288,7 +309,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
- "notes/123", user)
+ "notes/12345", user)
expect(response.status).to eq(404)
end
@@ -308,7 +329,7 @@ describe API::API, api: true do
it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/merge_requests/"\
- "#{merge_request.id}/notes/123", user)
+ "#{merge_request.id}/notes/12345", user)
expect(response.status).to eq(404)
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 142b637d291..ffb93bbb120 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -148,14 +148,24 @@ describe API::API, 'ProjectHooks', api: true do
expect(response.status).to eq(200)
end
- it "should return success when deleting non existent hook" do
+ it "should return a 404 error when deleting non existent hook" do
delete api("/projects/#{project.id}/hooks/42", user)
- expect(response.status).to eq(200)
+ expect(response.status).to eq(404)
end
it "should return a 405 error if hook id not given" do
delete api("/projects/#{project.id}/hooks", user)
expect(response.status).to eq(405)
end
+
+ it "shold return a 404 if a user attempts to delete project hooks he/she does not own" do
+ test_user = create(:user)
+ other_project = create(:project)
+ other_project.team << [test_user, :master]
+
+ delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
+ expect(response.status).to eq(404)
+ expect(WebHook.exists?(hook.id)).to be_truthy
+ end
end
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 3722ddf5a33..9706d060cfa 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -15,4 +15,91 @@ describe API::API, api: true do
expect(json_response['expires_at']).to be_nil
end
end
+
+ describe 'GET /projects/:project_id/snippets/' do
+ it 'all snippets available to team member' do
+ project = create(:project, :public)
+ user = create(:user)
+ project.team << [user, :developer]
+ public_snippet = create(:project_snippet, :public, project: project)
+ internal_snippet = create(:project_snippet, :internal, project: project)
+ private_snippet = create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(3)
+ expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ end
+
+ it 'hides private snippets from regular user' do
+ project = create(:project, :public)
+ user = create(:user)
+ create(:project_snippet, :private, project: project)
+
+ get api("/projects/#{project.id}/snippets/", user)
+ expect(response.status).to eq(200)
+ expect(json_response.size).to eq(0)
+ end
+ end
+
+ describe 'POST /projects/:project_id/snippets/' do
+ it 'creates a new snippet' do
+ admin = create(:admin)
+ project = create(:project)
+ params = {
+ title: 'Test Title',
+ file_name: 'test.rb',
+ code: 'puts "hello world"',
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ }
+
+ post api("/projects/#{project.id}/snippets/", admin), params
+
+ expect(response.status).to eq(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:code])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(params[:visibility_level])
+ end
+ end
+
+ describe 'PUT /projects/:project_id/snippets/:id/' do
+ it 'updates snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+ new_content = 'New content'
+
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+
+ expect(response.status).to eq(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ end
+ end
+
+ describe 'DELETE /projects/:project_id/snippets/:id/' do
+ it 'deletes snippet' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe 'GET /projects/:project_id/snippets/:id/raw' do
+ it 'returns raw text' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
+
+ expect(response.status).to eq(200)
+ expect(response.content_type).to eq 'text/plain'
+ expect(response.body).to eq(snippet.content)
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fccd08bd6da..66193eac051 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -11,7 +11,7 @@ describe API::API, api: true do
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
- let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
+ let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :master, user: user, project: project) }
let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 9f9c3b1cf4c..12e170b232f 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -32,9 +32,11 @@ describe API::API, api: true do
it "should return an array of project tags with release info" do
get api("/projects/#{project.id}/repository/tags", user)
+
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
+ expect(json_response.first['message']).to eq('Version 1.1.0')
expect(json_response.first['release']['description']).to eq(description)
end
end
@@ -145,7 +147,7 @@ describe API::API, api: true do
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Tag already exists')
+ expect(json_response['message']).to eq('Tag v8.0.0 already exists')
end
it 'should return 400 if ref name is invalid' do
@@ -153,7 +155,7 @@ describe API::API, api: true do
tag_name: 'mytag',
ref: 'foo'
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Invalid reference name')
+ expect(json_response['message']).to eq('Target foo is invalid')
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 679227bf881..40b24c125b5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -20,6 +20,24 @@ describe API::API, api: true do
end
context "when authenticated" do
+ #These specs are written just in case API authentication is not required anymore
+ context "when public level is restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true)
+ end
+
+ it "renders 403" do
+ get api("/users")
+ expect(response.status).to eq(403)
+ end
+
+ it "renders 404" do
+ get api("/users/#{user.id}")
+ expect(response.status).to eq(404)
+ end
+ end
+
it "should return an array of users" do
get api("/users", user)
expect(response.status).to eq(200)
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 57d7eb927fd..dfd361a2cdd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -20,8 +20,8 @@ describe Ci::API::API do
describe "POST /builds/register" do
it "should start a build" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
build = commit.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -56,8 +56,8 @@ describe Ci::API::API do
end
it "returns options" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -66,8 +66,8 @@ describe Ci::API::API do
end
it "returns variables" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -83,10 +83,10 @@ describe Ci::API::API do
it "returns variables for triggers" do
trigger = FactoryGirl.create(:ci_trigger, project: project)
- commit = FactoryGirl.create(:ci_commit, project: project)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
- commit.create_builds('master', false, nil, trigger_request)
+ commit.create_builds(nil, trigger_request)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -103,8 +103,8 @@ describe Ci::API::API do
end
it "returns dependent builds" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds('master', false, nil, nil)
+ commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
+ commit.create_builds(nil, nil)
commit.builds.where(stage: 'test').each(&:success)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -156,6 +156,52 @@ describe Ci::API::API do
end
end
+ describe 'PATCH /builds/:id/trace.txt' do
+ let(:build) { create(:ci_build, :trace, runner_id: runner.id) }
+ let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } }
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
+
+ before do
+ build.run!
+ patch ci_api("/builds/#{build.id}/trace.txt"), ' appended', headers_with_range
+ end
+
+ context 'when request is valid' do
+ it { expect(response.status).to eq 202 }
+ it { expect(build.reload.trace).to eq 'BUILD TRACE appended' }
+ it { expect(response.header).to have_key 'Range' }
+ it { expect(response.header).to have_key 'Build-Status' }
+ end
+
+ context 'when content-range start is too big' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) }
+
+ it { expect(response.status).to eq 416 }
+ it { expect(response.header).to have_key 'Range' }
+ it { expect(response.header['Range']).to eq '0-11' }
+ end
+
+ context 'when content-range start is too small' do
+ let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) }
+
+ it { expect(response.status).to eq 416 }
+ it { expect(response.header).to have_key 'Range' }
+ it { expect(response.header['Range']).to eq '0-11' }
+ end
+
+ context 'when Content-Range header is missing' do
+ let(:headers_with_range) { headers.merge({}) }
+
+ it { expect(response.status).to eq 400 }
+ end
+
+ context 'when build has been errased' do
+ let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it { expect(response.status).to eq 403 }
+ end
+ end
+
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
index 1fca3628686..ecc3a88a262 100644
--- a/spec/services/ci/create_builds_service_spec.rb
+++ b/spec/services/ci/create_builds_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::CreateBuildsService, services: true do
- let(:commit) { create(:ci_commit) }
+ let(:commit) { create(:ci_commit, ref: 'master') }
let(:user) { create(:user) }
describe '#execute' do
@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
#
subject do
- described_class.new.execute(commit, 'test', 'master', nil, user, nil, status)
+ described_class.new(commit).execute(commit, nil, user, status)
end
context 'next builds available' do
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 870861ad20a..4cc4b3870d1 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,7 +5,7 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
- let(:commit) { project.ensure_ci_commit(commit_sha) }
+ let(:commit) { project.ensure_ci_commit(commit_sha, 'master') }
let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
describe :execute do
diff --git a/spec/services/create_tag_service_spec.rb b/spec/services/create_tag_service_spec.rb
new file mode 100644
index 00000000000..91f9e663b66
--- /dev/null
+++ b/spec/services/create_tag_service_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe CreateTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ it 'creates the tag and returns success' do
+ response = service.execute('v42.42.42', 'master', 'Foo')
+
+ expect(response[:status]).to eq(:success)
+ expect(response[:tag]).to be_a Gitlab::Git::Tag
+ expect(response[:tag].name).to eq('v42.42.42')
+ end
+
+ context 'when target is invalid' do
+ it 'returns an error' do
+ response = service.execute('v1.1.0', 'foo', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Target foo is invalid')
+ end
+ end
+
+ context 'when tag already exists' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(Rugged::TagError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag v1.1.0 already exists')
+ end
+ end
+
+ context 'when pre-receive hook fails' do
+ it 'returns an error' do
+ expect(repository).to receive(:add_tag).
+ with(user, 'v1.1.0', 'master', 'Foo').
+ and_raise(GitHooksService::PreReceiveError)
+
+ response = service.execute('v1.1.0', 'master', 'Foo')
+
+ expect(response).to eq(status: :error,
+ message: 'Tag creation was rejected by Git hook')
+ end
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index b40a5c1c818..eeab540c2fd 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -201,6 +201,36 @@ describe GitPushService, services: true do
end
+ describe "Updates git attributes" do
+ context "for default branch" do
+ it "calls the copy attributes method for the first push to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with('master')
+
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "calls the copy attributes method for changes to the default branch" do
+ expect(project.repository).to receive(:copy_gitattributes).with('refs/heads/master')
+
+ execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master')
+ end
+ end
+
+ context "for non-default branch" do
+ before do
+ # Make sure the "default" branch is different
+ allow(project).to receive(:default_branch).and_return('not-master')
+ end
+
+ it "does not call copy attributes method" do
+ expect(project.repository).not_to receive(:copy_gitattributes)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+ end
+
+
describe "Webhooks" do
context "execute webhooks" do
it "when pushing a branch for the first time" do
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index cc780587e74..a63656e6268 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -5,19 +5,17 @@ describe GitTagPushService, services: true do
let(:user) { create :user }
let(:project) { create :project }
- let(:service) { GitTagPushService.new }
+ let(:service) { GitTagPushService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
- before do
- @oldrev = Gitlab::Git::BLANK_SHA
- @newrev = "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" # gitlab-test: git rev-parse refs/tags/v1.1.0
- @ref = 'refs/tags/v1.1.0'
- end
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
describe "Git Tag Push Data" do
before do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ service.execute
@push_data = service.push_data
- @tag_name = Gitlab::Git.ref_name(@ref)
+ @tag_name = Gitlab::Git.ref_name(ref)
@tag = project.repository.find_tag(@tag_name)
@commit = project.commit(@tag.target)
end
@@ -25,9 +23,9 @@ describe GitTagPushService, services: true do
subject { @push_data }
it { is_expected.to include(object_kind: 'tag_push') }
- it { is_expected.to include(ref: @ref) }
- it { is_expected.to include(before: @oldrev) }
- it { is_expected.to include(after: @newrev) }
+ it { is_expected.to include(ref: ref) }
+ it { is_expected.to include(before: oldrev) }
+ it { is_expected.to include(after: newrev) }
it { is_expected.to include(message: @tag.message) }
it { is_expected.to include(user_id: user.id) }
it { is_expected.to include(user_name: user.name) }
@@ -80,9 +78,11 @@ describe GitTagPushService, services: true do
describe "Webhooks" do
context "execute webhooks" do
+ let(:service) { GitTagPushService.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
+
it "when pushing tags" do
expect(project).to receive(:execute_hooks)
- service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
+ service.execute
end
end
end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 6a7ea4b2f44..e91906d0d49 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -100,7 +100,7 @@ describe Issues::BulkUpdateService, services: true do
describe :update_milestone do
before do
- @milestone = create :milestone
+ @milestone = create(:milestone, project: @project)
@params = {
issues_ids: [issue.id],
milestone_id: @milestone.id
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 5e7915db7e1..ac28b6f71f9 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -3,40 +3,75 @@ require 'spec_helper'
describe Issues::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- let(:assignee) { create(:user) }
- describe :execute do
- context 'valid params' do
+ describe '#execute' do
+ let(:issue) { described_class.new(project, user, opts).execute }
+
+ context 'when params are valid' do
+ let(:assignee) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:labels) { create_pair(:label, project: project) }
+
before do
project.team << [user, :master]
project.team << [assignee, :master]
+ end
- opts = {
- title: 'Awesome issue',
+ let(:opts) do
+ { title: 'Awesome issue',
description: 'please fix',
- assignee: assignee
- }
-
- @issue = Issues::CreateService.new(project, user, opts).execute
+ assignee: assignee,
+ label_ids: labels.map(&:id),
+ milestone_id: milestone.id }
end
- it { expect(@issue).to be_valid }
- it { expect(@issue.title).to eq('Awesome issue') }
- it { expect(@issue.assignee).to eq assignee }
+ it { expect(issue).to be_valid }
+ it { expect(issue.title).to eq('Awesome issue') }
+ it { expect(issue.assignee).to eq assignee }
+ it { expect(issue.labels).to match_array labels }
+ it { expect(issue.milestone).to eq milestone }
it 'creates a pending todo for new assignee' do
attributes = {
project: project,
author: user,
user: assignee,
- target_id: @issue.id,
- target_type: @issue.class.name,
+ target_id: issue.id,
+ target_type: issue.class.name,
action: Todo::ASSIGNED,
state: :pending
}
expect(Todo.where(attributes).count).to eq 1
end
+
+ context 'when label belongs to different project' do
+ let(:label) { create(:label) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ label_ids: [label.id] }
+ end
+
+ it 'does not assign label'do
+ expect(issue.labels).to_not include label
+ end
+ end
+
+ context 'when milestone belongs to different project' do
+ let(:milestone) { create(:milestone) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ milestone_id: milestone.id }
+ end
+
+ it 'does not assign milestone' do
+ expect(issue.milestone).to_not eq milestone
+ end
+ end
end
end
end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 2a5e4ac3ec4..c15e26189a5 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -7,10 +7,11 @@ describe Issues::MoveService, services: true do
let(:description) { 'Some issue description' }
let(:old_project) { create(:project) }
let(:new_project) { create(:project) }
+ let(:milestone1) { create(:milestone, project_id: old_project.id, title: 'v9.0') }
let(:old_issue) do
create(:issue, title: title, description: description,
- project: old_project, author: author)
+ project: old_project, author: author, milestone: milestone1)
end
let(:move_service) do
@@ -21,11 +22,24 @@ describe Issues::MoveService, services: true do
before do
old_project.team << [user, :reporter]
new_project.team << [user, :reporter]
+
+ ['label1', 'label2'].each do |label|
+ old_issue.labels << create(:label,
+ project_id: old_project.id,
+ title: label)
+ end
+
+ new_project.labels << create(:label, title: 'label1')
+ new_project.labels << create(:label, title: 'label2')
end
end
describe '#execute' do
shared_context 'issue move executed' do
+ let!(:milestone2) do
+ create(:milestone, project_id: new_project.id, title: 'v9.0')
+ end
+
let!(:new_issue) { move_service.execute(old_issue, new_project) }
end
@@ -39,6 +53,23 @@ describe Issues::MoveService, services: true do
expect(new_issue.project).to eq new_project
end
+ it 'assigns milestone to new issue' do
+ expect(new_issue.reload.milestone.title).to eq 'v9.0'
+ expect(new_issue.reload.milestone).to eq(milestone2)
+ end
+
+ it 'assign labels to new issue' do
+ expected_label_titles = new_issue.reload.labels.map(&:title)
+ expect(expected_label_titles).to include 'label1'
+ expect(expected_label_titles).to include 'label2'
+ expect(expected_label_titles.size).to eq 2
+
+ new_issue.labels.each do |label|
+ expect(new_project.labels).to include(label)
+ expect(old_project.labels).not_to include(label)
+ end
+ end
+
it 'rewrites issue title' do
expect(new_issue.title).to eq title
end
@@ -72,11 +103,6 @@ describe Issues::MoveService, services: true do
expect(new_issue.author).to eq author
end
- it 'removes data that is invalid in new context' do
- expect(new_issue.milestone).to be_nil
- expect(new_issue.labels).to be_empty
- end
-
it 'creates a new internal id for issue' do
expect(new_issue.iid).to be 1
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 6b214a0d96b..52f69306994 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -4,10 +4,15 @@ describe Issues::UpdateService, services: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
- let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
- let(:label) { create(:label) }
+ let(:project) { create(:empty_project) }
+ let(:label) { create(:label, project: project) }
let(:label2) { create(:label) }
- let(:project) { issue.project }
+
+ let(:issue) do
+ create(:issue, title: 'Old title',
+ assignee_id: user3.id,
+ project: project)
+ end
before do
project.team << [user, :master]
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
new file mode 100644
index 00000000000..782d74ec5ec
--- /dev/null
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -0,0 +1,181 @@
+require 'spec_helper'
+
+describe MergeRequests::BuildService, services: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:issue_confidential) { false }
+ let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) }
+ let(:description) { nil }
+ let(:source_branch) { 'feature-branch' }
+ let(:target_branch) { 'master' }
+ let(:merge_request) { service.execute }
+ let(:compare) { double(:compare, commits: commits) }
+ let(:commit_1) { double(:commit_1, safe_message: "Initial commit\n\nCreate the app") }
+ let(:commit_2) { double(:commit_2, safe_message: 'This is a bad commit message!') }
+ let(:commits) { nil }
+
+ let(:service) do
+ MergeRequests::BuildService.new(project, user,
+ description: description,
+ source_branch: source_branch,
+ target_branch: target_branch)
+ end
+
+ before do
+ allow(CompareService).to receive_message_chain(:new, :execute).and_return(compare)
+ end
+
+ describe 'execute' do
+ context 'missing source branch' do
+ let(:source_branch) { '' }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('You must select source and target branch')
+ end
+ end
+
+ context 'missing target branch' do
+ let(:target_branch) { '' }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+
+ it 'adds an error message to the merge request' do
+ expect(merge_request.errors).to contain_exactly('You must select source and target branch')
+ end
+ end
+
+ context 'no commits in the diff' do
+ let(:commits) { [] }
+
+ it 'forbids the merge request from being created' do
+ expect(merge_request.can_be_created).to eq(false)
+ end
+ end
+
+ context 'one commit in the diff' do
+ let(:commits) { [commit_1] }
+
+ it 'allows the merge request to be created' do
+ expect(merge_request.can_be_created).to eq(true)
+ end
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
+ end
+
+ it 'uses the description of the commit as the description of the merge request' do
+ expect(merge_request.description).to eq(commit_1.safe_message.split(/\n+/, 2).last)
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'keeps the description from the initial params' do
+ expect(merge_request.description).to eq(description)
+ end
+ end
+
+ context 'commit has no description' do
+ let(:commits) { [commit_2] }
+
+ it 'uses the title of the commit as the title of the merge request' do
+ expect(merge_request.title).to eq(commit_2.safe_message)
+ end
+
+ it 'sets the description to nil' do
+ expect(merge_request.description).to be_nil
+ end
+ end
+
+ context 'branch starts with issue IID followed by a hyphen' do
+ let(:source_branch) { "#{issue.iid}-fix-issue" }
+
+ it 'appends "Closes #$issue-iid" to the description' do
+ expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\nCloses ##{issue.iid}")
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'appends "Closes #$issue-iid" to the description' do
+ expect(merge_request.description).to eq("#{description}\nCloses ##{issue.iid}")
+ end
+ end
+
+ context 'commit has no description' do
+ let(:commits) { [commit_2] }
+
+ it 'sets the description to "Closes #$issue-iid"' do
+ expect(merge_request.description).to eq("Closes ##{issue.iid}")
+ end
+ end
+ end
+ end
+
+ context 'more than one commit in the diff' do
+ let(:commits) { [commit_1, commit_2] }
+
+ it 'allows the merge request to be created' do
+ expect(merge_request.can_be_created).to eq(true)
+ end
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq('Feature branch')
+ end
+
+ it 'does not add a description' do
+ expect(merge_request.description).to be_nil
+ end
+
+ context 'merge request already has a description set' do
+ let(:description) { 'Merge request description' }
+
+ it 'keeps the description from the initial params' do
+ expect(merge_request.description).to eq(description)
+ end
+ end
+
+ context 'branch starts with GitLab issue IID followed by a hyphen' do
+ let(:source_branch) { "#{issue.iid}-fix-issue" }
+
+ it 'sets the title to: Resolves "$issue-title"' do
+ expect(merge_request.title).to eq("Resolve \"#{issue.title}\"")
+ end
+
+ context 'issue does not exist' do
+ let(:source_branch) { "#{issue.iid.succ}-fix-issue" }
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq("#{issue.iid.succ} fix issue")
+ end
+ end
+
+ context 'issue is confidential' do
+ let(:issue_confidential) { true }
+
+ it 'uses the title of the branch as the merge request title' do
+ expect(merge_request.title).to eq("#{issue.iid} fix issue")
+ end
+ end
+ end
+
+ context 'branch starts with external issue IID followed by a hyphen' do
+ let(:source_branch) { '12345-fix-issue' }
+
+ before { allow(project).to receive(:default_issues_tracker?).and_return(false) }
+
+ it 'sets the title to: Resolves External Issue $issue-iid' do
+ expect(merge_request.title).to eq('Resolve External Issue 12345')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index cb8cff2fa8c..213e8c2eb3a 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,14 +1,19 @@
require 'spec_helper'
describe MergeRequests::UpdateService, services: true do
+ let(:project) { create(:project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
- let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
- let(:project) { merge_request.project }
- let(:label) { create(:label) }
+ let(:label) { create(:label, project: project) }
let(:label2) { create(:label) }
+ let(:merge_request) do
+ create(:merge_request, :simple, title: 'Old title',
+ assignee_id: user3.id,
+ source_project: project)
+ end
+
before do
project.team << [user, :master]
project.team << [user2, :developer]
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index d7c72dc0811..4bbc4ddc3ab 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -10,7 +10,7 @@ describe NotificationService, services: true do
end
describe 'Keys' do
- describe :new_key do
+ describe '#new_key' do
let!(:key) { create(:personal_key) }
it { expect(notification.new_key(key)).to be_truthy }
@@ -22,7 +22,7 @@ describe NotificationService, services: true do
end
describe 'Email' do
- describe :new_email do
+ describe '#new_email' do
let!(:email) { create(:email) }
it { expect(notification.new_email(email)).to be_truthy }
@@ -147,8 +147,8 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_note do
- it do
+ describe '#new_note' do
+ it 'notifies the team members' do
notification.new_note(note)
# Notify all team members
@@ -177,6 +177,39 @@ describe NotificationService, services: true do
end
end
+ context 'project snippet note' do
+ let(:project) { create(:empty_project, :public) }
+ let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
+ let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: snippet.project.id, note: '@all mentioned') }
+
+ before do
+ build_team(note.project)
+ note.project.team << [note.author, :master]
+ ActionMailer::Base.deliveries.clear
+ end
+
+ describe '#new_note' do
+ it 'notifies the team members' do
+ notification.new_note(note)
+
+ # Notify all team members
+ note.project.team.members.each do |member|
+ # User with disabled notification should not be notified
+ next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
+ should_email(member)
+ end
+
+ should_email(note.noteable.author)
+ should_not_email(note.author)
+ should_email(@u_mentioned)
+ should_not_email(@u_disabled)
+ should_email(@u_not_mentioned)
+ end
+ end
+ end
+
context 'commit note' do
let(:project) { create(:project, :public) }
let(:note) { create(:note_on_commit, project: project) }
@@ -187,7 +220,7 @@ describe NotificationService, services: true do
allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
end
- describe :new_note, :perform_enqueued_jobs do
+ describe '#new_note, #perform_enqueued_jobs' do
it do
notification.new_note(note)
@@ -230,7 +263,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_issue do
+ describe '#new_issue' do
it do
notification.new_issue(issue, @u_disabled)
@@ -289,7 +322,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_issue do
+ describe '#reassigned_issue' do
it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
@@ -419,7 +452,7 @@ describe NotificationService, services: true do
end
end
- describe :close_issue do
+ describe '#close_issue' do
it 'should sent email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
@@ -435,7 +468,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_issue do
+ describe '#reopen_issue' do
it 'should send email to issue assignee and issue author' do
notification.reopen_issue(issue, @u_disabled)
@@ -461,7 +494,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :new_merge_request do
+ describe '#new_merge_request' do
it do
notification.new_merge_request(merge_request, @u_disabled)
@@ -483,7 +516,7 @@ describe NotificationService, services: true do
end
end
- describe :reassigned_merge_request do
+ describe '#reassigned_merge_request' do
it do
notification.reassigned_merge_request(merge_request, merge_request.author)
@@ -498,7 +531,7 @@ describe NotificationService, services: true do
end
end
- describe :relabel_merge_request do
+ describe '#relabel_merge_request' do
let(:label) { create(:label, merge_requests: [merge_request]) }
let(:label2) { create(:label) }
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
@@ -527,7 +560,7 @@ describe NotificationService, services: true do
end
end
- describe :closed_merge_request do
+ describe '#closed_merge_request' do
it do
notification.close_mr(merge_request, @u_disabled)
@@ -542,7 +575,7 @@ describe NotificationService, services: true do
end
end
- describe :merged_merge_request do
+ describe '#merged_merge_request' do
it do
notification.merge_mr(merge_request, @u_disabled)
@@ -557,7 +590,7 @@ describe NotificationService, services: true do
end
end
- describe :reopen_merge_request do
+ describe '#reopen_merge_request' do
it do
notification.reopen_mr(merge_request, @u_disabled)
@@ -581,7 +614,7 @@ describe NotificationService, services: true do
ActionMailer::Base.deliveries.clear
end
- describe :project_was_moved do
+ describe '#project_was_moved' do
it do
notification.project_was_moved(project, "gitlab/gitlab")
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 32bf3acf483..7f2dcdab960 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -112,9 +112,16 @@ describe Projects::ImportService, services: true do
def stub_github_omniauth_provider
provider = OpenStruct.new(
- name: 'github',
- app_id: 'asd123',
- app_secret: 'asd123'
+ 'name' => 'github',
+ 'app_id' => 'asd123',
+ 'app_secret' => 'asd123',
+ 'args' => {
+ 'client_options' => {
+ 'site' => 'https://github.com/api/v3',
+ 'authorize_url' => 'https://github.com/login/oauth/authorize',
+ 'token_url' => 'https://github.com/login/oauth/access_token'
+ }
+ }
)
Gitlab.config.omniauth.providers << provider
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 240eae10052..5fbf2ae5247 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -506,6 +506,15 @@ describe SystemNoteService, services: true do
end
end
+ describe '.new_commit_summary' do
+ it 'escapes HTML titles' do
+ commit = double(title: '<pre>This is a test</pre>', short_id: '12345678')
+ escaped = '* 12345678 - &lt;pre&gt;This is a test&lt;&#x2F;pre&gt;'
+
+ expect(described_class.new_commit_summary([commit])).to eq([escaped])
+ end
+ end
+
include JiraServiceHelper
describe 'JIRA integration' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 455b19c89cc..0d88b77ad74 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -55,6 +55,25 @@ describe TodoService, services: true do
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
+
+ context 'when a private group is mentioned' do
+ let(:group) { create :group, :private }
+ let(:project) { create :project, :private, group: group }
+ let(:issue) { create :issue, author: author, project: project, description: group.to_reference }
+
+ before do
+ group.add_owner(author)
+ group.add_user(member, Gitlab::Access::DEVELOPER)
+ group.add_user(john_doe, Gitlab::Access::DEVELOPER)
+
+ service.new_issue(issue, author)
+ end
+
+ it 'creates a todo for group members' do
+ should_create_todo(user: member, target: issue)
+ should_create_todo(user: john_doe, target: issue)
+ end
+ end
end
describe '#update_issue' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 596d607f2a1..576d16e7ea3 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -51,10 +51,4 @@ FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
-# Work around a Rails 4.2.5.1 issue
-# See https://github.com/rspec/rspec-rails/issues/1532
-RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
- alias_method :find_all_anywhere, :find_all
-end
-
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml
index a5b256bd3ec..e55a61b2b94 100644
--- a/spec/support/gitlab_stubs/gitlab_ci.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci.yml
@@ -4,7 +4,7 @@ services:
before_script:
- gem install bundler
- - bundle install
+ - bundle install
- bundle exec rake db:create
variables:
@@ -17,7 +17,7 @@ types:
rspec:
script: "rake spec"
- tags:
+ tags:
- ruby
- postgres
only:
@@ -26,27 +26,32 @@ rspec:
spinach:
script: "rake spinach"
allow_failure: true
- tags:
+ tags:
- ruby
- mysql
except:
- tags
staging:
+ variables:
+ KEY1: value1
+ KEY2: value2
script: "cap deploy stating"
type: deploy
- tags:
+ tags:
- ruby
- mysql
except:
- stable
production:
+ variables:
+ DB_NAME: mysql
type: deploy
- script:
+ script:
- cap deploy production
- cap notify
- tags:
+ tags:
- ruby
- mysql
only:
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
new file mode 100644
index 00000000000..b6d7436c360
--- /dev/null
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -0,0 +1,7 @@
+RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
+ it { is_expected.to allow_value('https://example.com').for(url_attr) }
+
+ it { is_expected.not_to allow_value('example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) }
+ it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) }
+end
diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb
index 422083875d7..7dbaa6a6459 100644
--- a/spec/support/project_hook_data_shared_example.rb
+++ b/spec/support/project_hook_data_shared_example.rb
@@ -1,4 +1,4 @@
-RSpec.shared_examples 'project hook data' do |project_key: :project|
+RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description)
@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project|
end
end
+RSpec.shared_examples 'project hook data' do |project_key: :project|
+ it 'contains project data' do
+ expect(data[project_key][:name]).to eq(project.name)
+ expect(data[project_key][:description]).to eq(project.description)
+ expect(data[project_key][:web_url]).to eq(project.web_url)
+ expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
+ expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
+ expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
+ expect(data[project_key][:namespace]).to eq(project.namespace.name)
+ expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
+ expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
+ expect(data[project_key][:default_branch]).to eq(project.default_branch)
+ end
+end
+
RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
it 'contains deprecated repository data' do
expect(data[:repository][:name]).to eq(project.name)
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index aa8258d6dad..73f375c481b 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -42,7 +42,7 @@ Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
eos
)
end
-
+
def another_sample_commit
OpenStruct.new(
id: "e56497bb5f03a90a51293fc6d516788730953899",
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index f486e45ddad..27727d6abf9 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -4,7 +4,7 @@ describe RepositoryCheck::BatchWorker do
subject { described_class.new }
it 'prefers projects that have never been checked' do
- projects = create_list(:project, 3)
+ projects = create_list(:project, 3, created_at: 1.week.ago)
projects[0].update_column(:last_repository_check_at, 4.months.ago)
projects[2].update_column(:last_repository_check_at, 3.months.ago)
@@ -12,7 +12,7 @@ describe RepositoryCheck::BatchWorker do
end
it 'sorts projects by last_repository_check_at' do
- projects = create_list(:project, 3)
+ projects = create_list(:project, 3, created_at: 1.week.ago)
projects[0].update_column(:last_repository_check_at, 2.months.ago)
projects[1].update_column(:last_repository_check_at, 4.months.ago)
projects[2].update_column(:last_repository_check_at, 3.months.ago)
@@ -21,7 +21,7 @@ describe RepositoryCheck::BatchWorker do
end
it 'excludes projects that were checked recently' do
- projects = create_list(:project, 3)
+ projects = create_list(:project, 3, created_at: 1.week.ago)
projects[0].update_column(:last_repository_check_at, 2.days.ago)
projects[1].update_column(:last_repository_check_at, 2.months.ago)
projects[2].update_column(:last_repository_check_at, 3.days.ago)
@@ -30,10 +30,17 @@ describe RepositoryCheck::BatchWorker do
end
it 'does nothing when repository checks are disabled' do
- create(:empty_project)
+ create(:empty_project, created_at: 1.week.ago)
current_settings = double('settings', repository_checks_enabled: false)
expect(subject).to receive(:current_settings) { current_settings }
expect(subject.perform).to eq(nil)
end
+
+ it 'skips projects created less than 24 hours ago' do
+ project = create(:empty_project)
+ project.update_column(:created_at, 23.hours.ago)
+
+ expect(subject.perform).to eq([])
+ end
end
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
new file mode 100644
index 00000000000..5a03bb77ebd
--- /dev/null
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+require 'fileutils'
+
+describe RepositoryCheck::SingleRepositoryWorker do
+ subject { described_class.new }
+
+ it 'fails if the wiki repository is broken' do
+ project = create(:project_empty_repo, wiki_enabled: true)
+ project.create_wiki
+
+ # Test sanity: everything should be fine before the wiki repo is broken
+ subject.perform(project.id)
+ expect(project.reload.last_repository_check_failed).to eq(false)
+
+ break_wiki(project)
+ subject.perform(project.id)
+
+ expect(project.reload.last_repository_check_failed).to eq(true)
+ end
+
+ it 'skips wikis when disabled' do
+ project = create(:project_empty_repo, wiki_enabled: false)
+ # Make sure the test would fail if the wiki repo was checked
+ break_wiki(project)
+
+ subject.perform(project.id)
+
+ expect(project.reload.last_repository_check_failed).to eq(false)
+ end
+
+ it 'creates missing wikis' do
+ project = create(:project_empty_repo, wiki_enabled: true)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(project.reload.last_repository_check_failed).to eq(false)
+ end
+
+ it 'does not create a wiki if the main repo does not exist at all' do
+ project = create(:project_empty_repo)
+ FileUtils.rm_rf(project.repository.path_to_repo)
+ FileUtils.rm_rf(wiki_path(project))
+
+ subject.perform(project.id)
+
+ expect(File.exist?(wiki_path(project))).to eq(false)
+ end
+
+ def break_wiki(project)
+ FileUtils.rm_rf(wiki_path(project) + '/objects')
+ end
+
+ def wiki_path(project)
+ project.wiki.repository.path_to_repo
+ end
+end