summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-06-10 22:27:06 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2016-06-10 22:27:06 +0200
commit95a10f4533fdb708c61ffda95099bddd94800f02 (patch)
treebf5c561472c47d34e5ff1f643c49cd2a6b206f15
parent17c6bec79d876ce932edc0edc5d17622adb2f724 (diff)
parentb4e84809e8170a43126507da0bc6a3b94c33804b (diff)
downloadgitlab-ce-95a10f4533fdb708c61ffda95099bddd94800f02.tar.gz
Merge remote-tracking branch 'origin/master' into ci-lfs-fetch
-rw-r--r--.github/ISSUE_TEMPLATE.md3
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md3
-rw-r--r--.gitlab-ci.yml352
-rw-r--r--.rubocop.yml10
-rw-r--r--.vagrant_enabled0
-rw-r--r--CHANGELOG76
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile21
-rw-r--r--Gemfile.lock169
-rwxr-xr-xRakefile2
-rw-r--r--app/assets/javascripts/LabelManager.js.coffee84
-rw-r--r--app/assets/javascripts/activities.js.coffee5
-rw-r--r--app/assets/javascripts/application.js.coffee14
-rw-r--r--app/assets/javascripts/awards_handler.coffee427
-rw-r--r--app/assets/javascripts/ci/build.coffee91
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee3
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee5
-rw-r--r--app/assets/javascripts/flash.js.coffee2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee81
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee103
-rw-r--r--app/assets/javascripts/graphs/application.js.coffee1
-rw-r--r--app/assets/javascripts/issuable.js.coffee25
-rw-r--r--app/assets/javascripts/issues-bulk-assignment.js.coffee109
-rw-r--r--app/assets/javascripts/labels_select.js.coffee77
-rw-r--r--app/assets/javascripts/lib/common_utils.js.coffee24
-rw-r--r--app/assets/javascripts/lib/datetime_utility.js.coffee9
-rw-r--r--app/assets/javascripts/lib/emoji_aliases.js.coffee.erb2
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee19
-rw-r--r--app/assets/javascripts/notes.js.coffee20
-rw-r--r--app/assets/javascripts/pager.js.coffee3
-rw-r--r--app/assets/javascripts/project_new.js.coffee19
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee35
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee18
-rw-r--r--app/assets/javascripts/subscription.js.coffee5
-rw-r--r--app/assets/javascripts/u2f/authenticate.js.coffee63
-rw-r--r--app/assets/javascripts/u2f/error.js.coffee13
-rw-r--r--app/assets/javascripts/u2f/register.js.coffee63
-rw-r--r--app/assets/javascripts/u2f/util.js.coffee.erb15
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee3
-rw-r--r--app/assets/javascripts/users_select.js.coffee10
-rw-r--r--app/assets/stylesheets/framework/blocks.scss5
-rw-r--r--app/assets/stylesheets/framework/buttons.scss33
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss40
-rw-r--r--app/assets/stylesheets/framework/forms.scss1
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss34
-rw-r--r--app/assets/stylesheets/framework/header.scss33
-rw-r--r--app/assets/stylesheets/framework/jquery.scss43
-rw-r--r--app/assets/stylesheets/framework/lists.scss24
-rw-r--r--app/assets/stylesheets/framework/mixins.scss8
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss86
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss134
-rw-r--r--app/assets/stylesheets/framework/timeline.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss5
-rw-r--r--app/assets/stylesheets/framework/variables.scss11
-rw-r--r--app/assets/stylesheets/mailers/repository_push_email.scss179
-rw-r--r--app/assets/stylesheets/notify.scss16
-rw-r--r--app/assets/stylesheets/pages/awards.scss25
-rw-r--r--app/assets/stylesheets/pages/builds.scss98
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss10
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/labels.scss138
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss10
-rw-r--r--app/assets/stylesheets/pages/note_form.scss46
-rw-r--r--app/assets/stylesheets/pages/notes.scss58
-rw-r--r--app/assets/stylesheets/pages/projects.scss18
-rw-r--r--app/assets/stylesheets/pages/search.scss6
-rw-r--r--app/assets/stylesheets/pages/xterm.scss19
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/application_controller.rb16
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb59
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb31
-rw-r--r--app/controllers/jwt_controller.rb42
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb45
-rw-r--r--app/controllers/projects/artifacts_controller.rb2
-rw-r--r--app/controllers/projects/avatars_controller.rb5
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb8
-rw-r--r--app/controllers/projects/commit_controller.rb10
-rw-r--r--app/controllers/projects/git_http_controller.rb147
-rw-r--r--app/controllers/projects/issues_controller.rb16
-rw-r--r--app/controllers/projects/labels_controller.rb29
-rw-r--r--app/controllers/projects/merge_requests_controller.rb43
-rw-r--r--app/controllers/projects/notes_controller.rb42
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects/raw_controller.rb5
-rw-r--r--app/controllers/projects/repositories_controller.rb3
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/sessions_controller.rb37
-rw-r--r--app/finders/issuable_finder.rb8
-rw-r--r--app/finders/notes_finder.rb4
-rw-r--r--app/finders/todos_finder.rb14
-rw-r--r--app/helpers/appearances_helper.rb4
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/auth_helper.rb2
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb6
-rw-r--r--app/helpers/dropdowns_helper.rb4
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb13
-rw-r--r--app/helpers/issues_helper.rb14
-rw-r--r--app/helpers/labels_helper.rb6
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/helpers/nav_helper.rb2
-rw-r--r--app/helpers/notifications_helper.rb22
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/helpers/workhorse_helper.rb24
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/award_emoji.rb26
-rw-r--r--app/models/ci/build.rb29
-rw-r--r--app/models/ci/pipeline.rb (renamed from app/models/ci/commit.rb)14
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/commit_status.rb12
-rw-r--r--app/models/concerns/awardable.rb81
-rw-r--r--app/models/concerns/issuable.rb91
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb14
-rw-r--r--app/models/legacy_diff_note.rb4
-rw-r--r--app/models/merge_request.rb32
-rw-r--r--app/models/note.rb53
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/project.rb117
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/service.rb10
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/u2f_registration.rb40
-rw-r--r--app/models/user.rb63
-rw-r--r--app/services/ci/create_builds_service.rb20
-rw-r--r--app/services/ci/create_pipeline_service.rb4
-rw-r--r--app/services/ci/create_trigger_request_service.rb6
-rw-r--r--app/services/ci/image_for_build_service.rb6
-rw-r--r--app/services/create_commit_builds_service.rb18
-rw-r--r--app/services/issuable_base_service.rb34
-rw-r--r--app/services/issues/bulk_update_service.rb6
-rw-r--r--app/services/issues/move_service.rb9
-rw-r--r--app/services/merge_requests/base_service.rb8
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb4
-rw-r--r--app/services/notes/create_service.rb7
-rw-r--r--app/services/notes/post_process_service.rb2
-rw-r--r--app/services/notification_service.rb3
-rw-r--r--app/services/oauth2/access_token_validation_service.rb1
-rw-r--r--app/services/projects/create_service.rb26
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/todo_service.rb19
-rw-r--r--app/views/admin/application_settings/_form.html.haml5
-rw-r--r--app/views/admin/runners/show.html.haml4
-rw-r--r--app/views/award_emoji/_awards_block.html.haml15
-rw-r--r--app/views/dashboard/_groups_head.html.haml1
-rw-r--r--app/views/dashboard/_projects_head.html.haml1
-rw-r--r--app/views/devise/confirmations/almost_there.haml3
-rw-r--r--app/views/devise/sessions/two_factor.html.haml22
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/emojis/index.html.haml4
-rw-r--r--app/views/events/event/_common.html.haml10
-rw-r--r--app/views/groups/group_members/_group_member.html.haml6
-rw-r--r--app/views/groups/milestones/new.html.haml3
-rw-r--r--app/views/groups/show.html.haml3
-rw-r--r--app/views/help/_shortcuts.html.haml2
-rw-r--r--app/views/import/github/status.html.haml4
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml16
-rw-r--r--app/views/layouts/_search.html.haml7
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml1
-rw-r--r--app/views/layouts/devise_empty.html.haml1
-rw-r--r--app/views/layouts/errors.html.haml1
-rw-r--r--app/views/layouts/nav/_admin.html.haml42
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml36
-rw-r--r--app/views/layouts/nav/_explore.html.haml8
-rw-r--r--app/views/layouts/nav/_group.html.haml12
-rw-r--r--app/views/layouts/nav/_profile.html.haml11
-rw-r--r--app/views/layouts/nav/_project.html.haml52
-rw-r--r--app/views/layouts/project.html.haml4
-rw-r--r--app/views/notify/build_fail_email.html.haml4
-rw-r--r--app/views/notify/build_fail_email.text.erb6
-rw-r--r--app/views/notify/build_success_email.html.haml4
-rw-r--r--app/views/notify/build_success_email.text.erb6
-rw-r--r--app/views/profiles/accounts/show.html.haml25
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml39
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml69
-rw-r--r--app/views/projects/_home_panel.html.haml10
-rw-r--r--app/views/projects/_md_preview.html.haml6
-rw-r--r--app/views/projects/_merge_request_settings.html.haml11
-rw-r--r--app/views/projects/artifacts/browse.html.haml1
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/destroy.js.haml1
-rw-r--r--app/views/projects/branches/index.html.haml60
-rw-r--r--app/views/projects/builds/_header.html.haml16
-rw-r--r--app/views/projects/builds/_sidebar.html.haml93
-rw-r--r--app/views/projects/builds/_user.html.haml4
-rw-r--r--app/views/projects/builds/index.html.haml119
-rw-r--r--app/views/projects/builds/show.html.haml191
-rw-r--r--app/views/projects/buttons/_notifications.html.haml4
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml (renamed from app/views/projects/ci/commits/_commit.html.haml)44
-rw-r--r--app/views/projects/commit/_builds.html.haml4
-rw-r--r--app/views/projects/commit/_ci_commit.html.haml52
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/commit/_pipeline.html.haml52
-rw-r--r--app/views/projects/commits/_head.html.haml44
-rw-r--r--app/views/projects/commits/show.html.haml53
-rw-r--r--app/views/projects/compare/index.html.haml26
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/graphs/ci/_overall.haml2
-rw-r--r--app/views/projects/hooks/index.html.haml92
-rw-r--r--app/views/projects/issues/_head.html.haml25
-rw-r--r--app/views/projects/issues/_issue.html.haml4
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml6
-rw-r--r--app/views/projects/issues/_new_branch.html.haml12
-rw-r--r--app/views/projects/issues/_related_branches.html.haml6
-rw-r--r--app/views/projects/issues/index.html.haml32
-rw-r--r--app/views/projects/issues/show.html.haml12
-rw-r--r--app/views/projects/labels/_label.html.haml62
-rw-r--r--app/views/projects/labels/index.html.haml52
-rw-r--r--app/views/projects/merge_requests/_head.html.haml5
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml6
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml1
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml10
-rw-r--r--app/views/projects/merge_requests/index.html.haml27
-rw-r--r--app/views/projects/merge_requests/merge.js.haml3
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml6
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml32
-rw-r--r--app/views/projects/merge_requests/widget/open/_build_failed.html.haml6
-rw-r--r--app/views/projects/merge_requests/widget/open/_conflicts.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_sha_mismatch.html.haml6
-rw-r--r--app/views/projects/milestones/_form.html.haml3
-rw-r--r--app/views/projects/milestones/index.html.haml30
-rw-r--r--app/views/projects/milestones/show.html.haml4
-rw-r--r--app/views/projects/network/_head.html.haml13
-rw-r--r--app/views/projects/network/show.html.haml27
-rw-r--r--app/views/projects/notes/_note.html.haml13
-rw-r--r--app/views/projects/pipelines/_head.html.haml27
-rw-r--r--app/views/projects/pipelines/index.html.haml110
-rw-r--r--app/views/projects/pipelines/show.html.haml2
-rw-r--r--app/views/projects/project_members/_group_members.html.haml1
-rw-r--r--app/views/projects/project_members/_project_member.html.haml6
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tags/_download.html.haml7
-rw-r--r--app/views/projects/tags/_tag.html.haml4
-rw-r--r--app/views/projects/tags/destroy.js.haml1
-rw-r--r--app/views/projects/tags/index.html.haml44
-rw-r--r--app/views/projects/tree/show.html.haml20
-rw-r--r--app/views/projects/wikis/_main_links.html.haml6
-rw-r--r--app/views/projects/wikis/_nav.html.haml1
-rw-r--r--app/views/projects/wikis/edit.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/shared/_issues.html.haml2
-rw-r--r--app/views/shared/_label_row.html.haml12
-rw-r--r--app/views/shared/_labels_row.html.haml11
-rw-r--r--app/views/shared/_new_project_item_select.html.haml3
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/groups/_group.html.haml6
-rw-r--r--app/views/shared/icons/_activity.svg16
-rw-r--r--app/views/shared/icons/_commits.svg10
-rw-r--r--app/views/shared/icons/_contributionanalytics.svg17
-rw-r--r--app/views/shared/icons/_files.svg17
-rw-r--r--app/views/shared/icons/_group.svg18
-rw-r--r--app/views/shared/icons/_issues.svg13
-rw-r--r--app/views/shared/icons/_members.svg13
-rw-r--r--app/views/shared/icons/_milestones.svg15
-rw-r--r--app/views/shared/icons/_mr.svg13
-rw-r--r--app/views/shared/icons/_pipelines.svg10
-rw-r--r--app/views/shared/icons/_project.svg10
-rw-r--r--app/views/shared/icons/_wiki.svg10
-rw-r--r--app/views/shared/issuable/_filter.html.haml6
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml12
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml33
-rw-r--r--app/views/shared/milestones/_milestone.html.haml8
-rw-r--r--app/views/shared/web_hooks/_form.html.haml91
-rw-r--r--app/views/sherlock/queries/_backtrace.html.haml6
-rw-r--r--app/views/sherlock/queries/_general.html.haml8
-rw-r--r--app/views/u2f/_authenticate.html.haml28
-rw-r--r--app/views/u2f/_register.html.haml31
-rw-r--r--app/views/users/show.html.haml4
-rw-r--r--app/views/votes/_votes_block.html.haml30
-rw-r--r--app/workers/repository_fork_worker.rb6
-rw-r--r--app/workers/repository_import_worker.rb3
-rw-r--r--config/dependency_decisions.yml183
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/doorkeeper.rb2
-rw-r--r--config/initializers/inflections.rb4
-rw-r--r--config/initializers/metrics.rb7
-rw-r--r--config/license_finder.yml2
-rw-r--r--config/mail_room.yml2
-rw-r--r--config/routes.rb42
-rw-r--r--db/fixtures/development/14_builds.rb2
-rw-r--r--db/fixtures/production/001_admin.rb12
-rw-r--r--db/migrate/20121220064453_init_schema.rb1
-rw-r--r--db/migrate/20130102143055_rename_owner_to_creator_for_project.rb1
-rw-r--r--db/migrate/20130110172407_add_public_to_project.rb1
-rw-r--r--db/migrate/20130123114545_add_issues_tracker_to_project.rb1
-rw-r--r--db/migrate/20130125090214_add_user_permissions.rb1
-rw-r--r--db/migrate/20130131070232_remove_private_flag_from_project.rb1
-rw-r--r--db/migrate/20130206084024_add_description_to_namsespace.rb1
-rw-r--r--db/migrate/20130207104426_add_description_to_teams.rb1
-rw-r--r--db/migrate/20130211085435_add_issues_tracker_id_to_project.rb1
-rw-r--r--db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb1
-rw-r--r--db/migrate/20130218140952_add_state_to_issue.rb1
-rw-r--r--db/migrate/20130218141038_add_state_to_merge_request.rb1
-rw-r--r--db/migrate/20130218141117_add_state_to_milestone.rb1
-rw-r--r--db/migrate/20130218141258_convert_closed_to_state_in_issue.rb1
-rw-r--r--db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb1
-rw-r--r--db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb1
-rw-r--r--db/migrate/20130218141444_remove_merged_from_merge_request.rb1
-rw-r--r--db/migrate/20130218141507_remove_closed_from_issue.rb1
-rw-r--r--db/migrate/20130218141536_remove_closed_from_merge_request.rb1
-rw-r--r--db/migrate/20130218141554_remove_closed_from_milestone.rb1
-rw-r--r--db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb1
-rw-r--r--db/migrate/20130220125544_convert_merge_status_in_merge_request.rb1
-rw-r--r--db/migrate/20130220125545_remove_merge_status_from_merge_request.rb1
-rw-r--r--db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb1
-rw-r--r--db/migrate/20130304104623_add_state_to_user.rb1
-rw-r--r--db/migrate/20130304104740_convert_blocked_to_state.rb1
-rw-r--r--db/migrate/20130304105317_remove_blocked_from_user.rb1
-rw-r--r--db/migrate/20130315124931_user_color_scheme.rb1
-rw-r--r--db/migrate/20130318212250_add_snippets_to_features.rb1
-rw-r--r--db/migrate/20130319214458_create_forked_project_links.rb1
-rw-r--r--db/migrate/20130323174317_add_private_to_snippets.rb1
-rw-r--r--db/migrate/20130324151736_add_type_to_snippets.rb1
-rw-r--r--db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb1
-rw-r--r--db/migrate/20130324203535_add_type_value_for_snippets.rb1
-rw-r--r--db/migrate/20130325173941_add_notification_level_to_user.rb1
-rw-r--r--db/migrate/20130326142630_add_index_to_users_authentication_token.rb1
-rw-r--r--db/migrate/20130403003950_add_last_activity_column_into_project.rb1
-rw-r--r--db/migrate/20130404164628_add_notification_level_to_user_project.rb1
-rw-r--r--db/migrate/20130410175022_remove_wiki_table.rb1
-rw-r--r--db/migrate/20130419190306_allow_merges_for_forks.rb1
-rw-r--r--db/migrate/20130506085413_add_type_to_key.rb1
-rw-r--r--db/migrate/20130506090604_create_deploy_keys_projects.rb1
-rw-r--r--db/migrate/20130506095501_remove_project_id_from_key.rb1
-rw-r--r--db/migrate/20130522141856_add_more_fields_to_service.rb1
-rw-r--r--db/migrate/20130528184641_add_system_to_notes.rb1
-rw-r--r--db/migrate/20130611210815_increase_snippet_text_column_size.rb1
-rw-r--r--db/migrate/20130613165816_add_password_expires_at_to_users.rb1
-rw-r--r--db/migrate/20130613173246_add_created_by_id_to_user.rb1
-rw-r--r--db/migrate/20130614132337_add_improted_to_project.rb1
-rw-r--r--db/migrate/20130617095603_create_users_groups.rb1
-rw-r--r--db/migrate/20130621195223_add_notification_level_to_user_group.rb1
-rw-r--r--db/migrate/20130622115340_add_more_db_index.rb1
-rw-r--r--db/migrate/20130624162710_add_fingerprint_to_key.rb1
-rw-r--r--db/migrate/20130711063759_create_project_group_links.rb1
-rw-r--r--db/migrate/20130804151314_add_st_diff_to_note.rb1
-rw-r--r--db/migrate/20130809124851_add_permission_check_to_user.rb1
-rw-r--r--db/migrate/20130812143708_add_import_url_to_project.rb1
-rw-r--r--db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb1
-rw-r--r--db/migrate/20130820102832_add_access_to_project_group_link.rb1
-rw-r--r--db/migrate/20130821090530_remove_deprecated_tables.rb1
-rw-r--r--db/migrate/20130821090531_add_internal_ids_to_milestones.rb1
-rw-r--r--db/migrate/20130909132950_add_description_to_merge_request.rb1
-rw-r--r--db/migrate/20130926081215_change_owner_id_for_group.rb1
-rw-r--r--db/migrate/20131005191208_add_avatar_to_users.rb1
-rw-r--r--db/migrate/20131009115346_add_confirmable_to_users.rb1
-rw-r--r--db/migrate/20131106151520_remove_default_branch.rb1
-rw-r--r--db/migrate/20131112114325_create_broadcast_messages.rb1
-rw-r--r--db/migrate/20131112220935_add_visibility_level_to_projects.rb1
-rw-r--r--db/migrate/20131129154016_add_archived_to_projects.rb1
-rw-r--r--db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb1
-rw-r--r--db/migrate/20131202192556_add_event_fields_for_web_hook.rb1
-rw-r--r--db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb1
-rw-r--r--db/migrate/20131217102743_add_recipients_to_service.rb1
-rw-r--r--db/migrate/20140116231608_add_website_url_to_users.rb1
-rw-r--r--db/migrate/20140122112253_create_merge_request_diffs.rb1
-rw-r--r--db/migrate/20140122114406_migrate_mr_diffs.rb1
-rw-r--r--db/migrate/20140122122549_remove_m_rdiff_fields.rb1
-rw-r--r--db/migrate/20140125162722_add_avatar_to_projects.rb1
-rw-r--r--db/migrate/20140127170938_add_group_avatars.rb1
-rw-r--r--db/migrate/20140209025651_create_emails.rb1
-rw-r--r--db/migrate/20140214102325_add_api_key_to_services.rb1
-rw-r--r--db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb1
-rw-r--r--db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb1
-rw-r--r--db/migrate/20140312145357_add_import_status_to_project.rb1
-rw-r--r--db/migrate/20140313092127_migrate_already_imported_projects.rb1
-rw-r--r--db/migrate/20140407135544_fix_namespaces.rb1
-rw-r--r--db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb1
-rw-r--r--db/migrate/20140415124820_limits_to_mysql.rb1
-rw-r--r--db/migrate/20140416074002_add_index_on_iid.rb1
-rw-r--r--db/migrate/20140416185734_index_on_current_sign_in_at.rb1
-rw-r--r--db/migrate/20140428105831_add_notes_index_updated_at.rb1
-rw-r--r--db/migrate/20140502115131_add_repo_size_to_db.rb1
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb1
-rw-r--r--db/migrate/20140611135229_add_position_to_merge_request.rb1
-rw-r--r--db/migrate/20140625115202_create_users_star_projects.rb1
-rw-r--r--db/migrate/20140729134820_create_labels.rb1
-rw-r--r--db/migrate/20140729140420_create_label_links.rb1
-rw-r--r--db/migrate/20140729145339_migrate_project_tags.rb1
-rw-r--r--db/migrate/20140729152420_migrate_taggable_labels.rb1
-rw-r--r--db/migrate/20140730111702_add_index_to_labels.rb1
-rw-r--r--db/migrate/20140903115954_migrate_to_new_shell.rb1
-rw-r--r--db/migrate/20140907220153_serialize_service_properties.rb1
-rw-r--r--db/migrate/20140914113604_add_members_table.rb1
-rw-r--r--db/migrate/20140914145549_migrate_to_new_members_model.rb1
-rw-r--r--db/migrate/20140914173417_remove_old_member_tables.rb1
-rw-r--r--db/migrate/20141006143943_move_slack_service_to_webhook.rb1
-rw-r--r--db/migrate/20141007100818_add_visibility_level_to_snippet.rb1
-rw-r--r--db/migrate/20141118150935_add_audit_event.rb1
-rw-r--r--db/migrate/20141121133009_add_timestamps_to_members.rb1
-rw-r--r--db/migrate/20141121161704_add_identity_table.rb1
-rw-r--r--db/migrate/20141205134006_add_locked_at_to_merge_request.rb1
-rw-r--r--db/migrate/20141216155758_create_doorkeeper_tables.rb1
-rw-r--r--db/migrate/20141217125223_add_owner_to_application.rb1
-rw-r--r--db/migrate/20141223135007_add_import_data_to_project_table.rb1
-rw-r--r--db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb1
-rw-r--r--db/migrate/20150108073740_create_application_settings.rb1
-rw-r--r--db/migrate/20150116234544_add_home_page_url_for_application_settings.rb1
-rw-r--r--db/migrate/20150116234545_add_gitlab_access_token_to_user.rb1
-rw-r--r--db/migrate/20150125163100_add_default_branch_protection_setting.rb1
-rw-r--r--db/migrate/20150205211843_add_timestamps_to_identities.rb1
-rw-r--r--db/migrate/20150206181414_add_index_to_created_at.rb1
-rw-r--r--db/migrate/20150206222854_add_notification_email_to_user.rb1
-rw-r--r--db/migrate/20150209222013_add_missing_index.rb1
-rw-r--r--db/migrate/20150211172122_add_template_to_service.rb1
-rw-r--r--db/migrate/20150211174341_allow_null_in_services_project_id.rb1
-rw-r--r--db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20150213114800_add_hide_no_password_to_user.rb1
-rw-r--r--db/migrate/20150213121042_add_password_automatically_set_to_user.rb1
-rw-r--r--db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb1
-rw-r--r--db/migrate/20150219004514_add_events_to_services.rb1
-rw-r--r--db/migrate/20150223022001_set_missing_last_activity_at.rb1
-rw-r--r--db/migrate/20150225065047_add_note_events_to_services.rb1
-rw-r--r--db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb1
-rw-r--r--db/migrate/20150306023106_fix_namespace_duplication.rb1
-rw-r--r--db/migrate/20150306023112_add_unique_index_to_namespace.rb1
-rw-r--r--db/migrate/20150310194358_add_version_check_to_application_settings.rb1
-rw-r--r--db/migrate/20150313012111_create_subscriptions_table.rb1
-rw-r--r--db/migrate/20150320234437_add_location_to_user.rb1
-rw-r--r--db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb1
-rw-r--r--db/migrate/20150327122227_add_public_to_key.rb1
-rw-r--r--db/migrate/20150327150017_add_import_data_to_project.rb1
-rw-r--r--db/migrate/20150327223628_add_devise_two_factor_to_users.rb1
-rw-r--r--db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb1
-rw-r--r--db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb1
-rw-r--r--db/migrate/20150406133311_add_invite_data_to_member.rb1
-rw-r--r--db/migrate/20150411000035_fix_identities.rb1
-rw-r--r--db/migrate/20150411180045_rename_buildbox_service.rb1
-rw-r--r--db/migrate/20150413192223_add_public_email_to_users.rb1
-rw-r--r--db/migrate/20150417121913_create_project_import_data.rb1
-rw-r--r--db/migrate/20150417122318_remove_import_data_from_project.rb1
-rw-r--r--db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb1
-rw-r--r--db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb1
-rw-r--r--db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb1
-rw-r--r--db/migrate/20150425164647_remove_duplicate_tags.rb1
-rw-r--r--db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb1
-rw-r--r--db/migrate/20150425164649_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb1
-rw-r--r--db/migrate/20150425164650_add_missing_taggable_index.acts_as_taggable_on_engine.rb1
-rw-r--r--db/migrate/20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb1
-rw-r--r--db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb1
-rw-r--r--db/migrate/20150429002313_remove_abandoned_group_members_records.rb1
-rw-r--r--db/migrate/20150502064022_add_restricted_signup_domains_to_application_settings.rb1
-rw-r--r--db/migrate/20150509180749_convert_legacy_reference_notes.rb1
-rw-r--r--db/migrate/20150516060434_add_note_events_to_web_hooks.rb1
-rw-r--r--db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb1
-rw-r--r--db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb1
-rw-r--r--db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb1
-rw-r--r--db/migrate/20150610065936_add_dashboard_to_users.rb1
-rw-r--r--db/migrate/20150620233230_add_default_otp_required_for_login_value.rb1
-rw-r--r--db/migrate/20150713160110_add_project_view_to_users.rb1
-rw-r--r--db/migrate/20150717130904_add_commits_count_to_project.rb1
-rw-r--r--db/migrate/20150730122406_add_updated_by_to_issuables_and_notes.rb1
-rw-r--r--db/migrate/20150806104937_create_abuse_reports.rb1
-rw-r--r--db/migrate/20150812080800_add_settings_import_sources.rb1
-rw-r--r--db/migrate/20150814065925_remove_oauth_tokens_from_users.rb1
-rw-r--r--db/migrate/20150817163600_deduplicate_user_identities.rb1
-rw-r--r--db/migrate/20150818213832_add_sent_notifications.rb1
-rw-r--r--db/migrate/20150824002011_add_enable_ssl_verification.rb1
-rw-r--r--db/migrate/20150826001931_add_ci_tables.rb1
-rw-r--r--db/migrate/20150902001023_add_template_to_label.rb1
-rw-r--r--db/migrate/20150914215247_add_ci_tags.rb1
-rw-r--r--db/migrate/20150915001905_enable_ssl_verification_by_default.rb1
-rw-r--r--db/migrate/20150916000405_enable_ssl_verification_for_web_hooks.rb1
-rw-r--r--db/migrate/20150916114643_add_help_page_text_to_application_settings.rb1
-rw-r--r--db/migrate/20150916145038_add_index_for_committed_at_and_id.rb1
-rw-r--r--db/migrate/20150918084513_add_ci_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20150918161719_remove_invalid_milestones_from_merge_requests.rb1
-rw-r--r--db/migrate/20150920010715_add_consumed_timestep_to_users.rb1
-rw-r--r--db/migrate/20150920161119_add_line_code_to_sent_notification.rb1
-rw-r--r--db/migrate/20150924125150_add_project_id_to_ci_commit.rb1
-rw-r--r--db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb1
-rw-r--r--db/migrate/20150930001110_merge_request_error_field.rb1
-rw-r--r--db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb1
-rw-r--r--db/migrate/20150930110012_add_group_share_lock.rb1
-rw-r--r--db/migrate/20151002112914_add_stage_idx_to_builds.rb1
-rw-r--r--db/migrate/20151002121400_add_index_for_builds.rb1
-rw-r--r--db/migrate/20151002122929_add_ref_and_tag_to_builds.rb1
-rw-r--r--db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb1
-rw-r--r--db/migrate/20151005075649_add_user_id_to_build.rb1
-rw-r--r--db/migrate/20151005150751_add_layout_option_for_users.rb1
-rw-r--r--db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb1
-rw-r--r--db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb1
-rw-r--r--db/migrate/20151008110232_add_users_lower_username_email_indexes.rb1
-rw-r--r--db/migrate/20151008123042_add_type_and_description_to_builds.rb1
-rw-r--r--db/migrate/20151008130321_migrate_name_to_description_for_builds.rb1
-rw-r--r--db/migrate/20151008143519_add_admin_notification_email_setting.rb1
-rw-r--r--db/migrate/20151012173029_set_jira_service_api_url.rb1
-rw-r--r--db/migrate/20151013092124_add_artifacts_file_to_builds.rb1
-rw-r--r--db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb1
-rw-r--r--db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb1
-rw-r--r--db/migrate/20151016195706_add_notes_line_code_index.rb1
-rw-r--r--db/migrate/20151019111551_fix_build_tags.rb1
-rw-r--r--db/migrate/20151019111703_fail_build_without_names.rb1
-rw-r--r--db/migrate/20151020145526_add_services_template_index.rb1
-rw-r--r--db/migrate/20151020173516_ci_limits_to_mysql.rb1
-rw-r--r--db/migrate/20151020173906_add_ci_builds_index_for_status.rb1
-rw-r--r--db/migrate/20151023112551_fail_build_with_empty_name.rb1
-rw-r--r--db/migrate/20151023144219_remove_satellites.rb1
-rw-r--r--db/migrate/20151026182941_add_project_path_index.rb1
-rw-r--r--db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb1
-rw-r--r--db/migrate/20151103001141_add_public_to_group.rb1
-rw-r--r--db/migrate/20151103133339_add_shared_runners_setting.rb1
-rw-r--r--db/migrate/20151103134857_create_lfs_objects.rb1
-rw-r--r--db/migrate/20151103134958_create_lfs_objects_projects.rb1
-rw-r--r--db/migrate/20151104105513_add_file_to_lfs_objects.rb1
-rw-r--r--db/migrate/20151105094515_create_releases.rb1
-rw-r--r--db/migrate/20151106000015_add_is_award_to_notes.rb1
-rw-r--r--db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb1
-rw-r--r--db/migrate/20151109134526_add_issues_state_index.rb1
-rw-r--r--db/migrate/20151109134916_add_projects_visibility_level_index.rb1
-rw-r--r--db/migrate/20151110125604_add_import_error_to_project.rb1
-rw-r--r--db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb1
-rw-r--r--db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb1
-rw-r--r--db/migrate/20151118162244_add_projects_public_index.rb1
-rw-r--r--db/migrate/20151201203948_raise_hook_url_limit.rb1
-rw-r--r--db/migrate/20151203162133_add_hide_project_limit_to_users.rb1
-rw-r--r--db/migrate/20151203162134_add_build_events_to_services.rb1
-rw-r--r--db/migrate/20151209144329_migrate_ci_web_hooks.rb1
-rw-r--r--db/migrate/20151209145909_migrate_ci_emails.rb1
-rw-r--r--db/migrate/20151210030143_add_unlock_token_to_user.rb1
-rw-r--r--db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb1
-rw-r--r--db/migrate/20151210125232_migrate_ci_slack_service.rb1
-rw-r--r--db/migrate/20151210125927_migrate_ci_hip_chat_service.rb1
-rw-r--r--db/migrate/20151210125928_add_ci_to_project.rb1
-rw-r--r--db/migrate/20151210125929_add_project_id_to_ci.rb1
-rw-r--r--db/migrate/20151210125930_migrate_ci_to_project.rb1
-rw-r--r--db/migrate/20151210125931_add_index_to_ci_tables.rb1
-rw-r--r--db/migrate/20151210125932_drop_null_for_ci_tables.rb1
-rw-r--r--db/migrate/20151218154042_add_tfa_to_application_settings.rb1
-rw-r--r--db/migrate/20151221234414_add_tfa_additional_fields.rb1
-rw-r--r--db/migrate/20151224123230_rename_emojis.rb1
-rw-r--r--db/migrate/20151228111122_remove_public_from_namespace.rb1
-rw-r--r--db/migrate/20151228150906_influxdb_settings.rb1
-rw-r--r--db/migrate/20151228175719_add_recaptcha_to_application_settings.rb1
-rw-r--r--db/migrate/20151229102248_influxdb_udp_port_setting.rb1
-rw-r--r--db/migrate/20151229112614_influxdb_remote_database_setting.rb1
-rw-r--r--db/migrate/20151230132518_add_artifacts_metadata_to_ci_build.rb1
-rw-r--r--db/migrate/20151231152326_add_akismet_to_application_settings.rb1
-rw-r--r--db/migrate/20151231202530_remove_alert_type_from_broadcast_messages.rb1
-rw-r--r--db/migrate/20160106162223_add_index_milestones_title.rb1
-rw-r--r--db/migrate/20160106164438_remove_influxdb_credentials.rb1
-rw-r--r--db/migrate/20160109054846_create_spam_logs.rb1
-rw-r--r--db/migrate/20160113111034_add_metrics_sample_interval.rb1
-rw-r--r--db/migrate/20160118155830_add_sentry_to_application_settings.rb1
-rw-r--r--db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb1
-rw-r--r--db/migrate/20160119111158_add_services_category.rb1
-rw-r--r--db/migrate/20160119112418_add_services_default.rb1
-rw-r--r--db/migrate/20160119145451_add_ldap_email_to_users.rb1
-rw-r--r--db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb1
-rw-r--r--db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb1
-rw-r--r--db/migrate/20160122185421_add_pending_delete_to_project.rb1
-rw-r--r--db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb1
-rw-r--r--db/migrate/20160128233227_change_lfs_objects_size_column.rb1
-rw-r--r--db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb1
-rw-r--r--db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb1
-rw-r--r--db/migrate/20160202091601_add_erasable_to_ci_build.rb1
-rw-r--r--db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb1
-rw-r--r--db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb1
-rw-r--r--db/migrate/20160209130428_add_index_to_snippet.rb1
-rw-r--r--db/migrate/20160212123307_create_tasks.rb1
-rw-r--r--db/migrate/20160217100506_add_description_to_label.rb1
-rw-r--r--db/migrate/20160217174422_add_note_to_tasks.rb1
-rw-r--r--db/migrate/20160220123949_rename_tasks_to_todos.rb1
-rw-r--r--db/migrate/20160222153918_create_appearances_ce.rb1
-rw-r--r--db/migrate/20160223192159_add_confidential_to_issues.rb1
-rw-r--r--db/migrate/20160225090018_add_delete_at_to_issues.rb1
-rw-r--r--db/migrate/20160225101956_add_delete_at_to_merge_requests.rb1
-rw-r--r--db/migrate/20160226114608_add_trigram_indexes_for_searching.rb1
-rw-r--r--db/migrate/20160227120001_add_event_field_for_web_hook.rb1
-rw-r--r--db/migrate/20160227120047_add_event_to_services.rb1
-rw-r--r--db/migrate/20160229193553_add_main_language_to_repository.rb1
-rw-r--r--db/migrate/20160301124843_add_visibility_level_to_groups.rb1
-rw-r--r--db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb1
-rw-r--r--db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb1
-rw-r--r--db/migrate/20160305220806_remove_expires_at_from_snippets.rb1
-rw-r--r--db/migrate/20160307221555_disallow_blank_line_code_on_note.rb1
-rw-r--r--db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb1
-rw-r--r--db/migrate/20160309140734_fix_todos.rb1
-rw-r--r--db/migrate/20160310124959_add_due_date_to_issues.rb1
-rw-r--r--db/migrate/20160310185910_add_external_flag_to_users.rb1
-rw-r--r--db/migrate/20160314094147_add_priority_to_label.rb7
-rw-r--r--db/migrate/20160314143402_projects_add_pushes_since_gc.rb1
-rw-r--r--db/migrate/20160315135439_project_add_repository_check.rb1
-rw-r--r--db/migrate/20160316123110_ci_runners_token_index.rb1
-rw-r--r--db/migrate/20160316192622_change_target_id_to_null_on_todos.rb1
-rw-r--r--db/migrate/20160316204731_add_commit_id_to_todos.rb1
-rw-r--r--db/migrate/20160317092222_add_moved_to_to_issue.rb1
-rw-r--r--db/migrate/20160320204112_index_namespaces_on_visibility_level.rb1
-rw-r--r--db/migrate/20160324020319_remove_todos_for_deleted_issues.rb1
-rw-r--r--db/migrate/20160328112808_create_notification_settings.rb1
-rw-r--r--db/migrate/20160328115649_migrate_new_notification_setting.rb1
-rw-r--r--db/migrate/20160328121138_add_notification_setting_index.rb1
-rw-r--r--db/migrate/20160329144452_add_index_on_pending_delete_projects.rb1
-rw-r--r--db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb1
-rw-r--r--db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb1
-rw-r--r--db/migrate/20160407120251_add_images_enabled_for_project.rb1
-rw-r--r--db/migrate/20160412140240_add_repository_checks_enabled_setting.rb1
-rw-r--r--db/migrate/20160412173416_add_fields_to_ci_commit.rb1
-rw-r--r--db/migrate/20160412173417_update_ci_commit.rb1
-rw-r--r--db/migrate/20160412173418_add_ci_commit_indexes.rb1
-rw-r--r--db/migrate/20160413115152_add_token_to_web_hooks.rb1
-rw-r--r--db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb1
-rw-r--r--db/migrate/20160416180807_add_award_emoji.rb15
-rw-r--r--db/migrate/20160416182152_convert_award_note_to_emoji_award.rb10
-rw-r--r--db/migrate/20160416190505_remove_note_is_award.rb6
-rw-r--r--db/migrate/20160419120017_add_metrics_packet_size.rb1
-rw-r--r--db/migrate/20160419122101_add_only_allow_merge_if_build_succeeds_to_projects.rb15
-rw-r--r--db/migrate/20160421130527_disable_repository_checks.rb1
-rw-r--r--db/migrate/20160425045124_create_u2f_registrations.rb14
-rw-r--r--db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb1
-rw-r--r--db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb1
-rw-r--r--db/migrate/20160508194200_remove_wall_enabled_from_projects.rb1
-rw-r--r--db/migrate/20160508215820_add_type_to_notes.rb1
-rw-r--r--db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb1
-rw-r--r--db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb1
-rw-r--r--db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb1
-rw-r--r--db/migrate/20160525205328_remove_main_language_from_projects.rb1
-rw-r--r--db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb1
-rw-r--r--db/migrate/20160528043124_add_users_state_index.rb1
-rw-r--r--db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb1
-rw-r--r--db/migrate/20160603075128_add_has_external_issue_tracker_to_projects.rb10
-rw-r--r--db/migrate/20160603180330_remove_duplicated_notification_settings.rb33
-rw-r--r--db/migrate/20160603182247_add_index_to_notification_settings.rb10
-rw-r--r--db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb6
-rw-r--r--db/migrate/limits_to_mysql.rb1
-rw-r--r--db/schema.rb70
-rw-r--r--doc/administration/container_registry.md4
-rw-r--r--doc/api/builds.md24
-rw-r--r--doc/api/merge_requests.md11
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/licensing.md93
-rw-r--r--doc/development/ui_guide.md22
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/profile/2fa_u2f_authenticate.pngbin0 -> 54413 bytes
-rw-r--r--doc/profile/2fa_u2f_register.pngbin0 -> 112414 bytes
-rw-r--r--doc/profile/two_factor_authentication.md63
-rw-r--r--doc/update/8.8-to-8.9.md162
-rw-r--r--doc/workflow/merge_requests.md11
-rw-r--r--doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.pngbin0 -> 17552 bytes
-rw-r--r--features/project/active_tab.feature49
-rw-r--r--features/project/builds/summary.feature1
-rw-r--r--features/project/issues/issues.feature7
-rw-r--r--features/project/merge_requests.feature14
-rw-r--r--features/project/shortcuts.feature8
-rw-r--r--features/steps/dashboard/todos.rb2
-rw-r--r--features/steps/project/active_tab.rb20
-rw-r--r--features/steps/project/builds/artifacts.rb4
-rw-r--r--features/steps/project/builds/summary.rb4
-rw-r--r--features/steps/project/commits/commits.rb6
-rw-r--r--features/steps/project/issues/filter_labels.rb2
-rw-r--r--features/steps/project/issues/issues.rb12
-rw-r--r--features/steps/project/issues/labels.rb12
-rw-r--r--features/steps/project/labels.rb2
-rw-r--r--features/steps/project/merge_requests.rb14
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/project_find_file.rb4
-rw-r--r--features/steps/project/wiki.rb4
-rw-r--r--features/steps/shared/active_tab.rb4
-rw-r--r--features/steps/shared/builds.rb6
-rw-r--r--features/steps/shared/issuable.rb16
-rw-r--r--features/steps/shared/project.rb2
-rw-r--r--features/steps/shared/project_tab.rb16
-rw-r--r--features/support/env.rb5
-rw-r--r--lib/api/builds.rb2
-rw-r--r--lib/api/commit_statuses.rb12
-rw-r--r--lib/api/entities.rb11
-rw-r--r--lib/api/helpers.rb10
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb14
-rw-r--r--lib/api/repositories.rb10
-rw-r--r--lib/api/session.rb3
-rw-r--r--lib/award_emoji.rb84
-rw-r--r--lib/backup/database.rb4
-rw-r--r--lib/backup/manager.rb30
-rw-r--r--lib/backup/repository.rb26
-rw-r--r--lib/banzai/filter/external_link_filter.rb1
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb12
-rw-r--r--lib/banzai/filter/reference_filter.rb7
-rw-r--r--lib/banzai/filter/user_reference_filter.rb29
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb32
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb40
-rw-r--r--lib/ci/api/entities.rb2
-rw-r--r--lib/ci/charts.rb2
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb10
-rw-r--r--lib/gitlab/auth.rb95
-rw-r--r--lib/gitlab/auth/ip_rate_limiter.rb42
-rw-r--r--lib/gitlab/award_emoji.rb84
-rw-r--r--lib/gitlab/backend/grack_auth.rb55
-rw-r--r--lib/gitlab/build_data_builder.rb2
-rw-r--r--lib/gitlab/ci/config.rb16
-rw-r--r--lib/gitlab/ci/config/loader.rb25
-rw-r--r--lib/gitlab/current_settings.rb5
-rw-r--r--lib/gitlab/database.rb14
-rw-r--r--lib/gitlab/database/migration_helpers.rb6
-rw-r--r--lib/gitlab/github_import/base_formatter.rb4
-rw-r--r--lib/gitlab/github_import/client.rb35
-rw-r--r--lib/gitlab/github_import/comment_formatter.rb5
-rw-r--r--lib/gitlab/github_import/hook_formatter.rb23
-rw-r--r--lib/gitlab/github_import/importer.rb93
-rw-r--r--lib/gitlab/github_import/issue_formatter.rb4
-rw-r--r--lib/gitlab/github_import/label_formatter.rb4
-rw-r--r--lib/gitlab/github_import/milestone_formatter.rb4
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb4
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/key_fingerprint.rb6
-rw-r--r--lib/gitlab/ldap/config.rb1
-rw-r--r--lib/gitlab/o_auth/user.rb17
-rw-r--r--lib/gitlab/saml/user.rb4
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/workhorse.rb34
-rw-r--r--lib/tasks/gitlab/backup.rake80
-rw-r--r--lib/tasks/gitlab/check.rake178
-rw-r--r--lib/tasks/gitlab/cleanup.rake18
-rw-r--r--lib/tasks/gitlab/db.rake10
-rw-r--r--lib/tasks/gitlab/git.rake8
-rw-r--r--lib/tasks/gitlab/import.rake14
-rw-r--r--lib/tasks/gitlab/info.rake26
-rw-r--r--lib/tasks/gitlab/setup.rake2
-rw-r--r--lib/tasks/gitlab/shell.rake4
-rw-r--r--lib/tasks/gitlab/task_helpers.rake10
-rw-r--r--lib/tasks/gitlab/two_factor.rake8
-rw-r--r--lib/tasks/gitlab/update_commit_count.rake6
-rw-r--r--lib/tasks/gitlab/update_gitignore.rake4
-rw-r--r--lib/tasks/gitlab/web_hook.rake6
-rw-r--r--lib/tasks/migrate/migrate_iids.rake6
-rw-r--r--lib/tasks/spinach.rake2
-rwxr-xr-xscripts/merge-reports29
-rwxr-xr-xscripts/prepare_build.sh10
-rw-r--r--spec/controllers/groups_controller_spec.rb12
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb1
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb1
-rw-r--r--spec/controllers/import/github_controller_spec.rb1
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb1
-rw-r--r--spec/controllers/import/gitorious_controller_spec.rb1
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb1
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb29
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb14
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb4
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb16
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb53
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb95
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb36
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb5
-rw-r--r--spec/controllers/sessions_controller_spec.rb26
-rw-r--r--spec/factories/award_emoji.rb12
-rw-r--r--spec/factories/ci/builds.rb4
-rw-r--r--spec/factories/ci/commits.rb10
-rw-r--r--spec/factories/commit_statuses.rb4
-rw-r--r--spec/factories/notes.rb6
-rw-r--r--spec/factories/u2f_registrations.rb8
-rw-r--r--spec/factories/users.rb14
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/features/admin/admin_builds_spec.rb26
-rw-r--r--spec/features/admin/admin_runners_spec.rb4
-rw-r--r--spec/features/admin/admin_users_spec.rb18
-rw-r--r--spec/features/builds_spec.rb169
-rw-r--r--spec/features/commits_spec.rb56
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb46
-rw-r--r--spec/features/issues/award_emoji_spec.rb2
-rw-r--r--spec/features/issues/award_spec.rb49
-rw-r--r--spec/features/issues/bulk_assigment_labels_spec.rb196
-rw-r--r--spec/features/issues/filter_by_labels_spec.rb50
-rw-r--r--spec/features/issues/filter_issues_spec.rb36
-rw-r--r--spec/features/issues/update_issues_spec.rb23
-rw-r--r--spec/features/issues_spec.rb22
-rw-r--r--spec/features/login_spec.rb26
-rw-r--r--spec/features/markdown_spec.rb17
-rw-r--r--spec/features/merge_requests/award_spec.rb49
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb6
-rw-r--r--spec/features/merge_requests/merge_when_build_succeeds_spec.rb8
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb105
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb25
-rw-r--r--spec/features/pipelines_spec.rb28
-rw-r--r--spec/features/projects/commit/builds_spec.rb6
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb87
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb115
-rw-r--r--spec/features/projects/shortcuts_spec.rb (renamed from spec/features/project/shortcuts_spec.rb)0
-rw-r--r--spec/features/security/project/public_access_spec.rb4
-rw-r--r--spec/features/todos/target_state_spec.rb2
-rw-r--r--spec/features/todos/todos_spec.rb17
-rw-r--r--spec/features/u2f_spec.rb239
-rw-r--r--spec/fixtures/markdown.md.erb2
-rw-r--r--spec/helpers/ci_status_helper_spec.rb4
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb3
-rw-r--r--spec/helpers/issues_helper_spec.rb11
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/javascripts/awards_handler_spec.js.coffee201
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js.coffee22
-rw-r--r--spec/javascripts/fixtures/awards_handler.html.haml52
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml2
-rw-r--r--spec/javascripts/fixtures/emoji_menu.coffee957
-rw-r--r--spec/javascripts/fixtures/u2f/authenticate.html.haml1
-rw-r--r--spec/javascripts/fixtures/u2f/register.html.haml1
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js12
-rw-r--r--spec/javascripts/new_branch_spec.js.coffee2
-rw-r--r--spec/javascripts/u2f/authenticate_spec.coffee52
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js.coffee15
-rw-r--r--spec/javascripts/u2f/register_spec.js.coffee57
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb45
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb19
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb85
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb108
-rw-r--r--spec/lib/ci/charts_spec.rb10
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb4
-rw-r--r--spec/lib/gitlab/auth_spec.rb56
-rw-r--r--spec/lib/gitlab/award_emoji_spec.rb (renamed from spec/lib/award_emoji_spec.rb)6
-rw-r--r--spec/lib/gitlab/backend/grack_auth_spec.rb209
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb26
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb4
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/loader_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb47
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb13
-rw-r--r--spec/lib/gitlab/database_spec.rb16
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/hook_formatter_spec.rb65
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb4
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb18
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/models/award_emoji_spec.rb30
-rw-r--r--spec/models/build_spec.rb54
-rw-r--r--spec/models/ci/commit_spec.rb403
-rw-r--r--spec/models/ci/pipeline_spec.rb403
-rw-r--r--spec/models/commit_status_spec.rb50
-rw-r--r--spec/models/concerns/awardable_spec.rb48
-rw-r--r--spec/models/concerns/issuable_spec.rb48
-rw-r--r--spec/models/generic_commit_status_spec.rb4
-rw-r--r--spec/models/issue_spec.rb17
-rw-r--r--spec/models/merge_request_spec.rb184
-rw-r--r--spec/models/note_spec.rb55
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb114
-rw-r--r--spec/models/service_spec.rb33
-rw-r--r--spec/models/user_spec.rb149
-rw-r--r--spec/requests/api/builds_spec.rb18
-rw-r--r--spec/requests/api/commit_statuses_spec.rb8
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/issues_spec.rb1
-rw-r--r--spec/requests/api/merge_requests_spec.rb29
-rw-r--r--spec/requests/api/triggers_spec.rb12
-rw-r--r--spec/requests/ci/api/builds_spec.rb48
-rw-r--r--spec/requests/ci/api/triggers_spec.rb12
-rw-r--r--spec/requests/git_http_spec.rb395
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/services/ci/create_builds_service_spec.rb4
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb6
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb4
-rw-r--r--spec/services/ci/register_build_service_spec.rb4
-rw-r--r--spec/services/create_commit_builds_service_spec.rb162
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb281
-rw-r--r--spec/services/issues/move_service_spec.rb5
-rw-r--r--spec/services/issues/update_service_spec.rb46
-rw-r--r--spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb8
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb32
-rw-r--r--spec/services/notes/create_service_spec.rb30
-rw-r--r--spec/services/projects/import_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb3
-rw-r--r--spec/services/todo_service_spec.rb47
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--spec/support/fake_u2f_device.rb36
-rw-r--r--spec/support/import_spec_helper.rb (renamed from spec/controllers/import/import_spec_helper.rb)2
-rw-r--r--spec/support/markdown_feature.rb4
-rw-r--r--spec/support/stub_gitlab_calls.rb8
-rw-r--r--spec/workers/post_receive_spec.rb12
-rw-r--r--vendor/assets/javascripts/task_list.js.coffee258
-rw-r--r--vendor/assets/javascripts/u2f.js748
889 files changed, 13105 insertions, 4516 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000..2e88b7aa0a9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,3 @@
+We’re closing our issue tracker on GitHub so we can focus on the GitLab.com project and respond to issues more quickly.
+
+We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). You can log into GitLab.com using your GitHub account.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..c3b04026440
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,3 @@
+Thank you for taking the time to contribute back to GitLab!
+
+Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account.
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85730e1b687..3dc48a89463 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,7 @@ image: "ruby:2.1"
services:
- mysql:latest
- - redis:latest
+ - redis:alpine
cache:
key: "ruby21"
@@ -13,229 +13,201 @@ variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
# retry tests only in CI environment
RSPEC_RETRY_RETRY_COUNT: "3"
+ RAILS_ENV: "test"
+ SIMPLECOV: "true"
+ USE_DB: "true"
+ USE_BUNDLE_INSTALL: "true"
before_script:
- source ./scripts/prepare_build.sh
- - ruby -v
- - which ruby
- - retry gem install bundler --no-ri --no-rdoc
- cp config/gitlab.yml.example config/gitlab.yml
- - touch log/application.log
- - touch log/test.log
- - retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
+ - bundle --version
+ - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"'
+ - retry gem install knapsack
+ - '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate'
stages:
+- prepare
- test
-- notifications
+- post-test
-spec:feature:
- stage: test
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
-
-spec:api:
- stage: test
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
-
-spec:models:
- stage: test
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
-
-spec:lib:
- stage: test
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
-
-spec:services:
- stage: test
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
-
-spec:other:
- stage: test
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
-
-spinach:project:half:
- stage: test
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
-
-spinach:project:rest:
- stage: test
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
-
-spinach:other:
- stage: test
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
-
-teaspoon:
- stage: test
- script:
- - RAILS_ENV=test bundle exec teaspoon
-
-rubocop:
- stage: test
- script:
- - bundle exec rubocop
-
-scss-lint:
- stage: test
- script:
- - bundle exec rake scss_lint
+# Prepare and merge knapsack tests
-brakeman:
- stage: test
- script:
- - bundle exec rake brakeman
+.knapsack-state: &knapsack-state
+ services: []
+ variables:
+ USE_DB: "false"
+ USE_BUNDLE_INSTALL: "false"
+ cache:
+ key: "knapsack"
+ paths:
+ - knapsack/
+ artifacts:
+ paths:
+ - knapsack/
-flog:
- stage: test
+knapsack:
+ <<: *knapsack-state
+ stage: prepare
script:
- - bundle exec rake flog
+ - mkdir -p knapsack/
+ - '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
+ - '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
-flay:
- stage: test
+update-knapsack:
+ <<: *knapsack-state
+ stage: post-test
script:
- - bundle exec rake flay
-
-bundler:audit:
- stage: test
+ - scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json
+ - scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json
+ - rm -f knapsack/*_node_*.json
only:
- master
- 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
+# Execute all testing suites
-spec:feature:ruby22:
+.rspec-knapsack: &rspec-knapsack
stage: test
- image: ruby:2.2
- only:
- - master
script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
- cache:
- key: "ruby22"
+ - bundle exec rake assets:precompile 2>/dev/null
+ - JOB_NAME=( $CI_BUILD_NAME )
+ - export CI_NODE_INDEX=${JOB_NAME[1]}
+ - export CI_NODE_TOTAL=${JOB_NAME[2]}
+ - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_GENERATE_REPORT=true
+ - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
+ - knapsack rspec
+ artifacts:
paths:
- - vendor
-
-spec:api:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
- cache:
- key: "ruby22"
- paths:
- - vendor
-
-spec:models:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
- cache:
- key: "ruby22"
- paths:
- - vendor
-
-spec:lib:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
- cache:
- key: "ruby22"
+ - knapsack/
+
+.spinach-knapsack: &spinach-knapsack
+ stage: test
+ script:
+ - bundle exec rake assets:precompile 2>/dev/null
+ - JOB_NAME=( $CI_BUILD_NAME )
+ - export CI_NODE_INDEX=${JOB_NAME[1]}
+ - export CI_NODE_TOTAL=${JOB_NAME[2]}
+ - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_GENERATE_REPORT=true
+ - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
+ - knapsack spinach "-r rerun"
+ # retry failed tests 3 times
+ - retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
+ artifacts:
paths:
- - vendor
-
-spec:services:ruby22:
- stage: test
- image: ruby:2.2
+ - knapsack/
+
+rspec 0 20: *rspec-knapsack
+rspec 1 20: *rspec-knapsack
+rspec 2 20: *rspec-knapsack
+rspec 3 20: *rspec-knapsack
+rspec 4 20: *rspec-knapsack
+rspec 5 20: *rspec-knapsack
+rspec 6 20: *rspec-knapsack
+rspec 7 20: *rspec-knapsack
+rspec 8 20: *rspec-knapsack
+rspec 9 20: *rspec-knapsack
+rspec 10 20: *rspec-knapsack
+rspec 11 20: *rspec-knapsack
+rspec 12 20: *rspec-knapsack
+rspec 13 20: *rspec-knapsack
+rspec 14 20: *rspec-knapsack
+rspec 15 20: *rspec-knapsack
+rspec 16 20: *rspec-knapsack
+rspec 17 20: *rspec-knapsack
+rspec 18 20: *rspec-knapsack
+rspec 19 20: *rspec-knapsack
+
+spinach 0 10: *spinach-knapsack
+spinach 1 10: *spinach-knapsack
+spinach 2 10: *spinach-knapsack
+spinach 3 10: *spinach-knapsack
+spinach 4 10: *spinach-knapsack
+spinach 5 10: *spinach-knapsack
+spinach 6 10: *spinach-knapsack
+spinach 7 10: *spinach-knapsack
+spinach 8 10: *spinach-knapsack
+spinach 9 10: *spinach-knapsack
+
+# Execute all testing suites against Ruby 2.2
+
+.ruby-22: &ruby-22
+ image: "ruby:2.2"
only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
- cache:
- key: "ruby22"
- paths:
- - vendor
-
-spec:other:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
+ - master
cache:
key: "ruby22"
paths:
- vendor
-spinach:project:half:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
- cache:
- key: "ruby22"
- paths:
- - vendor
+.rspec-knapsack-ruby22: &rspec-knapsack-ruby22
+ <<: *rspec-knapsack
+ <<: *ruby-22
+
+.spinach-knapsack-ruby22: &spinach-knapsack-ruby22
+ <<: *spinach-knapsack
+ <<: *ruby-22
+
+rspec 0 20 ruby22: *rspec-knapsack-ruby22
+rspec 1 20 ruby22: *rspec-knapsack-ruby22
+rspec 2 20 ruby22: *rspec-knapsack-ruby22
+rspec 3 20 ruby22: *rspec-knapsack-ruby22
+rspec 4 20 ruby22: *rspec-knapsack-ruby22
+rspec 5 20 ruby22: *rspec-knapsack-ruby22
+rspec 6 20 ruby22: *rspec-knapsack-ruby22
+rspec 7 20 ruby22: *rspec-knapsack-ruby22
+rspec 8 20 ruby22: *rspec-knapsack-ruby22
+rspec 9 20 ruby22: *rspec-knapsack-ruby22
+rspec 10 20 ruby22: *rspec-knapsack-ruby22
+rspec 11 20 ruby22: *rspec-knapsack-ruby22
+rspec 12 20 ruby22: *rspec-knapsack-ruby22
+rspec 13 20 ruby22: *rspec-knapsack-ruby22
+rspec 14 20 ruby22: *rspec-knapsack-ruby22
+rspec 15 20 ruby22: *rspec-knapsack-ruby22
+rspec 16 20 ruby22: *rspec-knapsack-ruby22
+rspec 17 20 ruby22: *rspec-knapsack-ruby22
+rspec 18 20 ruby22: *rspec-knapsack-ruby22
+rspec 19 20 ruby22: *rspec-knapsack-ruby22
+
+spinach 0 10 ruby22: *spinach-knapsack-ruby22
+spinach 1 10 ruby22: *spinach-knapsack-ruby22
+spinach 2 10 ruby22: *spinach-knapsack-ruby22
+spinach 3 10 ruby22: *spinach-knapsack-ruby22
+spinach 4 10 ruby22: *spinach-knapsack-ruby22
+spinach 5 10 ruby22: *spinach-knapsack-ruby22
+spinach 6 10 ruby22: *spinach-knapsack-ruby22
+spinach 7 10 ruby22: *spinach-knapsack-ruby22
+spinach 8 10 ruby22: *spinach-knapsack-ruby22
+spinach 9 10 ruby22: *spinach-knapsack-ruby22
+
+# Other generic tests
+
+.exec: &exec
+ stage: test
+ script:
+ - bundle exec $CI_BUILD_NAME
+
+teaspoon: *exec
+rubocop: *exec
+rake scss_lint: *exec
+rake brakeman: *exec
+rake flog: *exec
+rake flay: *exec
+rake db:migrate:reset: *exec
+license_finder: *exec
-spinach:project:rest:ruby22:
+bundler:audit:
stage: test
- image: ruby:2.2
only:
- - master
+ - master
script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
- cache:
- key: "ruby22"
- paths:
- - vendor
+ - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
-spinach:other:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
- cache:
- key: "ruby22"
- paths:
- - vendor
+# Notify slack in the end
notify:slack:
- stage: notifications
+ stage: post-test
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure
diff --git a/.rubocop.yml b/.rubocop.yml
index 84a8015b410..c637f5e12f5 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -13,7 +13,8 @@ AllCops:
# Exclude some GitLab files
Exclude:
- 'vendor/**/*'
- - 'db/**/*'
+ - 'db/*'
+ - 'db/fixtures/**/*'
- 'tmp/**/*'
- 'bin/**/*'
- 'lib/backup/**/*'
@@ -194,7 +195,7 @@ Style/EmptyLines:
# Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier:
- Enabled: false
+ Enabled: true
# Keeps track of empty lines around block bodies.
Style/EmptyLinesAroundBlockBody:
@@ -771,7 +772,7 @@ Metrics/PerceivedComplexity:
# Checks for ambiguous operators in the first argument of a method invocation
# without parentheses.
Lint/AmbiguousOperator:
- Enabled: false
+ Enabled: true
# Checks for ambiguous regexp literals in the first argument of a method
# invocation without parentheses.
@@ -1088,6 +1089,9 @@ Rails/TimeZone:
Rails/Validation:
Enabled: false
+Rails/UniqBeforePluck:
+ Enabled: false
+
##################### RSpec ##################################
# Check that instances are not being stubbed globally.
diff --git a/.vagrant_enabled b/.vagrant_enabled
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/.vagrant_enabled
diff --git a/CHANGELOG b/CHANGELOG
index b6c1959bc4c..9205adbc7d7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,39 +1,103 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased)
+ - Bulk assign/unassign labels to issues.
+ - Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
+ - Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
+ - Bump rouge to 1.11.0
+ - Fix issue with arrow keys not working in search autocomplete dropdown
- Make EmailsOnPushWorker use Sidekiq mailers queue
- Fix wiki page events' webhook to point to the wiki repository
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
+ - Allow customisable text on the 'nearly there' page after a user signs up
+ - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
- Allow forking projects with restricted visibility level
+ - Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API
+ - Reduce number of fog gem dependencies
- Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Redesign navigation for project pages
- Fix groups API to list only user's accessible projects
- Redesign account and email confirmation emails
+ - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
+ - Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0
+ - Upgrade to jQuery 2
+ - Use Knapsack to evenly distribute tests across multiple nodes
+ - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
+ - Don't allow MRs to be merged when commits were added since the last review / page load
- Add DB index on users.state
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning)
+ - Links from a wiki page to other wiki pages should be rewritten as expected
+ - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Fix issues filter when ordering by milestone
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
+ - Add support for using Yubikeys (U2F) for two-factor authentication
+ - Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature
- Pipelines can be canceled only when there are running builds
- Use downcased path to container repository as this is expected path by Docker
- Allow to use CI token to fetch LFS objects
- Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails
+ - Make Omniauth providers specs to not modify global configuration
- Make authentication service for Container Registry to be compatible with < Docker 1.11
- Add Application Setting to configure Container Registry token expire delay (default 5min)
+ - Cache assigned issue and merge request counts in sidebar nav
+ - Use Knapsack only in CI environment
+ - Cache project build count in sidebar nav
+ - Add milestone expire date to the right sidebar
+ - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing
+ - Reduce number of queries needed to render issue labels in the sidebar
+ - Improve error handling importing projects
+ - Remove duplicated notification settings
+ - Put project Files and Commits tabs under Code tab
+ - Replace Colorize with Rainbow for coloring console output in Rake tasks.
+ - Add workhorse controller and API helpers
+ - An indicator is now displayed at the top of the comment field for confidential issues.
+ - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
+ - Improve issuables APIs performance when accessing notes !4471
+ - External links now open in a new tab
+ - Markdown editor now correctly resets the input value on edit cancellation !4175
+ - Toggling a task list item in a issue/mr description does not creates a Todo for mentions
+ - Improved UX of date pickers on issue & milestone forms
+ - Cache on the database if a project has an active external issue tracker.
+ - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
+
+v 8.8.5 (unreleased)
+ - Ensure branch cleanup regardless of whether the GitHub import process succeeds
+ - Fix todos page throwing errors when you have a project pending deletion
+ - Reduce number of SQL queries when rendering user references
+ - Import GitHub repositories respecting the API rate limit
+ - Fix importer for GitHub comments on diff
+ - Disable Webhooks before proceeding with the GitHub import
+ - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace
+
+v 8.8.4
+ - Fix LDAP-based login for users with 2FA enabled. !4493
v 8.8.3
- - Fix incorrect links on pipeline page when merge request created from fork
- - Fix gitlab importer failing to import new projects due to missing credentials
- - Fix import URL migration not rescuing with the correct Error
- - In search results, only show notes on confidential issues that the user has access to
- - Fix health check access token changing due to old application settings being used
+ - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
+ - Fixed JS error when trying to remove discussion form. !4303
+ - Fixed issue with button color when no CI enabled. !4287
+ - Fixed potential issue with 2 CI status polling events happening. !3869
+ - Improve design of Pipeline view. !4230
+ - Fix gitlab importer failing to import new projects due to missing credentials. !4301
+ - Fix import URL migration not rescuing with the correct Error. !4321
+ - Fix health check access token changing due to old application settings being used. !4332
+ - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363
+ - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364
+ - Pass the "Remember me" value to the 2FA token form. !4369
+ - Fix incorrect links on pipeline page when merge request created from fork. !4376
+ - Use downcased path to container repository as this is expected path by Docker. !4420
+ - Fix wiki project clone address error (chujinjin). !4429
+ - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392
+ - Fix missing number on generated ordered list element. !4437
+ - Prevent disclosure of notes on confidential issues in search results.
v 8.8.2
- Added remove due date button. !4209
@@ -126,6 +190,7 @@ v 8.8.0
- Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
- Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs)
- When creating a .gitignore file a dropdown with templates will be provided
+ - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562
v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space
@@ -135,6 +200,7 @@ v 8.7.6
- Fix import from GitLab.com to a private instance failure. !4181
- Fix external imports not finding the import data. !4106
- Fix notification delay when changing status of an issue
+ - Bump Workhorse to 0.7.5 so it can serve raw diffs
v 8.7.5
- Fix relative links in wiki pages. !4050
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a15f8c4fec7..f4472214778 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -96,7 +96,7 @@ The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
-The current designs can be found in the [`gitlab1.atype` file].
+The current designs can be found in the [`gitlab8.atype` file].
### UI development kit
@@ -308,7 +308,7 @@ tests are least likely to receive timely feedback. The workflow to make a merge
request is as follows:
1. Fork the project into your personal space on GitLab.com
-1. Create a feature branch
+1. Create a feature branch, branch away from `master`.
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide]
@@ -405,6 +405,7 @@ description area. Copy-paste it to retain the markdown format.
entire line to follow it. This prevents linting tools from generating warnings.
- Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant.
+1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error.
## Changes for Stable Releases
@@ -530,4 +531,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[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/
+[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
+[license-finder-doc]: doc/development/licensing.md
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 0a1ffad4b4d..8bd6ba8c5c3 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.7.4
+0.7.5
diff --git a/Gemfile b/Gemfile
index 0ab78c2a738..b2660144f2b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,16 +38,17 @@ gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt'
# Spam and anti-bot protection
-gem 'recaptcha', require: 'recaptcha/rails'
+gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 3.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.0.0'
+gem 'u2f', '~> 0.2.1'
# Browser detection
-gem "browser", '~> 1.0.0'
+gem "browser", '~> 2.0.3'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
@@ -83,8 +84,15 @@ gem "carrierwave", '~> 0.10.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
+# for backups
+gem 'fog-aws', '~> 0.9'
+gem 'fog-azure', '~> 0.0'
+gem 'fog-core', '~> 1.40'
+gem 'fog-local', '~> 0.3'
+gem 'fog-google', '~> 0.3'
+gem 'fog-openstack', '~> 0.1'
+
# for aws storage
-gem "fog", "~> 1.36.0"
gem "unf", '~> 0.1.4'
# Authorization
@@ -104,7 +112,7 @@ gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
-gem 'rouge', '~> 1.10.1'
+gem 'rouge', '~> 1.11'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
@@ -137,7 +145,7 @@ gem 'redis-namespace'
gem "httparty", '~> 0.13.3'
# Colored output to console
-gem "colorize", '~> 0.7.0'
+gem "rainbow", '~> 2.1.0'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
@@ -299,6 +307,9 @@ group :development, :test do
gem 'bundler-audit', require: false
gem 'benchmark-ips', require: false
+
+ gem "license_finder", require: false
+ gem 'knapsack'
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 4e000fa5b5b..dfc15700494 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,6 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (4.0.2)
actionmailer (4.2.6)
@@ -71,6 +70,21 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
+ azure (0.7.5)
+ addressable (~> 2.3)
+ azure-core (~> 0.1)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ json (~> 1.8)
+ mime-types (>= 1, < 3.0)
+ nokogiri (~> 1.6)
+ systemu (~> 2.6)
+ thor (~> 0.19)
+ uuid (~> 2.0)
+ azure-core (0.1.2)
+ faraday (~> 0.9)
+ faraday_middleware (~> 0.10)
+ nokogiri (~> 1.6)
babosa (1.0.2)
base32 (0.3.2)
bcrypt (3.1.11)
@@ -93,7 +107,7 @@ GEM
sass (~> 3.0)
slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
- browser (1.0.1)
+ browser (2.0.3)
builder (3.2.2)
bullet (5.0.0)
activesupport (>= 3.0.0)
@@ -183,7 +197,7 @@ GEM
erubis (2.7.0)
escape_utils (1.1.1)
eventmachine (1.0.8)
- excon (0.45.4)
+ excon (0.49.0)
execjs (2.6.0)
expression_parser (0.9.0)
factory_girl (4.5.0)
@@ -200,8 +214,6 @@ GEM
multi_json
ffaker (2.0.0)
ffi (1.9.10)
- fission (0.5.0)
- CFPropertyList (~> 2.2)
flay (2.6.1)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
@@ -211,109 +223,33 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
- fog (1.36.0)
- fog-aliyun (>= 0.1.0)
- fog-atmos
- fog-aws (>= 0.6.0)
- fog-brightbox (~> 0.4)
- fog-core (~> 1.32)
- fog-dynect (~> 0.0.2)
- fog-ecloud (~> 0.1)
- fog-google (<= 0.1.0)
- fog-json
- fog-local
- fog-powerdns (>= 0.1.1)
- fog-profitbricks
- fog-radosgw (>= 0.0.2)
- fog-riakcs
- fog-sakuracloud (>= 0.0.4)
- fog-serverlove
- fog-softlayer
- fog-storm_on_demand
- fog-terremark
- fog-vmfusion
- fog-voxel
- fog-xenserver
- fog-xml (~> 0.1.1)
- ipaddress (~> 0.5)
- nokogiri (~> 1.5, >= 1.5.11)
- fog-aliyun (0.1.0)
+ fog-aws (0.9.2)
fog-core (~> 1.27)
fog-json (~> 1.0)
+ fog-xml (~> 0.1)
ipaddress (~> 0.8)
- xml-simple (~> 1.1)
- fog-atmos (0.1.0)
- fog-core
- fog-xml
- fog-aws (0.8.1)
+ fog-azure (0.0.2)
+ azure (~> 0.6)
fog-core (~> 1.27)
fog-json (~> 1.0)
fog-xml (~> 0.1)
- ipaddress (~> 0.8)
- fog-brightbox (0.10.1)
- fog-core (~> 1.22)
- fog-json
- inflecto (~> 0.0.2)
- fog-core (1.35.0)
+ fog-core (1.40.0)
builder
- excon (~> 0.45)
+ excon (~> 0.49)
formatador (~> 0.2)
- fog-dynect (0.0.2)
- fog-core
- fog-json
- fog-xml
- fog-ecloud (0.3.0)
- fog-core
- fog-xml
- fog-google (0.1.0)
+ fog-google (0.3.2)
fog-core
fog-json
fog-xml
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
- fog-local (0.2.1)
+ fog-local (0.3.0)
fog-core (~> 1.27)
- fog-powerdns (0.1.1)
- fog-core (~> 1.27)
- fog-json (~> 1.0)
- fog-xml (~> 0.1)
- fog-profitbricks (0.0.5)
- fog-core
- fog-xml
- nokogiri
- fog-radosgw (0.0.5)
- fog-core (>= 1.21.0)
- fog-json
- fog-xml (>= 0.0.1)
- fog-riakcs (0.1.0)
- fog-core
- fog-json
- fog-xml
- fog-sakuracloud (1.7.5)
- fog-core
- fog-json
- fog-serverlove (0.1.2)
- fog-core
- fog-json
- fog-softlayer (1.0.3)
- fog-core
- fog-json
- fog-storm_on_demand (0.1.1)
- fog-core
- fog-json
- fog-terremark (0.1.0)
- fog-core
- fog-xml
- fog-vmfusion (0.1.0)
- fission
- fog-core
- fog-voxel (0.1.0)
- fog-core
- fog-xml
- fog-xenserver (0.2.2)
- fog-core
- fog-xml
+ fog-openstack (0.1.6)
+ fog-core (>= 1.39)
+ fog-json (>= 1.0)
+ ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
@@ -422,11 +358,10 @@ GEM
httpclient (2.7.0.1)
i18n (0.7.0)
ice_nine (0.11.1)
- inflecto (0.0.2)
influxdb (0.2.3)
cause
json
- ipaddress (0.8.2)
+ ipaddress (0.8.3)
jquery-atwho-rails (1.3.2)
jquery-rails (4.1.1)
rails-dom-testing (>= 1, < 3)
@@ -443,6 +378,9 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.10.0)
+ knapsack (1.11.0)
+ rake
+ timecop (>= 0.1.0)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
@@ -451,6 +389,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
+ license_finder (2.1.0)
+ bundler
+ httparty
+ rubyzip
+ thor
+ xml-simple
licensee (8.0.0)
rugged (>= 0.24b)
listen (3.0.5)
@@ -466,7 +410,7 @@ GEM
method_source (0.8.2)
mime-types (2.99.1)
mimemagic (0.3.0)
- mini_portile2 (2.0.0)
+ mini_portile2 (2.1.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
multi_json (1.11.2)
@@ -477,8 +421,9 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
newrelic_rpm (3.14.1.311)
- nokogiri (1.6.7.2)
- mini_portile2 (~> 2.0.0.rc2)
+ nokogiri (1.6.8)
+ mini_portile2 (~> 2.1.0)
+ pkg-config (~> 1.1.7)
oauth (0.4.7)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
@@ -550,6 +495,7 @@ GEM
parser (2.3.1.0)
ast (~> 2.2)
pg (0.18.4)
+ pkg-config (1.1.7)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -625,7 +571,7 @@ GEM
debugger-ruby_core_source (~> 1.3)
rdoc (3.12.2)
json (~> 1.4)
- recaptcha (1.0.2)
+ recaptcha (3.0.0)
json
redcarpet (3.3.3)
redis (3.3.0)
@@ -654,7 +600,7 @@ GEM
railties (>= 4.2.0, < 5.1)
rinku (1.7.3)
rotp (2.1.2)
- rouge (1.10.1)
+ rouge (1.11.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -703,6 +649,7 @@ GEM
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
+ rubyzip (1.2.0)
rufus-scheduler (3.1.10)
rugged (0.24.0)
safe_yaml (1.0.4)
@@ -813,6 +760,7 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.2)
+ timecop (0.8.1)
timfel-krb5-auth (0.8.3)
tinder (1.10.1)
eventmachine (~> 1.0)
@@ -832,6 +780,7 @@ GEM
simple_oauth (~> 0.1.4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
+ u2f (0.2.1)
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
@@ -900,7 +849,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.2.0)
- browser (~> 1.0.0)
+ browser (~> 2.0.3)
bullet
bundler-audit
byebug
@@ -909,7 +858,6 @@ DEPENDENCIES
carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0)
- colorize (~> 0.7.0)
connection_pool (~> 2.0)
coveralls (~> 0.8.2)
creole (~> 0.5.0)
@@ -927,7 +875,12 @@ DEPENDENCIES
ffaker (~> 2.0.0)
flay
flog
- fog (~> 1.36.0)
+ fog-aws (~> 0.9)
+ fog-azure (~> 0.0)
+ fog-core (~> 1.40)
+ fog-google (~> 0.3)
+ fog-local (~> 0.3)
+ fog-openstack (~> 0.1)
font-awesome-rails (~> 4.2)
foreman
fuubar (~> 2.0.0)
@@ -956,7 +909,9 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0)
jwt
kaminari (~> 0.17.0)
+ knapsack
letter_opener_web (~> 1.3.0)
+ license_finder
licensee (~> 8.0.0)
loofah (~> 2.0.3)
mail_room (~> 0.7)
@@ -996,10 +951,11 @@ DEPENDENCIES
rack-oauth2 (~> 1.2.1)
rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
+ rainbow (~> 2.1.0)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
- recaptcha
+ recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace
@@ -1007,7 +963,7 @@ DEPENDENCIES
request_store (~> 1.3.0)
rerun (~> 0.11.0)
responders (~> 2.0)
- rouge (~> 1.10.1)
+ rouge (~> 1.11)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.4.0)
rspec-retry
@@ -1045,6 +1001,7 @@ DEPENDENCIES
thin (~> 1.6.1)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
+ u2f (~> 0.2.1)
uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0)
unf (~> 0.1.4)
@@ -1057,4 +1014,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.12.4
+ 1.12.5
diff --git a/Rakefile b/Rakefile
index 5dd389d5678..85fff2d51eb 100755
--- a/Rakefile
+++ b/Rakefile
@@ -8,3 +8,5 @@ relative_url_conf = File.expand_path('../config/initializers/relative_url', __FI
require relative_url_conf if File.exist?("#{relative_url_conf}.rb")
Gitlab::Application.load_tasks
+
+Knapsack.load_tasks if defined?(Knapsack)
diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee
new file mode 100644
index 00000000000..365a062bb81
--- /dev/null
+++ b/app/assets/javascripts/LabelManager.js.coffee
@@ -0,0 +1,84 @@
+class @LabelManager
+ errorMessage: 'Unable to update label prioritization at this time'
+
+ constructor: (opts = {}) ->
+ # Defaults
+ {
+ @togglePriorityButton = $('.js-toggle-priority')
+ @prioritizedLabels = $('.js-prioritized-labels')
+ @otherLabels = $('.js-other-labels')
+ } = opts
+
+ @prioritizedLabels.sortable(
+ items: 'li'
+ placeholder: 'list-placeholder'
+ axis: 'y'
+ update: @onPrioritySortUpdate.bind(@)
+ )
+
+ @bindEvents()
+
+ bindEvents: ->
+ @togglePriorityButton.on 'click', @, @onTogglePriorityClick
+
+ onTogglePriorityClick: (e) ->
+ e.preventDefault()
+ _this = e.data
+ $btn = $(e.currentTarget)
+ $label = $("##{$btn.data('domId')}")
+ action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
+ _this.toggleLabelPriority($label, action)
+
+ toggleLabelPriority: ($label, action, persistState = true) ->
+ _this = @
+ url = $label.find('.js-toggle-priority').data 'url'
+
+ $target = @prioritizedLabels
+ $from = @otherLabels
+
+ # Optimistic update
+ if action is 'remove'
+ $target = @otherLabels
+ $from = @prioritizedLabels
+
+ if $from.find('li').length is 1
+ $from.find('.empty-message').show()
+
+ if not $target.find('li').length
+ $target.find('.empty-message').hide()
+
+ $label.detach().appendTo($target)
+
+ # Return if we are not persisting state
+ return unless persistState
+
+ if action is 'remove'
+ xhr = $.ajax url: url, type: 'DELETE'
+ else
+ xhr = @savePrioritySort($label, action)
+
+ xhr.fail @rollbackLabelPosition.bind(@, $label, action)
+
+ onPrioritySortUpdate: ->
+ xhr = @savePrioritySort()
+
+ xhr.fail ->
+ new Flash(@errorMessage, 'alert')
+
+ savePrioritySort: () ->
+ $.post
+ url: @prioritizedLabels.data('url')
+ data:
+ label_ids: @getSortedLabelsIds()
+
+ rollbackLabelPosition: ($label, originalAction)->
+ action = if originalAction is 'remove' then 'add' else 'remove'
+ @toggleLabelPriority($label, action, false)
+
+ new Flash(@errorMessage, 'alert')
+
+ getSortedLabelsIds: ->
+ sortedIds = []
+ @prioritizedLabels.find('li').each ->
+ sortedIds.push $(@).data 'id'
+ sortedIds
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index 5092e824e65..ed5a5d0260c 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,11 +1,14 @@
class @Activities
constructor: ->
- Pager.init 20, true
+ Pager.init 20, true, false, @updateTooltips
$(".event-filter-link").on "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
+ updateTooltips: ->
+ gl.utils.localTimeAgo($('.js-timeago', '#activity'))
+
reloadActivities: ->
$(".content_list").html ''
Pager.init 20, true
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 18c1aa0d4e2..e0ca546350b 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -4,7 +4,7 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
-#= require jquery
+#= require jquery2
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
#= require jquery-ui/draggable
@@ -35,7 +35,6 @@
#= require raphael
#= require g.raphael
#= require g.bar
-#= require Chart
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
@@ -56,9 +55,11 @@
#= require_directory ./commit
#= require_directory ./extensions
#= require_directory ./lib
+#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
#= require cropper
+#= require u2f
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -224,6 +225,10 @@ $ ->
form = btn.closest("form")
new ConfirmDangerModal(form, text)
+
+ $(document).on 'click', 'button', ->
+ $(this).blur()
+
$('input[type="search"]').each ->
$this = $(this)
$this.attr 'value', $this.val()
@@ -262,9 +267,10 @@ $ ->
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
- .off "resize"
- .on "resize", (e) ->
+ .off "resize.app"
+ .on "resize.app", (e) ->
fitSidebarForSize()
+ gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize()
new Aside()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index bf95e06b4e5..136db8ee14d 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,201 +1,354 @@
class @AwardsHandler
- constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
- $('.js-add-award').on 'click', (event) =>
- event.stopPropagation()
- event.preventDefault()
- @showEmojiMenu()
+ constructor: ->
- $('html').on 'click', (event) ->
- if !$(event.target).closest('.emoji-menu').length
+ @aliases = gl.emojiAliases()
+
+ $(document)
+ .off 'click', '.js-add-award'
+ .on 'click', '.js-add-award', (e) =>
+ e.stopPropagation()
+ e.preventDefault()
+
+ @showEmojiMenu $(e.currentTarget)
+
+ $('html').on 'click', (e) ->
+ $target = $ e.target
+
+ unless $target.closest('.emoji-menu-content').length
+ $('.js-awards-block.current').removeClass 'current'
+
+ unless $target.closest('.emoji-menu').length
if $('.emoji-menu').is(':visible')
+ $('.js-add-award.is-active').removeClass 'is-active'
$('.emoji-menu').removeClass 'is-visible'
- $('.awards')
- .off 'click'
- .on 'click', '.js-emoji-btn', @handleClick
+ $(document)
+ .off 'click', '.js-emoji-btn'
+ .on 'click', '.js-emoji-btn', (e) =>
+ e.preventDefault()
- @renderFrequentlyUsedBlock()
+ $target = $ e.currentTarget
+ emoji = $target.find('.icon').data 'emoji'
- handleClick: (e) ->
- e.preventDefault()
- emoji = $(this)
- .find('.icon')
- .data 'emoji'
+ $target.closest('.js-awards-block').addClass 'current'
+ @addAward @getVotesBlock(), @getAwardUrl(), emoji
- if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
- awardsHandler.addAward 'thumbsdown'
- else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
- awardsHandler.addAward 'thumbsup'
+ showEmojiMenu: ($addBtn) ->
- awardsHandler.addAward emoji
+ $menu = $ '.emoji-menu'
- $(this).trigger 'blur'
+ if $addBtn.hasClass 'js-note-emoji'
+ $addBtn.parents('.note').find('.js-awards-block').addClass 'current'
+ else
+ $addBtn.closest('.js-awards-block').addClass 'current'
- didUserClickEmoji: (that, emoji) ->
- if $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title')
- $(that).siblings("button:has([data-emoji=#{emoji}])").attr('data-original-title').indexOf('me') > -1
+ if $menu.length
+ $holder = $addBtn.closest('.js-award-holder')
- showEmojiMenu: ->
- if $('.emoji-menu').length
- if $('.emoji-menu').is '.is-visible'
- $('.emoji-menu').removeClass 'is-visible'
+ if $menu.is '.is-visible'
+ $addBtn.removeClass 'is-active'
+ $menu.removeClass 'is-visible'
$('#emoji_search').blur()
else
- $('.emoji-menu').addClass 'is-visible'
+ $addBtn.addClass 'is-active'
+ @positionMenu($menu, $addBtn)
+
+ $menu.addClass 'is-visible'
$('#emoji_search').focus()
else
- $('.js-add-award').addClass 'is-loading'
- $.get @getEmojisUrl, (response) =>
- $('.js-add-award').removeClass 'is-loading'
- $('.js-award-holder').append response
+ $addBtn.addClass 'is-loading is-active'
+ url = @getAwardMenuUrl()
+
+ @createEmojiMenu url, =>
+ $addBtn.removeClass 'is-loading'
+ $menu = $('.emoji-menu')
+ @positionMenu($menu, $addBtn)
+ @renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
+
setTimeout =>
- $('.emoji-menu').addClass 'is-visible'
+ $menu.addClass 'is-visible'
$('#emoji_search').focus()
@setupSearch()
, 200
- addAward: (emoji) ->
- @postEmoji emoji, =>
- @addAwardToEmojiBar(emoji)
+
+ createEmojiMenu: (awardMenuUrl, callback) ->
+
+ $.get awardMenuUrl, (response) ->
+ $('body').append response
+ callback()
+
+
+ positionMenu: ($menu, $addBtn) ->
+
+ position = $addBtn.data('position')
+
+ # The menu could potentially be off-screen or in a hidden overflow element
+ # So we position the element absolute in the body
+ css =
+ top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"
+
+ if position? and position is 'right'
+ css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
+ $menu.addClass 'is-aligned-right'
+ else
+ css.left = "#{$addBtn.offset().left}px"
+ $menu.removeClass 'is-aligned-right'
+
+ $menu.css(css)
+
+
+ addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
+
+ emoji = @normilizeEmojiName emoji
+
+ @postEmoji awardUrl, emoji, =>
+ @addAwardToEmojiBar votesBlock, emoji, checkMutuality
+ callback?()
$('.emoji-menu').removeClass 'is-visible'
- addAwardToEmojiBar: (emoji) ->
- @addEmojiToFrequentlyUsedList(emoji)
- if @exist(emoji)
- if @isActive(emoji)
- @decrementCounter(emoji)
+ addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
+
+ @checkMutuality votesBlock, emoji if checkForMutuality
+ @addEmojiToFrequentlyUsedList emoji
+
+ emoji = @normilizeEmojiName emoji
+ $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
+
+ if $emojiButton.length > 0
+ if @isActive $emojiButton
+ @decrementCounter $emojiButton, emoji
else
- counter = @findEmojiIcon(emoji).siblings('.js-counter')
- counter.text(parseInt(counter.text()) + 1)
- counter.parent().addClass('active')
- @addMeToAuthorList(emoji)
+ counter = $emojiButton.find '.js-counter'
+ counter.text parseInt(counter.text()) + 1
+ $emojiButton.addClass 'active'
+ @addMeToUserList votesBlock, emoji
+ @animateEmoji $emojiButton
else
- @createEmoji(emoji)
-
- exist: (emoji) ->
- @findEmojiIcon(emoji).length > 0
-
- isActive: (emoji) ->
- @findEmojiIcon(emoji).parent().hasClass('active')
-
- decrementCounter: (emoji) ->
- counter = @findEmojiIcon(emoji).siblings('.js-counter')
- emojiIcon = counter.parent()
- if parseInt(counter.text()) > 1
- counter.text(parseInt(counter.text()) - 1)
- emojiIcon.removeClass('active')
- @removeMeFromAuthorList(emoji)
- else if emoji == 'thumbsup' || emoji == 'thumbsdown'
- emojiIcon.tooltip('destroy')
- counter.text(0)
- emojiIcon.removeClass('active')
- @removeMeFromAuthorList(emoji)
+ votesBlock.removeClass 'hidden'
+ @createEmoji votesBlock, emoji
+
+
+ getVotesBlock: ->
+
+ currentBlock = $ '.js-awards-block.current'
+ return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
+
+
+ getAwardUrl: -> return @getVotesBlock().data 'award-url'
+
+
+ checkMutuality: (votesBlock, emoji) ->
+
+ awardUrl = @getAwardUrl()
+
+ if emoji in [ 'thumbsup', 'thumbsdown' ]
+ mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
+ $emojiButton = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
+ isAlreadyVoted = $emojiButton.hasClass 'active'
+
+ if isAlreadyVoted
+ @showEmojiLoader $emojiButton
+ @addAward votesBlock, awardUrl, mutualVote, false, ->
+ $emojiButton.removeClass 'is-loading'
+
+
+ showEmojiLoader: ($emojiButton) ->
+
+ $loader = $emojiButton.find '.fa-spinner'
+
+ unless $loader.length
+ $emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'
+
+ $emojiButton.addClass 'is-loading'
+
+
+ isActive: ($emojiButton) -> $emojiButton.hasClass 'active'
+
+
+ decrementCounter: ($emojiButton, emoji) ->
+
+ counter = $ '.js-counter', $emojiButton
+ counterNumber = parseInt counter.text(), 10
+
+ if counterNumber > 1
+ counter.text counterNumber - 1
+ @removeMeFromUserList $emojiButton, emoji
+ else if emoji is 'thumbsup' or emoji is 'thumbsdown'
+ $emojiButton.tooltip 'destroy'
+ counter.text '0'
+ @removeMeFromUserList $emojiButton, emoji
+ @removeEmoji $emojiButton if $emojiButton.parents('.note').length
else
- emojiIcon.tooltip('destroy')
- emojiIcon.remove()
-
- removeMeFromAuthorList: (emoji) ->
- awardBlock = @findEmojiIcon(emoji).parent()
- authors = awardBlock
- .attr('data-original-title')
- .split(', ')
- authors.splice(authors.indexOf('me'),1)
+ @removeEmoji $emojiButton
+
+ $emojiButton.removeClass 'active'
+
+
+ removeEmoji: ($emojiButton) ->
+
+ $emojiButton.tooltip('destroy')
+ $emojiButton.remove()
+
+ $votesBlock = @getVotesBlock()
+
+ if $votesBlock.find('.js-emoji-btn').length is 0
+ $votesBlock.addClass 'hidden'
+
+
+ getAwardTooltip: ($awardBlock) ->
+
+ return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
+
+
+ removeMeFromUserList: ($emojiButton, emoji) ->
+
+ awardBlock = $emojiButton
+ originalTitle = @getAwardTooltip awardBlock
+
+ authors = originalTitle.split ', '
+ authors.splice authors.indexOf('me'), 1
+
+ newAuthors = authors.join ', '
+
awardBlock
- .closest('.js-emoji-btn')
- .attr('data-original-title', authors.join(', '))
- @resetTooltip(awardBlock)
-
- addMeToAuthorList: (emoji) ->
- awardBlock = @findEmojiIcon(emoji).parent()
- origTitle = awardBlock.attr('data-original-title').trim()
- authors = []
+ .closest '.js-emoji-btn'
+ .removeData 'original-title'
+ .attr 'data-original-title', newAuthors
+
+ @resetTooltip awardBlock
+
+
+ addMeToUserList: (votesBlock, emoji) ->
+
+ awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
+ origTitle = @getAwardTooltip awardBlock
+ users = []
+
if origTitle
- authors = origTitle.split(', ')
- authors.push('me')
- awardBlock.attr('data-original-title', authors.join(', '))
- @resetTooltip(awardBlock)
+ users = origTitle.trim().split ', '
+
+ users.push 'me'
+ awardBlock.attr 'title', users.join ', '
+
+ @resetTooltip awardBlock
+
resetTooltip: (award) ->
- award.tooltip('destroy')
- # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
- setTimeout (->
- award.tooltip()
- ), 200
+ award.tooltip 'destroy'
+
+ # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
+ cb = -> award.tooltip()
+ setTimeout cb, 200
+
+ createEmoji_: (votesBlock, emoji) ->
- createEmoji: (emoji) ->
- emojiCssClass = @resolveNameToCssClass(emoji)
+ emojiCssClass = @resolveNameToCssClass emoji
+ buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
+ <div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
+ <span class='award-control-text js-counter'>1</span>
+ </button>"
- nodes = []
- nodes.push(
- "<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
- "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
- "<span class='award-control-text js-counter'>1</span>",
- "</button>"
- )
+ $emojiButton = $ buttonHtml
+ $emojiButton
+ .insertBefore votesBlock.find '.js-award-holder'
+ .find '.emoji-icon'
+ .data 'emoji', emoji
- $(nodes.join("\n"))
- .insertBefore('.js-award-holder')
- .find('.emoji-icon')
- .data('emoji', emoji)
+ @animateEmoji $emojiButton
$('.award-control').tooltip()
+ votesBlock.removeClass 'current'
+
+
+ animateEmoji: ($emoji) ->
+
+ className = 'pulse animated'
+
+ $emoji.addClass className
+ setTimeout (-> $emoji.removeClass className), 321
+
+
+ createEmoji: (votesBlock, emoji) ->
+
+ if $('.emoji-menu').length
+ return @createEmoji_ votesBlock, emoji
+
+ @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
+
+
+ getAwardMenuUrl: -> return gon.award_menu_url
+
resolveNameToCssClass: (emoji) ->
- emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+
+ emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
if emojiIcon.length > 0
- unicodeName = emojiIcon.data('unicode-name')
+ 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}"
+ return "emoji-#{unicodeName}"
- postEmoji: (emoji, callback) ->
- $.post @postEmojiUrl, { note: {
- note: ":#{emoji}:"
- noteable_type: @noteableType
- noteable_id: @noteableId
- }},(data) ->
- if data.ok
- callback.call()
- findEmojiIcon: (emoji) ->
- $(".awards > .js-emoji-btn [data-emoji='#{emoji}']")
+ postEmoji: (awardUrl, emoji, callback) ->
+
+ $.post awardUrl, { name: emoji }, (data) ->
+ callback() if data.ok
+
+
+ findEmojiIcon: (votesBlock, emoji) ->
+
+ return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
+
scrollToAwards: ->
- $('body, html').animate({
- scrollTop: $('.awards').offset().top - 80
- }, 200)
+
+ options = scrollTop: $('.awards').offset().top - 110
+ $('body, html').animate options, 200
+
+
+ normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
+
addEmojiToFrequentlyUsedList: (emoji) ->
+
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- frequentlyUsedEmojis.push(emoji)
- $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
+ frequentlyUsedEmojis.push emoji
+ $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
+
getFrequentlyUsedEmojis: ->
- frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
- _.compact(_.uniq(frequentlyUsedEmojis))
+
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
+ return _.compact _.uniq frequentlyUsedEmojis
+
renderFrequentlyUsedBlock: ->
- if $.cookie('frequently_used_emojis')
+
+ if $.cookie 'frequently_used_emojis'
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
- ul = $('<ul>')
+ ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
for emoji in frequentlyUsedEmojis
- do (emoji) ->
- $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
+ $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
+ @frequentEmojiBlockRendered = true
+
+
setupSearch: ->
- $('input.emoji-search').keyup (ev) =>
+
+ $('input.emoji-search').on 'keyup', (ev) =>
term = $(ev.target).val()
# Clean previous search results
@@ -204,12 +357,14 @@ class @AwardsHandler
if term
# Generate a search result block
h5 = $('<h5>').text('Search results').addClass('emoji-search')
- foundEmojis = @searchEmojis(term).show()
- ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
+ 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)
else
$('.emoji-menu-content').children().show()
- searchEmojis: (term)->
- $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
+
+ searchEmojis: (term) ->
+
+ $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee
index 98d05e41273..f763ba96e33 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/ci/build.coffee
@@ -1,19 +1,31 @@
-class CiBuild
+class @CiBuild
@interval: null
@state: null
- constructor: (build_url, build_status, build_state) ->
+ constructor: (@build_url, @build_status, @state) ->
clearInterval(CiBuild.interval)
- @state = build_state
+ # Init breakpoint checker
+ @bp = Breakpoints.get()
+ @hideSidebar()
+ $('.js-build-sidebar').niceScroll()
+ $(document)
+ .off 'click', '.js-sidebar-build-toggle'
+ .on 'click', '.js-sidebar-build-toggle', @toggleSidebar
- @initScrollButtonAffix()
+ $(window)
+ .off 'resize.build'
+ .on 'resize.build', @hideSidebar
- if build_status == "running" || build_status == "pending"
+ if $('#build-trace').length
+ @getInitialBuildTrace()
+ @initScrollButtonAffix()
+
+ if @build_status is "running" or @build_status is "pending"
#
# Bind autoscroll button to follow build output
#
- $("#autoscroll-button").bind "click", ->
+ $('#autoscroll-button').on 'click', ->
state = $(this).data("state")
if "enabled" is state
$(this).data "state", "disabled"
@@ -27,26 +39,37 @@ class CiBuild
# Only valid for runnig build when output changes during time
#
CiBuild.interval = setInterval =>
- if window.location.href.split("#").first() is build_url
- last_state = @state
- $.ajax
- url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
- dataType: "json"
- success: (log) =>
- return unless last_state is @state
-
- if log.state and log.status is "running"
- @state = log.state
- if log.append
- $('.fa-refresh').before log.html
- else
- $('#build-trace code').html log.html
- $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
- @checkAutoscroll()
- else if log.status isnt build_status
- Turbolinks.visit build_url
+ if window.location.href.split("#").first() is @build_url
+ @getBuildTrace()
, 4000
+ getInitialBuildTrace: ->
+ $.ajax
+ url: @build_url
+ dataType: 'json'
+ success: (build_data) ->
+ $('.js-build-output').html build_data.trace_html
+
+ if build_data.status is 'success' or build_data.status is 'failed'
+ $('.js-build-refresh').remove()
+
+ getBuildTrace: ->
+ $.ajax
+ url: "#{@build_url}/trace.json?state=#{encodeURIComponent(@state)}"
+ dataType: "json"
+ success: (log) =>
+ if log.state
+ @state = log.state
+
+ if log.status is "running"
+ if log.append
+ $('.js-build-output').append log.html
+ else
+ $('.js-build-output').html log.html
+ @checkAutoscroll()
+ else if log.status isnt @build_status
+ Turbolinks.visit @build_url
+
checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
@@ -61,4 +84,22 @@ class CiBuild
$body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
)
-@CiBuild = CiBuild
+ shouldHideSidebar: ->
+ bootstrapBreakpoint = @bp.getBreakpointSize()
+
+ bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
+
+ toggleSidebar: =>
+ if @shouldHideSidebar()
+ $('.js-build-sidebar')
+ .toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
+
+ hideSidebar: =>
+ if @shouldHideSidebar()
+ $('.js-build-sidebar')
+ .removeClass 'right-sidebar-expanded'
+ .addClass 'right-sidebar-collapsed'
+ else
+ $('.js-build-sidebar')
+ .removeClass 'right-sidebar-collapsed'
+ .addClass 'right-sidebar-expanded'
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index a3185f87640..29ac0f70b30 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'
Issuable.init()
+ new IssuableBulkActions()
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
@@ -97,6 +98,8 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
+ when 'projects:labels:index'
+ new LabelManager() if $('.prioritized-labels').length
when 'projects:network:show'
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
index 3cc70185178..3d009a96d05 100644
--- a/app/assets/javascripts/due_date_select.js.coffee
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -21,7 +21,7 @@ class @DueDateSelect
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
)
addDueDate = (isDropdown) ->
@@ -42,12 +42,13 @@ class @DueDateSelect
type: 'PUT'
url: issueUpdateURL
data: data
+ dataType: 'json'
beforeSend: ->
$loading.fadeIn()
if isDropdown
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
$valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index 5de012e409f..4f73d215b85 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,5 +1,5 @@
class @Flash
- constructor: (message, type)->
+ constructor: (message, type = 'alert')->
@flash = $(".flash-container")
@flash.html("")
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 41dba342107..76c3083232b 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -3,6 +3,7 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
dataLoading: false
+ dataLoaded: false
dataSource: ''
@@ -22,6 +23,24 @@ GitLab.GfmAutoComplete =
Milestones:
template: '<li>${title}</li>'
+ Loading:
+ template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+
+ DefaultOptions:
+ sorter: (query, items, searchKey) ->
+ return items if items[0].name? and items[0].name is 'loading'
+
+ $.fn.atwho.default.callbacks.sorter(query, items, searchKey)
+ filter: (query, data, searchKey) ->
+ return data if data[0] is 'loading'
+
+ $.fn.atwho.default.callbacks.filter(query, data, searchKey)
+ beforeInsert: (value) ->
+ if not GitLab.GfmAutoComplete.dataLoaded
+ @at
+ else
+ value
+
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
@@ -53,18 +72,37 @@ GitLab.GfmAutoComplete =
# Emoji
@input.atwho
at: ':'
- displayTpl: @Emoji.template
+ displayTpl: (value) =>
+ if value.path?
+ @Emoji.template
+ else
+ @Loading.template
insertTpl: ':${name}:'
+ data: ['loading']
+ callbacks:
+ sorter: @DefaultOptions.sorter
+ filter: @DefaultOptions.filter
+ beforeInsert: @DefaultOptions.beforeInsert
# Team Members
@input.atwho
at: '@'
- displayTpl: @Members.template
+ displayTpl: (value) =>
+ if value.username?
+ @Members.template
+ else
+ @Loading.template
insertTpl: '${atwho-at}${username}'
searchKey: 'search'
+ data: ['loading']
callbacks:
+ sorter: @DefaultOptions.sorter
+ filter: @DefaultOptions.filter
+ beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (members) ->
$.map members, (m) ->
+ return m if not m.username?
+
title = m.name
title += " (#{m.count})" if m.count
@@ -76,11 +114,21 @@ GitLab.GfmAutoComplete =
at: '#'
alias: 'issues'
searchKey: 'search'
- displayTpl: @Issues.template
+ displayTpl: (value) =>
+ if value.title?
+ @Issues.template
+ else
+ @Loading.template
+ data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
+ sorter: @DefaultOptions.sorter
+ filter: @DefaultOptions.filter
+ beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (issues) ->
$.map issues, (i) ->
+ return i if not i.title?
+
id: i.iid
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
@@ -89,11 +137,18 @@ GitLab.GfmAutoComplete =
at: '%'
alias: 'milestones'
searchKey: 'search'
- displayTpl: @Milestones.template
+ displayTpl: (value) =>
+ if value.title?
+ @Milestones.template
+ else
+ @Loading.template
insertTpl: '${atwho-at}"${title}"'
+ data: ['loading']
callbacks:
beforeSave: (milestones) ->
$.map milestones, (m) ->
+ return m if not m.title?
+
id: m.iid
title: sanitize(m.title)
search: "#{m.title}"
@@ -102,11 +157,21 @@ GitLab.GfmAutoComplete =
at: '!'
alias: 'mergerequests'
searchKey: 'search'
- displayTpl: @Issues.template
+ displayTpl: (value) =>
+ if value.title?
+ @Issues.template
+ else
+ @Loading.template
+ data: ['loading']
insertTpl: '${atwho-at}${id}'
callbacks:
+ sorter: @DefaultOptions.sorter
+ filter: @DefaultOptions.filter
+ beforeInsert: @DefaultOptions.beforeInsert
beforeSave: (merges) ->
$.map merges, (m) ->
+ return m if not m.title?
+
id: m.iid
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
@@ -118,6 +183,8 @@ GitLab.GfmAutoComplete =
$.getJSON(dataSource)
loadData: (data) ->
+ @dataLoaded = true
+
# load members
@input.atwho 'load', '@', data.members
# load issues
@@ -128,3 +195,7 @@ GitLab.GfmAutoComplete =
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
+
+ # This trigger at.js again
+ # otherwise we would be stuck with loading until the user types
+ $(':focus').trigger('keyup')
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index b3f1dc969b8..b49bd4565a7 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -11,6 +11,8 @@ class GitLabDropdownFilter
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
+ @indeterminateIds = []
+
# Clear click
$clearButton.on 'click', (e) =>
e.preventDefault()
@@ -35,20 +37,20 @@ class GitLabDropdownFilter
if keyCode is 13
return false
- clearTimeout timeout
- timeout = setTimeout =>
- blur_field = @shouldBlur keyCode
- search_text = @input.val()
+ # Only filter asynchronously only if option remote is set
+ if @options.remote
+ clearTimeout timeout
+ timeout = setTimeout =>
+ blur_field = @shouldBlur keyCode
- if blur_field and @filterInputBlur
- @input.blur()
+ if blur_field and @filterInputBlur
+ @input.blur()
- if @options.remote
- @options.query search_text, (data) =>
+ @options.query @input.val(), (data) =>
@options.callback(data)
- else
- @filter search_text
- , 250
+ , 250
+ else
+ @filter @input.val()
shouldBlur: (keyCode) ->
return BLUR_KEYCODES.indexOf(keyCode) >= 0
@@ -142,6 +144,7 @@ class GitLabDropdown
LOADING_CLASS = "is-loading"
PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active"
+ INDETERMINATE_CLASS = "is-indeterminate"
currentIndex = -1
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
@@ -182,9 +185,6 @@ class GitLabDropdown
@fullData = data
@parseData @fullData
-
- if @options.filterable
- @filterInput.trigger 'keyup'
}
# Init filterable
@@ -211,6 +211,7 @@ class GitLabDropdown
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
+ $(@el).on "update.label", @updateLabel
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
@dropdown.on 'keyup', (e) =>
if e.which is 27 # Escape key
@@ -298,6 +299,13 @@ class GitLabDropdown
opened: =>
@addArrowKeyEvent()
+ if @options.setIndeterminateIds
+ @options.setIndeterminateIds.call(@)
+
+ # Makes indeterminate items effective
+ if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
+ @parseData @fullData
+
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
@@ -309,12 +317,18 @@ class GitLabDropdown
hidden: (e) =>
@removeArrayKeyEvent()
+
+ $input = @dropdown.find(".dropdown-input-field")
+
if @options.filterable
- @dropdown
- .find(".dropdown-input-field")
+ $input
.blur()
.val("")
- .trigger("keyup")
+
+ # Triggering 'keyup' will re-render the dropdown which is not always required
+ # specially if we want to keep the state of the dropdown needed for bulk-assignment
+ if not @options.persistWhenHide
+ $input.trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
@@ -358,7 +372,7 @@ class GitLabDropdown
if @options.renderRow
# Call the render function
- html = @options.renderRow(data)
+ html = @options.renderRow.call(@options, data, @)
else
if not selected
value = if @options.id then @options.id(data) else data.id
@@ -440,9 +454,20 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
- $(@el).find(".dropdown-toggle-text").text @options.toggleLabel
+ @updateLabel()
else
selectedObject
+ else if el.hasClass(INDETERMINATE_CLASS)
+ el.addClass ACTIVE_CLASS
+ el.removeClass INDETERMINATE_CLASS
+
+ if not value?
+ field.remove()
+
+ if not field.length and fieldName
+ @addInput(fieldName, value)
+
+ return selectedObject
else
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@@ -456,34 +481,45 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
- $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
+ @updateLabel(selectedObject, el)
if value?
if !field.length and fieldName
- # Create hidden input for form
- input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
- if @options.inputId?
- input = $(input)
- .attr('id', @options.inputId)
- @dropdown.before input
+ @addInput(fieldName, value)
else
field.val value
return selectedObject
- selectRowAtIndex: (index) ->
- selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
+ addInput: (fieldName, value)->
+ # Create hidden input for form
+ $input = $('<input>').attr('type', 'hidden')
+ .attr('name', fieldName)
+ .val(value)
+
+ if @options.inputId?
+ $input.attr('id', @options.inputId)
+
+ @dropdown.before $input
+
+ selectRowAtIndex: (e, index) ->
+ selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(#{index}) a"
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link
- $(selector, @dropdown).trigger "click"
+ $el = $(selector, @dropdown)
+
+ if $el.length
+ e.preventDefault()
+ e.stopImmediatePropagation()
+ $(selector, @dropdown)[0].click()
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
$input = @dropdown.find(".dropdown-input-field")
- selector = '.dropdown-content li:not(.divider)'
+ selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
@@ -511,8 +547,8 @@ class GitLabDropdown
return false
- if currentKeyCode is 13
- @selectRowAtIndex if currentIndex < 0 then 0 else currentIndex
+ if currentKeyCode is 13 and currentIndex isnt -1
+ @selectRowAtIndex e, currentIndex
removeArrayKeyEvent: ->
$('body').off 'keydown'
@@ -544,6 +580,9 @@ class GitLabDropdown
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
+ updateLabel: (selected = null, el = null) =>
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el)
+
$.fn.glDropdown = (opts) ->
return @.each ->
if (!$.data @, 'glDropdown')
diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/application.js.coffee
index e0f681acf0b..91f81a5d249 100644
--- a/app/assets/javascripts/graphs/application.js.coffee
+++ b/app/assets/javascripts/graphs/application.js.coffee
@@ -4,4 +4,5 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
+#= require Chart
#= require_tree .
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
index 6504e481102..c2447120033 100644
--- a/app/assets/javascripts/issuable.js.coffee
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -6,12 +6,18 @@ issuable_created = false
Issuable.initTemplates()
Issuable.initSearch()
Issuable.initChecks()
+ Issuable.initLabelFilterRemove()
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 class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;">
+ <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body">
+ <%= _.escape(label.title) %>
+ </a>
+ <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>">
+ <i class="fa fa-times"></i>
+ </button>
</span>
<% }); %>'
)
@@ -35,6 +41,21 @@ issuable_created = false
Issuable.filterResults $form
, 500)
+ initLabelFilterRemove: ->
+ $(document)
+ .off 'click', '.js-label-filter-remove'
+ .on 'click', '.js-label-filter-remove', (e) ->
+ $button = $(@)
+
+ # Remove the label input box
+ $('input[name="label_name[]"]')
+ .filter -> @value is $button.data('label')
+ .remove()
+
+ # Submit the form to get new data
+ Issuable.filterResults $('.filter-form')
+ $('.js-label-select').trigger('update.label')
+
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee
new file mode 100644
index 00000000000..16d023dd391
--- /dev/null
+++ b/app/assets/javascripts/issues-bulk-assignment.js.coffee
@@ -0,0 +1,109 @@
+class @IssuableBulkActions
+ constructor: (opts = {}) ->
+ # Set defaults
+ {
+ @container = $('.content')
+ @form = @getElement('.bulk-update')
+ @issues = @getElement('.issues-list .issue')
+ } = opts
+
+ @bindEvents()
+
+ getElement: (selector) ->
+ @container.find selector
+
+ bindEvents: ->
+ @form.off('submit').on('submit', @onFormSubmit.bind(@))
+
+ onFormSubmit: (e) ->
+ e.preventDefault()
+ @submit()
+
+ submit: ->
+ _this = @
+
+ xhr = $.ajax
+ url: @form.attr 'action'
+ method: @form.attr 'method'
+ dataType: 'JSON',
+ data: @getFormDataAsObject()
+
+ xhr.done (response, status, xhr) ->
+ location.reload()
+
+ xhr.fail ->
+ new Flash("Issue update failed")
+
+ xhr.always @onFormSubmitAlways.bind(@)
+
+ onFormSubmitAlways: ->
+ @form.find('[type="submit"]').enable()
+
+ getSelectedIssues: ->
+ @issues.has('.selected_issue:checked')
+
+ getLabelsFromSelection: ->
+ labels = []
+
+ @getSelectedIssues().map ->
+ _labels = $(@).data('labels')
+ if _labels
+ _labels.map (labelId) ->
+ labels.push(labelId) if labels.indexOf(labelId) is -1
+
+ labels
+
+ ###*
+ * Will return only labels that were marked previously and the user has unmarked
+ * @return {Array} Label IDs
+ ###
+ getUnmarkedIndeterminedLabels: ->
+ result = []
+ labelsToKeep = []
+
+ for el in @getElement('.labels-filter .is-indeterminate')
+ labelsToKeep.push $(el).data('labelId')
+
+ for id in @getLabelsFromSelection()
+ # Only the ones that we are not going to keep
+ result.push(id) if labelsToKeep.indexOf(id) is -1
+
+ result
+
+ ###*
+ * Simple form serialization, it will return just what we need
+ * Returns key/value pairs from form data
+ ###
+ getFormDataAsObject: ->
+ formData =
+ update:
+ state_event : @form.find('input[name="update[state_event]"]').val()
+ assignee_id : @form.find('input[name="update[assignee_id]"]').val()
+ milestone_id : @form.find('input[name="update[milestone_id]"]').val()
+ issues_ids : @form.find('input[name="update[issues_ids]"]').val()
+ add_label_ids : []
+ remove_label_ids : []
+
+ @getLabelsToApply().map (id) ->
+ formData.update.add_label_ids.push id
+
+ @getLabelsToRemove().map (id) ->
+ formData.update.remove_label_ids.push id
+
+ formData
+
+ getLabelsToApply: ->
+ labelIds = []
+ $labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
+
+ $labels.each (k, label) ->
+ labelIds.push $(label).val() if label
+
+ labelIds
+
+ ###*
+ * Just an alias of @getUnmarkedIndeterminedLabels
+ * @return {Array} Array of labels
+ ###
+ getLabelsToRemove: ->
+ @getUnmarkedIndeterminedLabels()
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 995fd768603..9ca88f1226e 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -1,5 +1,7 @@
class @LabelsSelect
constructor: ->
+ _this = @
+
$('.js-label-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
@@ -93,8 +95,11 @@ class @LabelsSelect
$newLabelCreateButton.enable()
if label.message?
+ errors = _.map label.message, (value, key) ->
+ "#{key} #{value[0]}"
+
$newLabelError
- .text label.message
+ .html errors.join("<br/>")
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
@@ -196,10 +201,18 @@ class @LabelsSelect
callback data
- renderRow: (label) ->
- removesAll = label.id is 0 or not label.id?
+ renderRow: (label, instance) ->
+ $li = $('<li>')
+ $a = $('<a href="#">')
selectedClass = []
+ removesAll = label.id is 0 or not label.id?
+
+ if $dropdown.hasClass('js-filter-bulk-update')
+ indeterminate = instance.indeterminateIds
+ if indeterminate.indexOf(label.id) isnt -1
+ selectedClass.push 'is-indeterminate'
+
if $form.find("input[type='hidden']\
[name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
@@ -230,17 +243,21 @@ class @LabelsSelect
else
colorEl = ''
- "<li>
- <a href='#' class='#{selectedClass.join(' ')}'>
- #{colorEl}
- #{_.escape(label.title)}
- </a>
- </li>"
- filterable: true
+ # We need to identify which items are actually labels
+ if label.id
+ selectedClass.push('label-item')
+ $a.attr('data-label-id', label.id)
+
+ $a.addClass(selectedClass.join(' '))
+ .html("#{colorEl} #{_.escape(label.title)}")
+
+ # Return generated html
+ $li.html($a).prop('outerHTML')
+ persistWhenHide: $dropdown.data('persistWhenHide')
search:
fields: ['title']
selectable: true
-
+ filterable: true
toggleLabel: (selected, el) ->
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
@@ -280,10 +297,19 @@ class @LabelsSelect
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
- saveLabelData()
+ if not $dropdown.hasClass 'js-filter-bulk-update'
+ saveLabelData()
+
+ if $dropdown.hasClass('js-filter-bulk-update')
+ # If we are persisting state we need the classes
+ if not @options.persistWhenHide
+ $dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
+ if $dropdown.hasClass('js-filter-bulk-update')
+ return
+
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is 'projects:merge_requests:index'
@@ -298,4 +324,31 @@ class @LabelsSelect
return
else
saveLabelData()
+
+ setIndeterminateIds: ->
+ if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
+ @indeterminateIds = _this.getIndeterminateIds()
)
+
+ @bindEvents()
+
+ bindEvents: ->
+ $('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
+
+ onSelectCheckboxIssue: ->
+ return if $('.selected_issue:checked').length
+
+ # Remove inputs
+ $('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
+
+ # Also restore button text
+ $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
+
+ getIndeterminateIds: ->
+ label_ids = []
+
+ $('.selected_issue:checked').each (i, el) ->
+ issue_id = $(el).data('id')
+ label_ids.push $("#issue_#{issue_id}").data('labels')
+
+ _.flatten(label_ids)
diff --git a/app/assets/javascripts/lib/common_utils.js.coffee b/app/assets/javascripts/lib/common_utils.js.coffee
new file mode 100644
index 00000000000..0000e99a650
--- /dev/null
+++ b/app/assets/javascripts/lib/common_utils.js.coffee
@@ -0,0 +1,24 @@
+((w) ->
+
+ jQuery.timefor = (time, suffix, expiredLabel) ->
+
+ return '' unless time
+
+ suffix or= 'remaining'
+ expiredLabel or= 'Past due'
+
+ jQuery.timeago.settings.allowFuture = yes
+
+ { suffixFromNow } = jQuery.timeago.settings.strings
+ jQuery.timeago.settings.strings.suffixFromNow = suffix
+
+ timefor = $.timeago time
+
+ if timefor.indexOf('ago') > -1
+ timefor = expiredLabel
+
+ jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow
+
+ return timefor
+
+) window
diff --git a/app/assets/javascripts/lib/datetime_utility.js.coffee b/app/assets/javascripts/lib/datetime_utility.js.coffee
index ad1d1c70481..948d6dbf07e 100644
--- a/app/assets/javascripts/lib/datetime_utility.js.coffee
+++ b/app/assets/javascripts/lib/datetime_utility.js.coffee
@@ -12,6 +12,13 @@
$el.attr('title', gl.utils.formatDate($el.attr('datetime')))
)
- $timeagoEls.timeago() if setTimeago
+ if setTimeago
+ $timeagoEls.timeago()
+ $timeagoEls.tooltip('destroy')
+
+ # Recreate with custom template
+ $timeagoEls.tooltip(
+ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ )
) window
diff --git a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
new file mode 100644
index 00000000000..80f9936b9c2
--- /dev/null
+++ b/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
@@ -0,0 +1,2 @@
+gl.emojiAliases = ->
+ JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 345a0e447af..648e1f3bde0 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -24,11 +24,21 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
- '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
+ '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>">
+ <span class="has-tooltip" data-container="body" title="<%= remaining %>">
+ <%= _.escape(title) %>
+ </span>
+ </a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
+ collapsedSidebarLabelTemplate = _.template(
+ '<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
+ <%= _.escape(title) %>
+ </span>'
+ )
+
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
@@ -83,7 +93,7 @@ class @MilestoneSelect
$selectbox.hide()
# display:block overrides the hide-collapse rule
- $value.removeAttr('style')
+ $value.css('display', '')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
@@ -118,12 +128,13 @@ class @MilestoneSelect
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
+ data.milestone.remaining = $.timefor data.milestone.due_date
$value.html(milestoneLinkTemplate(data.milestone))
- $sidebarCollapsedValue.find('span').text(data.milestone.title)
+ $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
else
$value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index f8151963fa7..ad216910c8d 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -162,13 +162,14 @@ class @Notes
renderNote: (note) ->
unless note.valid
if note.award
- flash = new Flash('You have already used this award emoji!', 'alert')
+ flash = new Flash('You have already awarded this emoji!', 'alert')
flash.pinTo('.header-content')
return
if note.award
- awardsHandler.addAwardToEmojiBar(note.note)
- awardsHandler.scrollToAwards()
+ votesBlock = $('.js-awards-block').eq 0
+ gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
+ gl.awardsHandler.scrollToAwards()
# render note if it not present in loaded list
# or skip if rendered
@@ -353,8 +354,7 @@ class @Notes
Called in response to clicking the edit note link
Replaces the note text with the note edit form
- Adds a hidden div with the original content of the note to fill the edit note form with
- if the user cancels
+ Adds a data attribute to the form with the original content of the note for cancellations
###
showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault()
@@ -370,6 +370,8 @@ class @Notes
done = ($noteText) ->
# Neat little trick to put the cursor at the end
noteTextVal = $noteText.val()
+ # Store the original note text in a data attribute to retrieve if a user cancels edit.
+ form.find('form.edit-note').data 'original-note', noteTextVal
$noteText.val('').val(noteTextVal);
new GLForm form
@@ -392,14 +394,16 @@ class @Notes
###
Called in response to clicking the edit note link
- Hides edit form
+ Hides edit form and restores the original note text to the editor textarea.
###
cancelEdit: (e) ->
e.preventDefault()
note = $(this).closest(".note")
+ form = note.find(".current-note-edit-form")
note.removeClass "is-editting"
- note.find(".current-note-edit-form")
- .removeClass("current-note-edit-form")
+ form.removeClass("current-note-edit-form")
+ # Replace markdown textarea text with original note text.
+ form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'))
###
Called in response to deleting a note of any kind.
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index 0ff83b7f0c8..8049c5c30e2 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -1,5 +1,5 @@
@Pager =
- init: (@limit = 0, preload, @disable = false) ->
+ init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
@loading = $('.loading').first()
if preload
@@ -19,6 +19,7 @@
@loading.hide()
success: (data) ->
Pager.append(data.count, data.html)
+ Pager.callback()
dataType: "json"
append: (count, html) ->
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
index 63dee4ed5d7..e48343a19b5 100644
--- a/app/assets/javascripts/project_new.js.coffee
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -7,12 +7,17 @@ class @ProjectNew
@toggleSettingsOnclick()
- toggleSettings: ->
- checked = $("#project_builds_enabled").prop("checked")
- if checked
- $('.builds-feature').show()
- else
- $('.builds-feature').hide()
+ toggleSettings: =>
+ @_showOrHide('#project_builds_enabled', '.builds-feature')
+ @_showOrHide('#project_merge_requests_enabled', '.merge-requests-feature')
toggleSettingsOnclick: ->
- $("#project_builds_enabled").on 'click', @toggleSettings
+ $('#project_builds_enabled, #project_merge_requests_enabled').on 'click', @toggleSettings
+
+ _showOrHide: (checkElement, container) ->
+ $container = $(container)
+
+ if $(checkElement).prop('checked')
+ $container.show()
+ else
+ $container.hide()
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index 6a7b4ad1db7..5eb915a51ea 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -20,8 +20,7 @@ class @SearchAutocomplete
@dropdown = @wrap.find('.dropdown')
@dropdownContent = @dropdown.find('.dropdown-content')
- @locationBadgeEl = @getElement('.search-location-badge')
- @locationText = @getElement('.location-text')
+ @locationBadgeEl = @getElement('.location-badge')
@scopeInputEl = @getElement('#scope')
@searchInput = @getElement('.search-input')
@projectInputEl = @getElement('#search_project_id')
@@ -133,7 +132,7 @@ class @SearchAutocomplete
scope: @scopeInputEl.val()
# Location badge
- _location: @locationText.text()
+ _location: @locationBadgeEl.text()
}
bindEvents: ->
@@ -143,23 +142,28 @@ class @SearchAutocomplete
@searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus
@clearInput.on 'click', @onClearInputClick
+ @locationBadgeEl.on 'click', =>
+ @searchInput.focus()
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
- if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
+ if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).closest('.search-form').length
@onSearchInputBlur()
enableAutocomplete: ->
# No need to enable anything if user is not logged in
return if !gon.current_user_id
- _this = @
- @loadingSuggestions = false
+ unless @dropdown.hasClass('open')
+ _this = @
+ @loadingSuggestions = false
- @dropdown.addClass('open')
- @searchInput.removeClass('disabled')
+ @dropdown
+ .addClass('open')
+ .trigger('shown.bs.dropdown')
+ @searchInput.removeClass('disabled')
onSearchInputKeyDown: =>
# Saves last length of the entered text
@@ -190,7 +194,7 @@ class @SearchAutocomplete
@disableAutocomplete()
else
# We should display the menu only when input is not empty
- @enableAutocomplete()
+ @enableAutocomplete() if e.keyCode isnt KEYCODE.ENTER
@wrap.toggleClass 'has-value', !!e.target.value
@@ -221,10 +225,8 @@ class @SearchAutocomplete
category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else ''
- html = "<span class='location-badge'>
- <i class='location-text'>#{category}#{value}</i>
- </span>"
- @locationBadgeEl.html(html)
+ badgeText = "#{category}#{value}"
+ @locationBadgeEl.text(badgeText).show()
@wrap.addClass('has-location-badge')
restoreOriginalState: ->
@@ -233,9 +235,8 @@ class @SearchAutocomplete
for input in inputs
@getElement("##{input}").val(@originalState[input])
-
if @originalState._location is ''
- @locationBadgeEl.empty()
+ @locationBadgeEl.hide()
else
@addLocationBadge(
value: @originalState._location
@@ -244,7 +245,7 @@ class @SearchAutocomplete
@dropdown.removeClass 'open'
badgePresent: ->
- @locationBadgeEl.children().length
+ @locationBadgeEl.length
resetSearchState: ->
inputs = Object.keys @originalState
@@ -257,7 +258,7 @@ class @SearchAutocomplete
@getElement("##{input}").val('')
removeLocationBadge: ->
- @locationBadgeEl.empty()
+ @locationBadgeEl.hide()
# Reset state
@resetSearchState()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index ccb42ab2168..c93bcf3ceec 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -10,14 +10,6 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@replyWithSelectedText()
return false
)
- Mousetrap.bind('j', =>
- @prevIssue()
- return false
- )
- Mousetrap.bind('k', =>
- @nextIssue()
- return false
- )
Mousetrap.bind('e', =>
@editIssue()
return false
@@ -29,16 +21,6 @@ class @ShortcutsIssuable extends ShortcutsNavigation
else
@enabledHelp.push('.hidden-shortcut.issues')
- prevIssue: ->
- $prevBtn = $('.prev-btn')
- if not $prevBtn.hasClass('disabled')
- Turbolinks.visit($prevBtn.attr('href'))
-
- nextIssue: ->
- $nextBtn = $('.next-btn')
- if not $nextBtn.hasClass('disabled')
- Turbolinks.visit($nextBtn.attr('href'))
-
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
index 1a430f3aa47..08d494aba9f 100644
--- a/app/assets/javascripts/subscription.js.coffee
+++ b/app/assets/javascripts/subscription.js.coffee
@@ -19,3 +19,8 @@ class @Subscription
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
btn.find('span').text(action)
@subscription_status.find('>div').toggleClass('hidden')
+
+ if btn.attr('data-original-title')
+ btn.tooltip('hide')
+ .attr('data-original-title', action)
+ .tooltip('fixTitle')
diff --git a/app/assets/javascripts/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee
new file mode 100644
index 00000000000..6deb902c8de
--- /dev/null
+++ b/app/assets/javascripts/u2f/authenticate.js.coffee
@@ -0,0 +1,63 @@
+# Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
+#
+# State Flow #1: setup -> in_progress -> authenticated -> POST to server
+# State Flow #2: setup -> in_progress -> error -> setup
+
+class @U2FAuthenticate
+ constructor: (@container, u2fParams) ->
+ @appId = u2fParams.app_id
+ @challenges = u2fParams.challenges
+ @signRequests = u2fParams.sign_requests
+
+ start: () =>
+ if U2FUtil.isU2FSupported()
+ @renderSetup()
+ else
+ @renderNotSupported()
+
+ authenticate: () =>
+ u2f.sign(@appId, @challenges, @signRequests, (response) =>
+ if response.errorCode
+ error = new U2FError(response.errorCode)
+ @renderError(error);
+ else
+ @renderAuthenticated(JSON.stringify(response))
+ , 10)
+
+ #############
+ # Rendering #
+ #############
+
+ templates: {
+ "notSupported": "#js-authenticate-u2f-not-supported",
+ "setup": '#js-authenticate-u2f-setup',
+ "inProgress": '#js-authenticate-u2f-in-progress',
+ "error": '#js-authenticate-u2f-error',
+ "authenticated": '#js-authenticate-u2f-authenticated'
+ }
+
+ renderTemplate: (name, params) =>
+ templateString = $(@templates[name]).html()
+ template = _.template(templateString)
+ @container.html(template(params))
+
+ renderSetup: () =>
+ @renderTemplate('setup')
+ @container.find('#js-login-u2f-device').on('click', @renderInProgress)
+
+ renderInProgress: () =>
+ @renderTemplate('inProgress')
+ @authenticate()
+
+ renderError: (error) =>
+ @renderTemplate('error', {error_message: error.message()})
+ @container.find('#js-u2f-try-again').on('click', @renderSetup)
+
+ renderAuthenticated: (deviceResponse) =>
+ @renderTemplate('authenticated')
+ # Prefer to do this instead of interpolating using Underscore templates
+ # because of JSON escaping issues.
+ @container.find("#js-device-response").val(deviceResponse)
+
+ renderNotSupported: () =>
+ @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/error.js.coffee b/app/assets/javascripts/u2f/error.js.coffee
new file mode 100644
index 00000000000..1a2fc3e757f
--- /dev/null
+++ b/app/assets/javascripts/u2f/error.js.coffee
@@ -0,0 +1,13 @@
+class @U2FError
+ constructor: (@errorCode) ->
+ @httpsDisabled = (window.location.protocol isnt 'https:')
+ console.error("U2F Error Code: #{@errorCode}")
+
+ message: () =>
+ switch
+ when (@errorCode is u2f.ErrorCodes.BAD_REQUEST and @httpsDisabled)
+ "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
+ when @errorCode is u2f.ErrorCodes.DEVICE_INELIGIBLE
+ "This device has already been registered with us."
+ else
+ "There was a problem communicating with your device."
diff --git a/app/assets/javascripts/u2f/register.js.coffee b/app/assets/javascripts/u2f/register.js.coffee
new file mode 100644
index 00000000000..74472cfa120
--- /dev/null
+++ b/app/assets/javascripts/u2f/register.js.coffee
@@ -0,0 +1,63 @@
+# Register U2F (universal 2nd factor) devices for users to authenticate with.
+#
+# State Flow #1: setup -> in_progress -> registered -> POST to server
+# State Flow #2: setup -> in_progress -> error -> setup
+
+class @U2FRegister
+ constructor: (@container, u2fParams) ->
+ @appId = u2fParams.app_id
+ @registerRequests = u2fParams.register_requests
+ @signRequests = u2fParams.sign_requests
+
+ start: () =>
+ if U2FUtil.isU2FSupported()
+ @renderSetup()
+ else
+ @renderNotSupported()
+
+ register: () =>
+ u2f.register(@appId, @registerRequests, @signRequests, (response) =>
+ if response.errorCode
+ error = new U2FError(response.errorCode)
+ @renderError(error);
+ else
+ @renderRegistered(JSON.stringify(response))
+ , 10)
+
+ #############
+ # Rendering #
+ #############
+
+ templates: {
+ "notSupported": "#js-register-u2f-not-supported",
+ "setup": '#js-register-u2f-setup',
+ "inProgress": '#js-register-u2f-in-progress',
+ "error": '#js-register-u2f-error',
+ "registered": '#js-register-u2f-registered'
+ }
+
+ renderTemplate: (name, params) =>
+ templateString = $(@templates[name]).html()
+ template = _.template(templateString)
+ @container.html(template(params))
+
+ renderSetup: () =>
+ @renderTemplate('setup')
+ @container.find('#js-setup-u2f-device').on('click', @renderInProgress)
+
+ renderInProgress: () =>
+ @renderTemplate('inProgress')
+ @register()
+
+ renderError: (error) =>
+ @renderTemplate('error', {error_message: error.message()})
+ @container.find('#js-u2f-try-again').on('click', @renderSetup)
+
+ renderRegistered: (deviceResponse) =>
+ @renderTemplate('registered')
+ # Prefer to do this instead of interpolating using Underscore templates
+ # because of JSON escaping issues.
+ @container.find("#js-device-response").val(deviceResponse)
+
+ renderNotSupported: () =>
+ @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/util.js.coffee.erb b/app/assets/javascripts/u2f/util.js.coffee.erb
new file mode 100644
index 00000000000..d59341c38b9
--- /dev/null
+++ b/app/assets/javascripts/u2f/util.js.coffee.erb
@@ -0,0 +1,15 @@
+# Helper class for U2F (universal 2nd factor) device registration and authentication.
+
+class @U2FUtil
+ @isU2FSupported: ->
+ if @testMode
+ true
+ else
+ gon.u2f.browser_supports_u2f
+
+ @enableTestMode: ->
+ @testMode = true
+
+<% if Rails.env.test? %>
+U2FUtil.enableTestMode();
+<% end %>
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
index 70614396a4e..29dad21faed 100644
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ b/app/assets/javascripts/user_tabs.js.coffee
@@ -122,6 +122,9 @@ class @UserTabs
@parentEl.find(tabSelector).html(data.html)
@loaded[action] = true
+ # Fix tooltips
+ gl.utils.localTimeAgo($('.js-timeago', tabSelector))
+
loadActivities: (source) ->
return if @loaded['activity'] is true
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 519618aa617..88246b0feb8 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -95,7 +95,7 @@ class @UsersSelect
data: (term, callback) =>
isAuthorFilter = $('.js-author-search')
- @users term, term is '' and isAuthorFilter, (users) =>
+ @users term, (users) =>
if term.length is 0
showDivider = 0
@@ -149,7 +149,7 @@ class @UsersSelect
hidden: (e) ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
- $value.removeAttr('style')
+ $value.css('display', '')
clicked: (user) ->
page = $('body').data 'page'
@@ -221,7 +221,7 @@ class @UsersSelect
multiple: $(select).hasClass('multiselect')
minimumInputLength: 0
query: (query) =>
- @users query.term, @projectId?, (users) =>
+ @users query.term, (users) =>
data = { results: users }
if query.term.length == 0
@@ -304,7 +304,7 @@ class @UsersSelect
# Return users list. Filtered by query
# Only active users retrieved
- users: (query, fromProject, callback) =>
+ users: (query, callback) =>
url = @buildUrl(@usersPath)
$.ajax(
@@ -313,7 +313,7 @@ class @UsersSelect
search: query
per_page: 20
active: true
- project_id: @projectId if fromProject
+ project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
author_id: @authorId
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 6981f834d30..fab96404a6c 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -61,6 +61,11 @@
margin-bottom: -$gl-padding;
}
+ &.content-component-block {
+ padding: 11px 0;
+ background-color: $white-light;
+ }
+
.title {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 467f3b35d74..1e3083cce55 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -79,6 +79,23 @@
@include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
}
+@mixin btn-with-margin {
+ margin-left: $btn-side-margin;
+ float: left;
+
+ &.inline {
+ float: none;
+ }
+
+ &.btn-sm {
+ margin-left: $btn-sm-side-margin;
+ }
+
+ &.btn-xs {
+ margin-left: $btn-xs-side-margin;
+ }
+}
+
.btn {
@include btn-default;
@include btn-white;
@@ -142,15 +159,9 @@
}
&.btn-grouped {
- margin-right: 7px;
- float: left;
- &:last-child {
- margin-right: 0;
- }
- &.btn-xs {
- margin-right: 3px;
- }
+ @include btn-with-margin;
}
+
&.disabled {
pointer-events: auto !important;
}
@@ -192,11 +203,7 @@
.btn-group {
&.btn-grouped {
- margin-right: 7px;
- float: left;
- &:last-child {
- margin-right: 0;
- }
+ @include btn-with-margin;
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 93c63c69843..d4d579a083d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -122,10 +122,9 @@
a {
display: block;
position: relative;
- padding-left: 10px;
- padding-right: 10px;
+ padding: 5px 10px;
color: $dropdown-link-color;
- line-height: 34px;
+ line-height: initial;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
@@ -162,6 +161,16 @@
}
}
+.dropdown-menu-large {
+ width: 340px;
+}
+
+.dropdown-menu-no-wrap {
+ a {
+ white-space: normal;
+ }
+}
+
.dropdown-menu-full-width {
width: 100%;
}
@@ -232,13 +241,11 @@
a {
padding-left: 25px;
- &.is-active {
+ &.is-indeterminate, &.is-active {
&::before {
- content: "\f00c";
position: absolute;
left: 5px;
- top: 50%;
- margin-top: -7px;
+ top: 8px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
@@ -246,6 +253,14 @@
-moz-osx-font-smoothing: grayscale;
}
}
+
+ &.is-indeterminate::before {
+ content: "\f068";
+ }
+
+ &.is-active::before {
+ content: "\f00c";
+ }
}
}
@@ -525,3 +540,14 @@
background-color: $calendar-unselectable-bg;
}
}
+
+.dropdown-menu-inner-title {
+ display: block;
+ color: $gl-title-color;
+ font-weight: 600;
+}
+
+.dropdown-menu-inner-content {
+ display: block;
+ color: $gl-placeholder-color;
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 46acc3b772f..43d55661541 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -76,6 +76,7 @@ label {
.form-control {
@include box-shadow(none);
border-radius: 3px;
+ padding: $gl-vert-padding $gl-input-padding;
}
.select-wrapper {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 16cf394c426..2540ff497f2 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -22,17 +22,17 @@
&:hover {
background-color: $color-dark;
a {
- color: #fff;
+ color: $white-light;
h3 {
- color: #fff;
+ color: $white-light;
}
}
}
}
.collapse-nav a {
- color: #fff;
+ color: $white-light;
background: $color;
}
@@ -45,7 +45,7 @@
&:hover {
background-color: $color-dark;
- color: #fff;
+ color: $white-light;
text-decoration: none;
}
}
@@ -63,10 +63,20 @@
color: $color-light;
}
+ path,
+ polygon {
+ fill: $color-light;
+ }
+
.count {
color: $color-light;
background: $color-dark;
}
+
+ svg {
+ position: relative;
+ top: 3px;
+ }
}
&.separate-item {
@@ -74,7 +84,7 @@
}
&.active a {
- color: #fff;
+ color: $white-light;
background: $color-dark;
&.no-highlight {
@@ -82,15 +92,23 @@
}
i {
- color: #fff
+ color: $white-light
+ }
+
+ path,
+ polygon {
+ fill: $white-light;
}
}
}
}
}
-$theme-blue: #2980b9;
$theme-charcoal: #3d454d;
+$theme-charcoal-dark: #383f45;
+$theme-charcoal-text: #b9bbbe;
+
+$theme-blue: #2980b9;
$theme-graphite: #666;
$theme-gray: #373737;
$theme-green: #019875;
@@ -102,7 +120,7 @@ body {
}
&.ui_charcoal {
- @include gitlab-theme(#d6d7d9, #485157, $theme-charcoal, #353b41);
+ @include gitlab-theme($theme-charcoal-text, #485157, $theme-charcoal, $theme-charcoal-dark);
}
&.ui_graphite {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 0da96c4017d..b8d4233537b 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -79,6 +79,10 @@ header {
&.header-collapsed {
padding: 0 16px;
+
+ .side-nav-toggle {
+ display: block;
+ }
}
.side-nav-toggle {
@@ -86,6 +90,7 @@ header {
position: absolute;
left: -10px;
margin: 6px 0;
+ font-size: 18px;
padding: 6px 10px;
border: none;
background-color: $background-color;
@@ -97,10 +102,6 @@ header {
&:focus {
outline: none;
}
-
- @media (max-width: $screen-xs-min) {
- display: block;
- }
}
}
@@ -171,31 +172,21 @@ header {
}
}
-@mixin collapsed-header {
- margin-left: $sidebar_collapsed_width;
-}
-
.header-collapsed {
- margin-left: $sidebar_collapsed_width;
-
- @media (min-width: $screen-md-min) {
- @include collapsed-header;
- }
+ margin-left: 0;
- @media (max-width: $screen-xs-min) {
- margin-left: 0;
+ .header-content {
+ padding-left: 30px;
+ transition-duration: .3s;
}
}
.header-expanded {
- margin-left: $sidebar_collapsed_width;
+ margin-left: 0;
- @media (min-width: $screen-md-min) {
+ .header-content {
margin-left: $sidebar_width;
- }
-
- @media (max-width: $screen-xs-min) {
- margin-left: 0;
+ transition-duration: .3s;
}
}
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 525ed81b059..30a5b837d69 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -2,6 +2,7 @@
font-family: $regular_font;
font-size: $font-size-base;
+ &.ui-datepicker,
&.ui-datepicker-inline {
border: 1px solid #ddd;
padding: 10px;
@@ -10,6 +11,25 @@
.ui-datepicker-header {
background: #fff;
border-color: #ddd;
+
+ .ui-datepicker-prev,
+ .ui-datepicker-next {
+ top: 4px;
+ }
+
+ .ui-datepicker-prev {
+ left: 2px;
+ }
+
+ .ui-datepicker-next {
+ right: 2px;
+ }
+
+ .ui-state-hover {
+ background: transparent;
+ border: 0;
+ cursor: pointer;
+ }
}
.ui-datepicker-calendar td a {
@@ -36,21 +56,18 @@
}
.ui-state-highlight {
- border: 1px solid #eee;
- background: #eee;
+ border: 0;
+ background: transparent;
}
- .ui-state-active {
- border: 1px solid $gl-primary;
- background: $gl-primary;
- color: #fff;
- }
-
- .ui-state-hover,
- .ui-state-focus {
- border: 1px solid $row-hover;
- background: $row-hover;
- color: #333;
+ .ui-datepicker-calendar {
+ .ui-state-active,
+ .ui-state-hover,
+ .ui-state-focus {
+ border: 1px solid $gl-primary;
+ background: $gl-primary;
+ color: #fff;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index b17c8bcbb1e..b34ec16cdba 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -137,10 +137,30 @@ ul.content-list {
padding-top: 1px;
float: right;
- .btn {
- padding: 10px 14px;
+ > .btn,
+ > .btn-group {
+ margin-right: $gl-padding-top;
+ display: inline-block;
+ margin-top: 4px;
+ margin-bottom: 4px;
+
+ &:last-child {
+ margin-right: 0;
+ }
}
}
+
+ // When dragging a list item
+ &.ui-sortable-helper {
+ border-bottom: none;
+ }
+
+ &.list-placeholder {
+ background-color: $gray-light;
+ border: dotted 1px $gray-dark;
+ margin: 1px 0;
+ min-height: 30px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 250d6309291..828e7224231 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -2,18 +2,10 @@
* Generic mixins
*/
@mixin box-shadow($shadow) {
- -webkit-box-shadow: $shadow;
- -moz-box-shadow: $shadow;
- -ms-box-shadow: $shadow;
- -o-box-shadow: $shadow;
box-shadow: $shadow;
}
@mixin border-radius($radius) {
- -webkit-border-radius: $radius;
- -moz-border-radius: $radius;
- -ms-border-radius: $radius;
- -o-border-radius: $radius;
border-radius: $radius;
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index bd531f8376b..d4e5cc819a4 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -66,10 +66,6 @@
display: none;
}
- %ul.notes .note-role, .note-actions {
- display: none;
- }
-
.nav-links, .nav-links {
li a {
font-size: 14px;
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 7eb7a8e4544..a036799e15a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -41,8 +41,7 @@
a {
display: inline-block;
- padding: 14px;
- padding-top: $gl-padding;
+ padding: $gl-btn-padding;
padding-bottom: 11px;
margin-bottom: -1px;
font-size: 15px;
@@ -67,6 +66,27 @@
color: #78a;
}
}
+
+ &.sub-nav {
+ background-color: $background-color;
+
+ .container-fluid {
+ background-color: $background-color;
+ }
+
+ li {
+
+ a {
+ margin: 0;
+ padding: 11px 10px 9px;
+ }
+
+ &.active a {
+ border-bottom: none;
+ color: $link-underline-blue;
+ }
+ }
+ }
}
.top-area {
@@ -81,6 +101,10 @@
width: 50%;
line-height: 28px;
+ &.wiki-page {
+ padding: 16px 10px 11px;
+ }
+
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
@@ -104,6 +128,10 @@
margin-bottom: 0;
border-bottom: none;
+ li a {
+ padding: 16px 10px 11px;
+ }
+
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
@@ -143,6 +171,7 @@
> form {
display: inline-block;
margin-top: -1px;
+ margin-bottom: 12px;
}
.icon-label {
@@ -179,7 +208,7 @@
@media (max-width: $screen-xs-max) {
padding-bottom: 0;
-
+ width: 100%;
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control {
margin: 0 0 10px;
display: block;
@@ -210,16 +239,6 @@
margin: 0;
}
}
-
- /* Small devices (tablets, 768px and lower) */
- @media (max-width: $screen-sm-max) {
- width: 100%;
- text-align: left;
-
- input {
- width: 300px;
- }
- }
}
}
@@ -276,6 +295,19 @@
border-bottom: none;
height: 51px;
+ svg {
+ position: relative;
+ top: 2px;
+ margin-right: 2px;
+ height: 15px;
+ width: auto;
+
+ path,
+ polygon {
+ fill: $layout-link-gray;
+ }
+ }
+
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
@@ -297,9 +329,17 @@
}
&.active {
+
a, i {
color: $black;
}
+
+ svg {
+ path,
+ polygon {
+ fill: $black;
+ }
+ }
}
.badge {
@@ -309,8 +349,8 @@
}
.nav-control {
- .fade-right {
+ .fade-right {
@media (min-width: $screen-xs-max) {
right: 67px;
}
@@ -321,6 +361,24 @@
}
}
+.scrolling-tabs-container {
+ position: relative;
+
+ .nav-links {
+ @include scrolling-links();
+
+ .fade-right {
+ @include fade(left, rgba(255, 255, 255, 0.4), $background-color);
+ right: 0;
+ }
+
+ .fade-left {
+ @include fade(right, rgba(255, 255, 255, 0.4), $background-color);
+ left: 0;
+ }
+ }
+}
+
.nav-block {
position: relative;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 6efc6ec1e4b..f242706ebe4 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -8,7 +8,7 @@
background: #fff;
border-color: $input-border;
height: 35px;
- padding: $gl-vert-padding $gl-btn-padding;
+ padding: $gl-vert-padding $gl-input-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
border-radius: $border-radius-base;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 67f491b6d9c..06a688690f8 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,11 +1,3 @@
-#logo {
- z-index: 2;
- position: absolute;
- width: 58px;
- cursor: pointer;
- margin-top: 8px;
-}
-
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
@@ -20,12 +12,6 @@
height: 100%;
transition-duration: .3s;
}
-
- .gitlab-text-container-link {
- z-index: 1;
- position: absolute;
- left: 0;
- }
}
.sidebar-wrapper {
@@ -50,55 +36,21 @@
.sidebar-wrapper {
.header-logo {
- border-bottom: 1px solid transparent;
- float: left;
height: $header-height;
+ padding: 8px 26px;
width: $sidebar_width;
position: fixed;
z-index: 999;
overflow: hidden;
transition-duration: .3s;
- a {
- float: left;
- height: $header-height;
- width: 100%;
- padding-left: 22px;
- overflow: hidden;
- outline: none;
- transition-duration: .3s;
-
- img {
- width: 36px;
- height: 36px;
- }
-
- #tanuki-logo, img {
- float: left;
- }
-
- .gitlab-text-container {
- width: 230px;
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 50px;
- font-size: 19px;
- line-height: 50px;
- font-weight: normal;
- }
- }
- }
-
&:hover {
background-color: #eee;
}
}
.sidebar-user {
- padding: 7px 22px;
+ padding: 15px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
@@ -126,8 +78,8 @@
.nav-sidebar {
- margin-top: 14 + $header-height;
- margin-bottom: 100px;
+ margin-top: 22 + $header-height;
+ margin-bottom: 116px;
transition-duration: .3s;
list-style: none;
overflow: hidden;
@@ -145,13 +97,12 @@
}
a {
- padding: 7px 15px;
+ text-align: center;
+ padding: 8px;
font-size: $gl-font-size;
- line-height: 24px;
color: $gray;
display: block;
text-decoration: none;
- padding-left: 23px;
font-weight: normal;
outline: none;
@@ -164,16 +115,13 @@
}
i {
- width: 16px;
- color: $gray-light;
- margin-right: 13px;
+ font-size: 16px;
}
- .count {
- float: right;
- background: #eee;
- padding: 0 8px;
- @include border-radius(6px);
+ .nav-link-text {
+ margin-top: 3px;
+ font-size: 13px;
+ line-height: 18px;
}
&.back-link i {
@@ -217,25 +165,14 @@
}
.page-sidebar-collapsed {
- padding-left: $sidebar_collapsed_width;
-
- @media (max-width: $screen-xs-min) {
- padding-left: 0;
- }
+ padding-left: 0;
.sidebar-wrapper {
- width: $sidebar_collapsed_width;
-
- @media (max-width: $screen-xs-min) {
- width: 0;
- }
+ width: 0;
.header-logo {
- width: $sidebar_collapsed_width;
-
- @media (max-width: $screen-xs-min) {
- width: 0;
- }
+ width: 0;
+ padding: 8px 0;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
@@ -246,6 +183,10 @@
}
}
+ #logo {
+ display: none;
+ }
+
.nav-sidebar {
width: $sidebar_collapsed_width;
@@ -261,44 +202,23 @@
}
.collapse-nav a {
- width: $sidebar_collapsed_width;
-
- @media (max-width: $screen-xs-min) {
- width: 0;
- }
+ 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;
- }
+ 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 {
- padding-left: $sidebar_collapsed_width;
-
- @media (min-width: $screen-md-min) {
- padding-left: $sidebar_width;
- }
+ padding-left: $sidebar_width;
@media (max-width: $screen-xs-min) {
padding-left: 0;
@@ -328,7 +248,7 @@
}
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
- padding-right: 62px;
+ padding-right: 90px;
}
@media (min-width: $screen-md-min) {
@@ -353,7 +273,9 @@
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
- padding-right: $sidebar_collapsed_width;
+ &:not(.build-sidebar) {
+ padding-right: $sidebar_collapsed_width;
+ }
}
@media (min-width: $screen-md-min) {
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 29501069d27..0b0bd80c326 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -5,7 +5,7 @@
padding: 0;
.timeline-entry {
- padding: $gl-padding $gl-btn-padding;
+ padding: $gl-padding $gl-btn-padding 11px;
border-color: $table-border-color;
color: $gl-gray;
border-bottom: 1px solid $border-white-light;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 6a45c34ccbb..e3154657c54 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -192,3 +192,8 @@
.text-info:hover {
color: $brand-info;
}
+
+// Prevent datetimes on tooltips to break into two lines
+.local-timeago {
+ white-space: nowrap;
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f253da814bc..847b2f80bdf 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -2,7 +2,7 @@
* Layout
*/
$sidebar_collapsed_width: 62px;
-$sidebar_width: 220px;
+$sidebar_width: 90px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
@@ -57,6 +57,7 @@ $code_line_height: 1.5;
*/
$gl-padding: 16px;
$gl-btn-padding: 10px;
+$gl-input-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
@@ -79,6 +80,9 @@ $provider-btn-not-active-color: #4688f1;
$link-underline-blue: #4a8bee;
$layout-link-gray: #7e7c7c;
$todo-alert-blue: #428bca;
+$btn-side-margin: 10px;
+$btn-sm-side-margin: 7px;
+$btn-xs-side-margin: 5px;
/*
* Color schema
@@ -121,7 +125,7 @@ $border-white-normal: #d6dae2;
$border-white-dark: #c6cacf;
$border-gray-light: #dcdcdc;
-$border-gray-normal: rgba(0, 0, 0, 0.10);
+$border-gray-normal: #d7d7d7;
$border-gray-dark: #c6cacf;
$border-green-light: #2faa60;
@@ -256,3 +260,6 @@ $calendar-header-color: #b8b8b8;
$calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: #faf9f9;
+
+$ci-output-bg: #1d1f21;
+$ci-text-color: #c5c8c6;
diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss
index 001994db97b..7f645d3089d 100644
--- a/app/assets/stylesheets/mailers/repository_push_email.scss
+++ b/app/assets/stylesheets/mailers/repository_push_email.scss
@@ -1,5 +1,15 @@
@import "framework/variables";
+// This file is largely copied from `highlight/white.scss`, but modified to
+// avoid all descendant selectors (`table td`). This is because the CSS inlining
+// we use performs dramatically worse on descendant selectors than the
+// alternatives.
+// <https://gitlab.com/gitlab-org/gitlab-ee/issues/490#note_12283632>
+//
+// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of
+// preference): plain class selectors, type (element name) selectors, or
+// explicit child selectors.
+
table.code {
width: 100%;
font-family: monospace;
@@ -11,33 +21,162 @@ table.code {
-premailer-cellspacing: 0;
-premailer-width: 100%;
- td {
+ > tr > td {
line-height: $code_line_height;
font-family: monospace;
font-size: $code_font_size;
+
+ &.diff-line-num {
+ margin: 0;
+ padding: 0;
+ border: none;
+ padding: 0 5px;
+ border-right: 1px solid;
+ text-align: right;
+ min-width: 35px;
+ max-width: 50px;
+ width: 35px;
+ }
+
+ &.line_content {
+ display: block;
+ margin: 0;
+ padding: 0 0.5em;
+ border: none;
+ white-space: pre;
+ }
}
+}
+
+.line-numbers, .diff-line-num {
+ background-color: $background-color;
+}
+
+.diff-line-num, .diff-line-num a {
+ color: $black-transparent;
+}
- td.diff-line-num {
- margin: 0;
- padding: 0;
- border: none;
- background: $background-color;
- color: rgba(0, 0, 0, 0.3);
- padding: 0 5px;
- border-right: 1px solid $border-color;
- text-align: right;
- min-width: 35px;
- max-width: 50px;
- width: 35px;
+pre.code, .diff-line-num {
+ border-color: $table-border-gray;
+}
+
+.code.white, pre.code, .line_content {
+ background-color: #fff;
+ color: #333;
+}
+
+.diff-line-num {
+ &.old {
+ background-color: $line-number-old;
+ border-color: $line-removed-dark;
}
- td.line_content {
- display: block;
- margin: 0;
- padding: 0 0.5em;
- border: none;
- white-space: pre;
+ &.new {
+ 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-color: $line-removed;
+
+ > .line > span.idiff, > .line > span > span.idiff {
+ background-color: $line-removed-dark;
+ }
+ }
+
+ &.new {
+ background-color: $line-added;
+
+ > .line > span.idiff, > .line > span > span.idiff {
+ background-color: $line-added-dark;
+ }
+ }
+
+ &.match {
+ color: $black-transparent;
+ background-color: $match-line;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
+ }
+}
+
+pre > .hll {
+ background-color: #f8eec7 !important;
+}
+
+span.highlight_word {
+ background-color: #fafe3d !important;
}
-@import "highlight/white";
+.hll { background-color: #f8f8f8 }
+.c { color: #998; font-style: italic; }
+.err { color: #a61717; background-color: #e3d2d2; }
+.k { font-weight: bold; }
+.o { font-weight: bold; }
+.cm { color: #998; font-style: italic; }
+.cp { color: #999; font-weight: bold; }
+.c1 { color: #998; font-style: italic; }
+.cs { color: #999; font-weight: bold; font-style: italic; }
+.gd { color: #000; background-color: #fdd; }
+.gd .x { color: #000; background-color: #faa; }
+.ge { font-style: italic; }
+.gr { color: #a00; }
+.gh { color: #999; }
+.gi { color: #000; background-color: #dfd; }
+.gi .x { color: #000; background-color: #afa; }
+.go { color: #888; }
+.gp { color: #555; }
+.gs { font-weight: bold; }
+.gu { color: #800080; font-weight: bold; }
+.gt { color: #a00; }
+.kc { font-weight: bold; }
+.kd { font-weight: bold; }
+.kn { font-weight: bold; }
+.kp { font-weight: bold; }
+.kr { font-weight: bold; }
+.kt { color: #458; font-weight: bold; }
+.m { color: #099; }
+.s { color: #d14; }
+.n { color: #333; }
+.na { color: teal; }
+.nb { color: #0086b3; }
+.nc { color: #458; font-weight: bold; }
+.no { color: teal; }
+.ni { color: purple; }
+.ne { color: #900; font-weight: bold; }
+.nf { color: #900; font-weight: bold; }
+.nn { color: #555; }
+.nt { color: navy; }
+.nv { color: teal; }
+.ow { font-weight: bold; }
+.w { color: #bbb; }
+.mf { color: #099; }
+.mh { color: #099; }
+.mi { color: #099; }
+.mo { color: #099; }
+.sb { color: #d14; }
+.sc { color: #d14; }
+.sd { color: #d14; }
+.s2 { color: #d14; }
+.se { color: #d14; }
+.sh { color: #d14; }
+.si { color: #d14; }
+.sx { color: #d14; }
+.sr { color: #009926; }
+.s1 { color: #d14; }
+.ss { color: #990073; }
+.bp { color: #999; }
+.vc { color: teal; }
+.vg { color: teal; }
+.vi { color: teal; }
+.il { color: #099; }
+.gc { color: #999; background-color: #eaf2f5; }
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index 0a13a7e0b54..fc12964872d 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -6,19 +6,19 @@ p.details {
font-style: italic;
color: #777
}
-.footer p {
+.footer > p {
font-size: small;
color: #777
}
pre.commit-message {
white-space: pre-wrap;
}
-.file-stats a {
+.file-stats > a {
text-decoration: none;
-}
-.file-stats .new-file {
- color: #090;
-}
-.file-stats .deleted-file {
- color: #b00;
+ > .new-file {
+ color: #090;
+ }
+ > .deleted-file {
+ color: #b00;
+ }
}
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 37bf38fa65d..6211f3a52eb 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -1,6 +1,4 @@
.awards {
- line-height: 34px;
-
.emoji-icon {
width: 20px;
height: 20px;
@@ -9,8 +7,6 @@
.emoji-menu {
position: absolute;
- top: 100%;
- left: 0;
margin-top: 3px;
z-index: 1000;
min-width: 160px;
@@ -23,7 +19,12 @@
opacity: 0;
transform: scale(.2);
transform-origin: 0 -45px;
- transition: all .3s cubic-bezier(.87,-.41,.19,1.44);
+ transition: .3s cubic-bezier(.87,-.41,.19,1.44);
+ transition-property: transform, opacity;
+
+ &.is-aligned-right {
+ transform-origin: 100% -45px;
+ }
&.is-visible {
pointer-events: all;
@@ -94,20 +95,30 @@
.award-control {
margin-right: 5px;
+ margin-bottom: 5px;
padding-left: 5px;
padding-right: 5px;
line-height: 20px;
outline: 0;
+ &:hover,
&.active,
&:active {
- background-color: $white-dark;
+ background-color: $row-hover;
+ border-color: $row-hover-border;
box-shadow: none;
outline: 0;
}
+ &.btn {
+ &:focus {
+ outline: 0;
+ }
+ }
+
&.is-loading {
- .award-control-icon {
+ .award-control-icon-normal,
+ .emoji-icon {
display: none;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index aa41565f812..e8f1935d239 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -3,12 +3,7 @@
background: #111;
color: #fff;
font-family: $monospace_font;
- white-space: pre;
- white-space: pre-wrap; /* css-3 */
- white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
- white-space: -pre-wrap; /* Opera 4-6 */
- white-space: -o-pre-wrap; /* Opera 7 */
- word-wrap: break-word; /* Internet Explorer 5.5+ */
+ white-space: pre-wrap;
overflow: auto;
overflow-y: hidden;
font-size: 12px;
@@ -58,37 +53,92 @@
left: 70px;
}
}
+}
- .build-widget {
- padding: 10px;
- background: $background-color;
- margin-bottom: 20px;
- border-radius: 4px;
+.build-header {
+ position: relative;
+ padding-right: 40px;
- .title {
- margin-top: 0;
- color: #666;
- line-height: 1.5;
- }
- .attr-name {
- color: #777;
- }
+ @media (min-width: $screen-sm-min) {
+ padding-right: 0;
}
- .alert-disabled {
- background: $background-color;
+ a {
+ color: $gl-gray;
- a {
- color: #3084bb !important;
+ &:hover {
+ color: $gl-link-color;
+ text-decoration: none;
}
}
+
+ code {
+ color: $code-color;
+ }
+
+ .avatar {
+ float: none;
+ margin-right: 2px;
+ margin-left: 2px;
+ }
}
table.builds {
-
.build-link {
a {
color: $gl-dark-link-color;
}
}
}
+
+.build-trace {
+ background: $ci-output-bg;
+ color: $ci-text-color;
+ white-space: pre;
+ overflow-x: auto;
+ font-size: 12px;
+
+ .fa-refresh {
+ font-size: 24px;
+ }
+
+ .bash {
+ display: block;
+ }
+}
+
+.right-sidebar.build-sidebar {
+ padding-top: $gl-padding;
+ padding-bottom: $gl-padding;
+
+ &.right-sidebar-collapsed {
+ display: none;
+ }
+
+ .block {
+ width: 100%;
+ }
+
+ .build-sidebar-header {
+ padding-top: 0;
+
+ .gutter-toggle {
+ margin-top: 0;
+ }
+ }
+}
+
+.build-detail-row {
+ margin-bottom: 5px;
+}
+
+.build-light-text {
+ color: $gl-placeholder-color;
+}
+
+.build-gutter-toggle {
+ position: absolute;
+ top: 50%;
+ right: 0;
+ margin-top: -17px;
+}
diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss
index 125f495d6d4..292225c5261 100644
--- a/app/assets/stylesheets/pages/confirmation.scss
+++ b/app/assets/stylesheets/pages/confirmation.scss
@@ -2,13 +2,21 @@
margin-bottom: 20px;
border-bottom: 1px solid #eee;
- > h1 {
+ > h1, h2, h3, h4, h5, h6 {
font-weight: 400;
}
.lead {
margin-bottom: 20px;
}
+
+ ul, ol {
+ padding-left: 0;
+ }
+
+ li {
+ list-style-type: none;
+ }
}
.confirmation-content {
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5e61e61d85c..1b389d83525 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -29,8 +29,6 @@
margin-top: 6px;
p {
- overflow-x: auto;
-
&:last-child {
margin-bottom: 0;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 787c387379e..ea453ce356a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -29,7 +29,7 @@
}
}
-.issuable-sidebar {
+.right-sidebar {
a {
color: inherit;
}
@@ -74,6 +74,10 @@
}
}
+ .block-first {
+ padding-top: 0;
+ }
+
.title {
color: $gl-text-color;
margin-bottom: 10px;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index e179bdf0048..bc65404a741 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -50,11 +50,26 @@
.label-row {
.label-name {
- display: inline-block;
- width: 200px;
+ display: block;
+ margin-bottom: 10px;
- @media (max-width: $screen-xs-min) {
- display: block;
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ width: 200px;
+ margin-bottom: 0;
+ }
+ }
+
+ .label-description {
+ display: block;
+ margin-bottom: 10px;
+
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ width: 40%;
+ margin-left: 10px;
+ margin-bottom: 0;
+ vertical-align: middle;
}
}
@@ -68,10 +83,6 @@
padding: 3px 4px;
}
-.label-subscription {
- display: inline-block;
-}
-
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
@@ -79,62 +90,95 @@
color: $white-light;
}
-@mixin labels-mobile {
- @media (max-width: $screen-xs-min) {
- display: block;
- width: 100%;
- margin-left: 0;
- padding: 10px 0;
- }
-}
+.manage-labels-list {
+ .btn-action {
+ color: $gl-dark-link-color;
+ .fa {
+ font-size: 18px;
+ vertical-align: middle;
+ }
-.manage-labels-list {
+ &:hover {
+ color: $gl-link-color;
- .prepend-left-10, .prepend-description-left {
- display: inline-block;
- width: 40%;
- vertical-align: middle;
+ &.remove-row {
+ color: $gl-danger;
+ }
+ }
+ }
- @include labels-mobile;
+ .dropdown {
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ }
}
+}
- .prepend-description-left {
- width: 57%;
+.prioritized-labels {
+ margin-bottom: 30px;
- @include labels-mobile;
+ .add-priority {
+ display: none;
+ color: $gray-light;
}
+}
- .pull-info-right {
- float: right;
+.other-labels {
+ .remove-priority {
+ display: none;
+ }
+}
- @media (max-width: $screen-xs-min) {
- float: none;
- }
+.toggle-priority {
+ display: inline-block;
+ vertical-align: middle;
- .action-buttons {
- border-color: transparent;
- padding: 6px;
- color: $gl-text-color;
+ button {
+ border-color: transparent;
+ padding: 5px 8px;
+ vertical-align: top;
+ font-size: 14px;
- &.label-subscribe-button {
- padding-left: 0;
- }
+ &:hover {
+ border-color: transparent;
}
+ }
+}
- i {
- color: $gl-text-color;
+.filtered-labels {
+ .label-row {
+ &:not(:last-child) {
+ margin-right: 5px;
}
+ }
- .append-right-20 {
- a {
- color: $gl-text-color;
- }
+ .label-remove {
+ border-left: 1px solid rgba(0, 0, 0, .1);
+ z-index: 3;
+ }
- @media (max-width: $screen-xs-min) {
- display: block;
- margin-bottom: 10px;
- }
+ .btn {
+ color: inherit;
+ }
+}
+
+.label-options-toggle {
+ width: 100%;
+}
+
+.label-subscribe-button {
+ .label-subscribe-button-loading {
+ display: none;
+ }
+
+ &.disabled {
+ .label-subscribe-button-icon {
+ display: none;
+ }
+
+ .label-subscribe-button-loading {
+ display: block;
}
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 8046e203a99..a47f2580aa3 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -79,11 +79,14 @@
}
&.ci-failed,
- &.ci-canceled,
&.ci-error {
color: $gl-danger;
}
+ &.ci-canceled {
+ color: $gl-gray;
+ }
+
a.monospace {
color: inherit;
}
@@ -105,6 +108,11 @@
font-size: 17px;
margin: 5px 0;
color: $gl-gray-dark;
+
+ &.has-conflicts .fa-exclamation-triangle {
+ color: $gl-warning;
+ }
+
}
p:last-child {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 7fa13e66b43..577dddae741 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -87,6 +87,39 @@
}
}
+.md-header .nav-links {
+ display: flex;
+ display: -webkit-flex;
+ flex-flow: row wrap;
+ -webkit-flex-flow: row wrap;
+ width: 100%;
+
+ .pull-right {
+ // Flexbox quirk to make sure right-aligned items stay right-aligned.
+ margin-left: auto;
+ }
+}
+
+.confidential-issue-warning {
+ background-color: $gray-normal;
+ border-radius: 3px;
+ padding: 3px 12px;
+ margin: auto;
+ margin-top: 0;
+ text-align: center;
+ font-size: 13px;
+
+ @media (max-width: $screen-md-min) {
+ // On smaller devices the warning becomes the fourth item in the list,
+ // rather than centering, and grows to span the full width of the
+ // comment area.
+ order: 4;
+ -webkit-order: 4;
+ margin: 6px auto;
+ width: 100%;
+ }
+}
+
.discussion-form {
padding: $gl-padding-top $gl-padding;
background-color: $white-light;
@@ -96,17 +129,8 @@
display: none;
font-size: 15px;
- .form-actions {
- padding-left: 20px;
-
- .btn-save {
- float: left;
- }
-
- .note-form-option {
- float: left;
- padding: 2px 0 0 25px;
- }
+ .md-area {
+ background-color: #fff;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index a3e1ac13a43..0c084118753 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -69,6 +69,10 @@ ul.notes {
.note-edit-form {
display: block;
+
+ &.current-note-edit-form + .note-awards {
+ display: none;
+ }
}
}
@@ -116,8 +120,41 @@ ul.notes {
}
}
+ .note-awards {
+ .js-awards-block {
+ padding: 2px;
+ margin-top: 10px;
+ }
+
+ .award-control {
+ font-size: 13px;
+ padding: 2px 5px;
+ }
+ }
+
.note-header {
padding-bottom: 3px;
+ padding-right: 20px;
+
+ @media (min-width: $screen-sm-min) {
+ padding-right: 0;
+ }
+ }
+
+ .note-emoji-button {
+ .fa-spinner {
+ display: none;
+ }
+
+ &.is-loading {
+ .fa-smile-o {
+ display: none;
+ }
+
+ .fa-spinner {
+ display: inline-block;
+ }
+ }
}
}
@@ -179,6 +216,8 @@ ul.notes {
.discussion-header,
.note-header {
+ position: relative;
+
a {
color: inherit;
@@ -215,6 +254,16 @@ ul.notes {
color: $notes-action-color;
}
+.note-actions {
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ @media (min-width: $screen-sm-min) {
+ position: relative;
+ }
+}
+
.discussion-actions {
@media (max-width: $screen-md-max) {
float: none;
@@ -228,8 +277,13 @@ ul.notes {
.note-action-button {
display: inline-block;
- margin-left: 10px;
- line-height: 24px;
+ margin-left: 0;
+ line-height: 20px;
+
+ @media (min-width: $screen-sm-min) {
+ margin-left: 10px;
+ line-height: 24px;
+ }
.fa {
color: $notes-action-color;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index edef336481d..bb250904255 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -32,6 +32,15 @@
.container-fluid {
position: relative;
+
+ @media (min-width: $screen-md-max) {
+ .row {
+ display: flex;
+ -ms-flex-align: center;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ }
+ }
}
.cover-controls {
@@ -57,7 +66,6 @@
max-width: 86px;
min-width: 86px;
padding-right: 0;
- margin: 11px 0;
@media (max-width: $screen-md-max) {
padding-left: 0;
@@ -489,9 +497,11 @@ pre.light-well {
margin: 0;
}
-.project-show-activity {
- .activity-filter-block {
- margin-top: -1px;
+
+.activity-filter-block {
+ .controls {
+ padding-bottom: 10px;
+ border-bottom: 1px solid $border-color;
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 2bff70c8c64..ae524cd6bae 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -28,6 +28,7 @@
}
.search-input {
+ padding-right: 20px;
border: none;
font-size: 14px;
outline: none;
@@ -47,6 +48,7 @@
display: inline-block;
background-color: $location-badge-bg;
vertical-align: top;
+ cursor: default;
}
.search-input-container {
@@ -55,7 +57,7 @@
position: relative;
}
- .search-location-badge, .search-input-wrap {
+ .search-input-wrap {
// Fallback if flexbox is not supported
display: inline-block;
}
@@ -156,13 +158,11 @@
.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;
diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 3f28e402929..8d855ce99b0 100644
--- a/app/assets/stylesheets/pages/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -11,18 +11,15 @@
$magenta: #cd00cd;
$cyan: #00cdcd;
$white: #e5e5e5;
- $l-black: #7f7f7f;
- $l-red: #f00;
- $l-green: #0f0;
- $l-yellow: #ff0;
- $l-blue: #5c5cff;
- $l-magenta: #f0f;
- $l-cyan: #0ff;
- $l-white: #fff;
+ $l-black: #373b41;
+ $l-red: #c66;
+ $l-green: #b5bd68;
+ $l-yellow: #f0c674;
+ $l-blue: #81a2be;
+ $l-magenta: #b294bb;
+ $l-cyan: #8abeb7;
+ $l-white: $ci-text-color;
- .term-bold {
- font-weight: bold;
- }
.term-italic {
font-style: italic;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 0a34a12e2a7..f4eda864aac 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -74,6 +74,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:two_factor_grace_period,
:gravatar_enabled,
:sign_in_text,
+ :after_sign_up_text,
:help_page_text,
:home_page_url,
:after_sign_out_path,
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c28d1ca9e3b..cd6ae507cf1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
+ include WorkhorseHelper
before_action :authenticate_user_from_token!
before_action :authenticate_user!
@@ -182,8 +183,8 @@ class ApplicationController < ActionController::Base
end
def check_2fa_requirement
- if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
- redirect_to new_profile_two_factor_auth_path
+ if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled? && !skip_two_factor?
+ redirect_to profile_two_factor_auth_path
end
end
@@ -342,6 +343,10 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
+ def browser_supports_u2f?
+ browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile?
+ end
+
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
@@ -355,6 +360,13 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path
end
+ # U2F (universal 2nd factor) devices need a unique identifier for the application
+ # to perform authentication.
+ # https://developers.yubico.com/U2F/App_ID.html
+ def u2f_app_id
+ request.base_url
+ end
+
private
def set_default_sort
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index d5918a7af3b..998b8adc411 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -24,7 +24,64 @@ module AuthenticatesWithTwoFactor
# Returns nil
def prompt_for_two_factor(user)
session[:otp_user_id] = user.id
+ setup_u2f_authentication(user)
+ render 'devise/sessions/two_factor'
+ end
+
+ def authenticate_with_two_factor
+ user = self.resource = find_user
+
+ if user_params[:otp_attempt].present? && session[:otp_user_id]
+ authenticate_with_two_factor_via_otp(user)
+ elsif user_params[:device_response].present? && session[:otp_user_id]
+ authenticate_with_two_factor_via_u2f(user)
+ elsif user && user.valid_password?(user_params[:password])
+ prompt_for_two_factor(user)
+ end
+ end
+
+ private
+
+ def authenticate_with_two_factor_via_otp(user)
+ if valid_otp_attempt?(user)
+ # Remove any lingering user data from login
+ session.delete(:otp_user_id)
+
+ remember_me(user) if user_params[:remember_me] == '1'
+ sign_in(user)
+ else
+ flash.now[:alert] = 'Invalid two-factor code.'
+ render :two_factor
+ end
+ end
+
+ # Authenticate using the response from a U2F (universal 2nd factor) device
+ def authenticate_with_two_factor_via_u2f(user)
+ if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenges])
+ # Remove any lingering user data from login
+ session.delete(:otp_user_id)
+ session.delete(:challenges)
+
+ sign_in(user)
+ else
+ flash.now[:alert] = 'Authentication via U2F device failed.'
+ prompt_for_two_factor(user)
+ end
+ end
+
+ # Setup in preparation of communication with a U2F (universal 2nd factor) device
+ # Actual communication is performed using a Javascript API
+ def setup_u2f_authentication(user)
+ key_handles = user.u2f_registrations.pluck(:key_handle)
+ u2f = U2F::U2F.new(u2f_app_id)
- render 'devise/sessions/two_factor' and return
+ if key_handles.present?
+ sign_requests = u2f.authentication_requests(key_handles)
+ challenges = sign_requests.map(&:challenge)
+ session[:challenges] = challenges
+ gon.push(u2f: { challenges: challenges, app_id: u2f_app_id,
+ sign_requests: sign_requests,
+ browser_supports_u2f: browser_supports_u2f? })
+ end
end
end
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
new file mode 100644
index 00000000000..036777c80c1
--- /dev/null
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -0,0 +1,31 @@
+module ToggleAwardEmoji
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authenticate_user!, only: [:toggle_award_emoji]
+ end
+
+ def toggle_award_emoji
+ name = params.require(:name)
+
+ awardable.toggle_award_emoji(name, current_user)
+ TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
+
+ render json: { ok: true }
+ end
+
+ private
+
+ def to_todoable(awardable)
+ case awardable
+ when Note
+ awardable.noteable
+ else
+ awardable
+ end
+ end
+
+ def awardable
+ raise NotImplementedError
+ end
+end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index cee3b6c43e7..131a16dad9b 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -42,46 +42,8 @@ class JwtController < ApplicationController
end
def authenticate_user(login, password)
- # TODO: this is a copy and paste from grack_auth,
- # it should be refactored in the future
-
- user = Gitlab::Auth.new.find(login, password)
-
- # If the user authenticated successfully, we reset the auth failure count
- # from Rack::Attack for that IP. A client may attempt to authenticate
- # with a username and blank password first, and only after it receives
- # a 401 error does it present a password. Resetting the count prevents
- # false positives from occurring.
- #
- # Otherwise, we let Rack::Attack know there was a failed authentication
- # attempt from this IP. This information is stored in the Rails cache
- # (Redis) and will be used by the Rack::Attack middleware to decide
- # whether to block requests from this IP.
- config = Gitlab.config.rack_attack.git_basic_auth
-
- if config.enabled
- if user
- # A successful login will reset the auth failure count from this IP
- Rack::Attack::Allow2Ban.reset(request.ip, config)
- else
- banned = Rack::Attack::Allow2Ban.filter(request.ip, config) do
- # Unless the IP is whitelisted, return true so that Allow2Ban
- # increments the counter (stored in Rails.cache) for the IP
- if config.ip_whitelist.include?(request.ip)
- false
- else
- true
- end
- end
-
- if banned
- Rails.logger.info "IP #{request.ip} failed to login " \
- "as #{login} but has been temporarily banned from Git auth"
- return
- end
- end
- end
-
+ user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
+ Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
user
end
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index c6bdd0602c1..0f54dfa4efc 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -32,7 +32,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
def verify_user_oauth_applications_enabled
return if current_application_settings.user_oauth_applications?
- redirect_to applications_profile_url
+ redirect_to profile_path
end
def set_index_vars
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 8f83fdd02bc..6a358fdcc05 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -1,7 +1,7 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_2fa_requirement
- def new
+ def show
unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32)
end
@@ -12,21 +12,22 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed?
- if two_factor_authentication_required?
+ if two_factor_authentication_required? && !current_user.two_factor_enabled?
if two_factor_grace_period_expired?
- flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
+ flash.now[:alert] = 'You must enable Two-Factor Authentication for your account.'
else
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
+ flash.now[:alert] = "You must enable Two-Factor Authentication for your account before #{l(grace_period_deadline)}."
end
end
@qr_code = build_qr_code
+ setup_u2f_registration
end
def create
if current_user.validate_and_consume_otp!(params[:pin_code])
- current_user.two_factor_enabled = true
+ current_user.otp_required_for_login = true
@codes = current_user.generate_otp_backup_codes!
current_user.save!
@@ -34,8 +35,23 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
else
@error = 'Invalid pin code'
@qr_code = build_qr_code
+ setup_u2f_registration
+ render 'show'
+ end
+ end
+
+ # A U2F (universal 2nd factor) device's information is stored after successful
+ # registration, which is then used while 2FA authentication is taking place.
+ def create_u2f
+ @u2f_registration = U2fRegistration.register(current_user, u2f_app_id, params[:device_response], session[:challenges])
- render 'new'
+ if @u2f_registration.persisted?
+ session.delete(:challenges)
+ redirect_to profile_account_path, notice: "Your U2F device was registered!"
+ else
+ @qr_code = build_qr_code
+ setup_u2f_registration
+ render :show
end
end
@@ -70,4 +86,21 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def issuer_host
Gitlab.config.gitlab.host
end
+
+ # Setup in preparation of communication with a U2F (universal 2nd factor) device
+ # Actual communication is performed using a Javascript API
+ def setup_u2f_registration
+ @u2f_registration ||= U2fRegistration.new
+ @registration_key_handles = current_user.u2f_registrations.pluck(:key_handle)
+ u2f = U2F::U2F.new(u2f_app_id)
+
+ registration_requests = u2f.registration_requests
+ sign_requests = u2f.authentication_requests(@registration_key_handles)
+ session[:challenges] = registration_requests.map(&:challenge)
+
+ gon.push(u2f: { challenges: session[:challenges], app_id: u2f_app_id,
+ register_requests: registration_requests,
+ sign_requests: sign_requests,
+ browser_supports_u2f: browser_supports_u2f? })
+ end
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index cfea1266516..832d7deb57d 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -37,7 +37,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
private
def build
- @build ||= project.builds.unscoped.find_by!(id: params[:build_id])
+ @build ||= project.builds.find_by!(id: params[:build_id])
end
def artifacts_file
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 72921b3aa14..5962f74c39b 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -10,10 +10,7 @@ class Projects::AvatarsController < Projects::ApplicationController
return if cached_blob?
- headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
- headers['Content-Disposition'] = 'inline'
- headers['Content-Type'] = safe_content_type(@blob)
- head :ok # 'render nothing: true' messes up the Content-Type
+ send_git_blob @repository, @blob
else
render_404
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index d09e7375b67..dd9508da049 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -50,7 +50,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace,
@project), status: 303
end
- format.js { render status: status[:return_code] }
+ format.js { render nothing: true, status: status[:return_code] }
end
end
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index bb1f6c5e980..14c82826342 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -26,9 +26,9 @@ class Projects::BuildsController < Projects::ApplicationController
end
def show
- @builds = @project.ci_commits.find_by_sha(@build.sha).builds.order('id DESC')
+ @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id)
- @commit = @build.commit
+ @pipeline = @build.pipeline
respond_to do |format|
format.html
@@ -41,7 +41,7 @@ class Projects::BuildsController < Projects::ApplicationController
def trace
respond_to do |format|
format.json do
- render json: @build.trace_with_state(params[:state]).merge!(id: @build.id, status: @build.status)
+ render json: @build.trace_with_state(params[:state].presence).merge!(id: @build.id, status: @build.status)
end
end
end
@@ -81,7 +81,7 @@ class Projects::BuildsController < Projects::ApplicationController
private
def build
- @build ||= project.builds.unscoped.find_by!(id: params[:id])
+ @build ||= project.builds.find_by!(id: params[:id])
end
def build_path(build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 10b5932affa..20637fa46fe 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -99,12 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
- def ci_commits
- @ci_commits ||= project.ci_commits.where(sha: commit.sha)
+ def pipelines
+ @pipelines ||= project.pipelines.where(sha: commit.sha)
end
def ci_builds
- @ci_builds ||= Ci::Build.where(commit: ci_commits)
+ @ci_builds ||= Ci::Build.where(pipeline: pipelines)
end
def define_show_vars
@@ -117,8 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
- @statuses = CommitStatus.where(commit: ci_commits)
- @builds = Ci::Build.where(commit: ci_commits)
+ @statuses = CommitStatus.where(pipeline: pipelines)
+ @builds = Ci::Build.where(pipeline: pipelines)
end
def assign_change_commit_vars(mr_source_branch)
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
new file mode 100644
index 00000000000..348d6cf4d96
--- /dev/null
+++ b/app/controllers/projects/git_http_controller.rb
@@ -0,0 +1,147 @@
+class Projects::GitHttpController < Projects::ApplicationController
+ attr_reader :user
+
+ # Git clients will not know what authenticity token to send along
+ skip_before_action :verify_authenticity_token
+ skip_before_action :repository
+ before_action :authenticate_user
+ before_action :ensure_project_found!
+
+ # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
+ # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
+ def info_refs
+ if upload_pack? && upload_pack_allowed?
+ render_ok
+ elsif receive_pack? && receive_pack_allowed?
+ render_ok
+ else
+ render_not_found
+ end
+ end
+
+ # POST /foo/bar.git/git-upload-pack (git pull)
+ def git_upload_pack
+ if upload_pack? && upload_pack_allowed?
+ render_ok
+ else
+ render_not_found
+ end
+ end
+
+ # POST /foo/bar.git/git-receive-pack" (git push)
+ def git_receive_pack
+ if receive_pack? && receive_pack_allowed?
+ render_ok
+ else
+ render_not_found
+ end
+ end
+
+ private
+
+ def authenticate_user
+ return if project && project.public? && upload_pack?
+
+ authenticate_or_request_with_http_basic do |login, password|
+ auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip)
+
+ if auth_result.type == :ci && upload_pack?
+ @ci = true
+ elsif auth_result.type == :oauth && !upload_pack?
+ # Not allowed
+ else
+ @user = auth_result.user
+ end
+
+ ci? || user
+ end
+ end
+
+ def ensure_project_found!
+ render_not_found if project.blank?
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ project_id, _ = project_id_with_suffix
+ if project_id.blank?
+ @project = nil
+ else
+ @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
+ end
+ end
+
+ # This method returns two values so that we can parse
+ # params[:project_id] (untrusted input!) in exactly one place.
+ def project_id_with_suffix
+ id = params[:project_id] || ''
+
+ %w[.wiki.git .git].each do |suffix|
+ if id.end_with?(suffix)
+ # Be careful to only remove the suffix from the end of 'id'.
+ # Accidentally removing it from the middle is how security
+ # vulnerabilities happen!
+ return [id.slice(0, id.length - suffix.length), suffix]
+ end
+ end
+
+ # Something is wrong with params[:project_id]; do not pass it on.
+ [nil, nil]
+ end
+
+ def upload_pack?
+ git_command == 'git-upload-pack'
+ end
+
+ def receive_pack?
+ git_command == 'git-receive-pack'
+ end
+
+ def git_command
+ if action_name == 'info_refs'
+ params[:service]
+ else
+ action_name.dasherize
+ end
+ end
+
+ def render_ok
+ render json: Gitlab::Workhorse.git_http_ok(repository, user)
+ end
+
+ def repository
+ _, suffix = project_id_with_suffix
+ if suffix == '.wiki.git'
+ project.wiki.repository
+ else
+ project.repository
+ end
+ end
+
+ def render_not_found
+ render text: 'Not Found', status: :not_found
+ end
+
+ def ci?
+ @ci.present?
+ end
+
+ def upload_pack_allowed?
+ return false unless Gitlab.config.gitlab_shell.upload_pack
+
+ if user
+ Gitlab::GitAccess.new(user, project).download_access_check.allowed?
+ else
+ ci? || project.public?
+ end
+ end
+
+ def receive_pack_allowed?
+ return false unless Gitlab.config.gitlab_shell.receive_pack
+
+ # Skip user authorization on upload request.
+ # It will be done by the pre-receive hook in the repository.
+ user.present?
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 016f5dd0005..4e2d3bebb2e 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,6 +1,7 @@
class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction
include IssuableActions
+ include ToggleAwardEmoji
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
@@ -62,7 +63,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.nonawards.with_associations.fresh
+ @notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_to do |format|
@@ -155,7 +156,12 @@ class Projects::IssuesController < Projects::ApplicationController
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" })
+
+ respond_to do |format|
+ format.json do
+ render json: { notice: "#{result[:count]} issues updated" }
+ end
+ end
end
protected
@@ -169,6 +175,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
+ alias_method :awardable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
@@ -214,7 +221,10 @@ class Projects::IssuesController < Projects::ApplicationController
:issues_ids,
:assignee_id,
:milestone_id,
- :state_event
+ :state_event,
+ label_ids: [],
+ add_label_ids: [],
+ remove_label_ids: []
)
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index ff771ea6d9c..0ca675623e5 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -5,13 +5,14 @@ class Projects::LabelsController < Projects::ApplicationController
before_action :label, only: [:edit, :update, :destroy]
before_action :authorize_read_label!
before_action :authorize_admin_labels!, only: [
- :new, :create, :edit, :update, :generate, :destroy
+ :new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities
]
respond_to :js, :html
def index
- @labels = @project.labels.page(params[:page])
+ @labels = @project.labels.unprioritized.page(params[:page])
+ @prioritized_labels = @project.labels.prioritized
respond_to do |format|
format.html
@@ -71,6 +72,30 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
+ def remove_priority
+ respond_to do |format|
+ if label.update_attribute(:priority, nil)
+ format.json { render json: label }
+ else
+ message = label.errors.full_messages.uniq.join('. ')
+ format.json { render json: { message: message }, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def set_priorities
+ Label.transaction do
+ params[:label_ids].each_with_index do |label_id, index|
+ label = @project.labels.find_by_id(label_id)
+ label.update_attribute(:priority, index) if label
+ end
+ end
+
+ respond_to do |format|
+ format.json { render json: { message: 'success' } }
+ end
+ end
+
protected
def module_enabled
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d54284d7b20..67e7187c10d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
include IssuableActions
+ include ToggleAwardEmoji
before_action :module_enabled
before_action :merge_request, only: [
@@ -57,9 +58,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
- format.json { render json: @merge_request }
- format.diff { render text: @merge_request.to_diff }
- format.patch { render text: @merge_request.to_patch }
+ format.json { render json: @merge_request }
+ format.patch { render text: @merge_request.to_patch }
+ format.diff do
+ return render_404 unless @merge_request.diff_refs
+
+ send_git_diff @project.repository, @merge_request.diff_refs
+ end
end
end
@@ -119,8 +124,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
- @ci_commit = @merge_request.ci_commit
- @statuses = @ci_commit.statuses if @ci_commit
+ @pipeline = @merge_request.pipeline
+ @statuses = @pipeline.statuses if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@@ -190,13 +195,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return
end
+ if params[:sha] != @merge_request.source_sha
+ @status = :sha_mismatch
+ return
+ end
+
TodoService.new.merge_merge_request(merge_request, current_user)
@merge_request.update(merge_error: nil)
- if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
+ if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
- .execute(@merge_request)
+ .execute(@merge_request)
@status = :merge_when_build_succeeds
else
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@@ -225,10 +235,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- ci_commit = @merge_request.ci_commit
- if ci_commit
- status = ci_commit.status
- coverage = ci_commit.try(:coverage)
+ pipeline = @merge_request.pipeline
+ if pipeline
+ status = pipeline.status
+ coverage = pipeline.try(:coverage)
status ||= "preparing"
else
@@ -265,6 +275,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
+ alias_method :awardable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
@@ -300,7 +311,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
- @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
+ @notes = @merge_request.mr_and_commit_notes.inc_author.fresh
@discussions = @notes.discussions
@noteable = @merge_request
@@ -310,8 +321,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
- @ci_commit = @merge_request.ci_commit
- @statuses = @ci_commit.statuses if @ci_commit
+ @pipeline = @merge_request.pipeline
+ @statuses = @pipeline.statuses if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@@ -320,8 +331,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_widget_vars
- @ci_commit = @merge_request.ci_commit
- @ci_commits = [@ci_commit].compact
+ @pipeline = @merge_request.pipeline
+ @pipelines = [@pipeline].compact
closes_issues
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 40b24d550e0..836f79ff080 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,9 +1,11 @@
class Projects::NotesController < Projects::ApplicationController
+ include ToggleAwardEmoji
+
# Authorize
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
- before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
+ before_action :find_current_user_notes, only: [:index]
def index
current_fetched_at = Time.now.to_i
@@ -56,35 +58,12 @@ class Projects::NotesController < Projects::ApplicationController
end
end
- def award_toggle
- noteable = if note_params[:noteable_type] == "issue"
- project.issues.find(note_params[:noteable_id])
- else
- project.merge_requests.find(note_params[:noteable_id])
- end
-
- data = {
- author: current_user,
- is_award: true,
- note: note_params[:note].delete(":")
- }
-
- note = noteable.notes.find_by(data)
-
- if note
- note.destroy
- else
- Notes::CreateService.new(project, current_user, note_params).execute
- end
-
- render json: { ok: true }
- end
-
private
def note
@note ||= @project.notes.find(params[:id])
end
+ alias_method :awardable, :note
def note_to_html(note)
render_to_string(
@@ -131,13 +110,20 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_json(note)
- if note.valid?
+ if note.is_a?(AwardEmoji)
+ {
+ valid: note.valid?,
+ award: true,
+ id: note.id,
+ name: note.name
+ }
+ elsif note.valid?
{
valid: true,
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
- award: note.is_award,
+ award: false,
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
@@ -145,7 +131,7 @@ class Projects::NotesController < Projects::ApplicationController
else
{
valid: false,
- award: note.is_award,
+ award: false,
errors: note.errors
}
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index b36081205d8..cac440ae53e 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -7,7 +7,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def index
@scope = params[:scope]
- all_pipelines = project.ci_commits
+ all_pipelines = project.pipelines
@pipelines_count = all_pipelines.count
@running_or_pending_count = all_pipelines.running_or_pending.count
@pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope)
@@ -15,7 +15,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def new
- @pipeline = project.ci_commits.new(ref: @project.default_branch)
+ @pipeline = project.pipelines.new(ref: @project.default_branch)
end
def create
@@ -50,7 +50,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def pipeline
- @pipeline ||= project.ci_commits.find_by!(id: params[:id])
+ @pipeline ||= project.pipelines.find_by!(id: params[:id])
end
def commit
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 10de0e60530..10d24da16d7 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -18,10 +18,7 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
else
- headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
- headers['Content-Disposition'] = 'inline'
- headers['Content-Type'] = safe_content_type(@blob)
- head :ok # 'render nothing: true' messes up the Content-Type
+ send_git_blob @repository, @blob
end
else
render_404
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index bb7a6b6a5ab..d5af0341d18 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,8 +11,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
- head :ok
+ send_git_archive @repository, ref: params[:ref], format: params[:format]
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 4b404eb03fa..2aa6bed0724 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -95,7 +95,7 @@ class Projects::WikisController < Projects::ApplicationController
ext.analyze(text, author: current_user)
render json: {
- body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
+ body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id]),
references: {
users: ext.users.map(&:username)
}
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index f94e2a84fa2..a6479c42d94 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -139,7 +139,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
- emojis: AwardEmoji.urls,
+ emojis: Gitlab::AwardEmoji.urls,
issues: autocomplete.issues,
milestones: autocomplete.milestones,
mergerequests: autocomplete.merge_requests,
@@ -234,7 +234,7 @@ class ProjectsController < Projects::ApplicationController
:issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
- :public_builds,
+ :public_builds, :only_allow_merge_if_build_succeeds
)
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index d68c2a708e3..dae8f7b1447 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -14,6 +14,7 @@ class SessionsController < Devise::SessionsController
before_action :load_recaptcha
def new
+ set_minimum_password_length
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
else
@@ -30,8 +31,7 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
- authenticated_with = user_params[:otp_attempt] ? "two-factor" : "standard"
- log_audit_event(current_user, with: authenticated_with)
+ log_audit_event(current_user, with: authentication_method)
end
end
@@ -54,7 +54,7 @@ class SessionsController < Devise::SessionsController
end
def user_params
- params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
+ params.require(:user).permit(:login, :password, :remember_me, :otp_attempt, :device_response)
end
def find_user
@@ -89,27 +89,6 @@ class SessionsController < Devise::SessionsController
find_user.try(:two_factor_enabled?)
end
- def authenticate_with_two_factor
- user = self.resource = find_user
-
- if user_params[:otp_attempt].present? && session[:otp_user_id]
- if valid_otp_attempt?(user)
- # Remove any lingering user data from login
- session.delete(:otp_user_id)
-
- remember_me(user) if user_params[:remember_me] == '1'
- sign_in(user) and return
- else
- flash.now[:alert] = 'Invalid two-factor code.'
- render :two_factor and return
- end
- else
- if user && user.valid_password?(user_params[:password])
- prompt_for_two_factor(user)
- end
- end
- end
-
def auto_sign_in_with_provider
provider = Gitlab.config.omniauth.auto_sign_in_with_provider
return unless provider.present?
@@ -138,4 +117,14 @@ class SessionsController < Devise::SessionsController
def load_recaptcha
Gitlab::Recaptcha.load_configurations!
end
+
+ def authentication_method
+ if user_params[:otp_attempt]
+ "two-factor"
+ elsif user_params[:device_response]
+ "two-factor-via-u2f-device"
+ else
+ "standard"
+ end
+ end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 7d8c56f4c22..a0932712bd0 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -224,7 +224,7 @@ class IssuableFinder
def sort(items)
# Ensure we always have an explicit sort order (instead of inheriting
# multiple orders when combining ActiveRecord::Relation objects).
- params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
+ params[:sort] ? items.sort(params[:sort], excluded_labels: label_names) : items.reorder(id: :desc)
end
def by_assignee(items)
@@ -318,7 +318,11 @@ class IssuableFinder
end
def label_names
- params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
+ if labels?
+ params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
+ else
+ []
+ end
end
def current_user_related?
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index c41be333537..ee14ac60fb4 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -12,9 +12,9 @@ class NotesFinder
when "commit"
project.notes.for_commit_id(target_id).non_diff_notes
when "issue"
- project.issues.find(target_id).notes.nonawards.inc_author
+ project.issues.find(target_id).notes.inc_author
when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author
+ project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes
else
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 4bd46a76087..1d88116d7d2 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -30,7 +30,7 @@ class TodosFinder
items = by_state(items)
items = by_type(items)
- items
+ items.reorder(id: :desc)
end
private
@@ -78,6 +78,16 @@ class TodosFinder
@project
end
+ def projects
+ return @projects if defined?(@projects)
+
+ if project?
+ @projects = project
+ else
+ @projects = ProjectsFinder.new.execute(current_user)
+ end
+ end
+
def type?
type.present? && ['Issue', 'MergeRequest'].include?(type)
end
@@ -105,6 +115,8 @@ class TodosFinder
def by_project(items)
if project?
items = items.where(project: project)
+ elsif projects
+ items = items.merge(projects).joins(:project)
end
items
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index e0abc3a2869..f240584ccbf 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -30,4 +30,8 @@ module AppearancesHelper
render 'shared/logo.svg'
end
end
+
+ def navbar_icon(icon_name)
+ render "shared/icons/#{icon_name}.svg"
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 03080d25931..55313fd8357 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 after_sign_up_text
+ current_application_settings.after_sign_up_text
+ end
+
def shared_runners_text
current_application_settings.shared_runners_text
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index b05fa0a14d6..cd4d778e508 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -66,7 +66,7 @@ module AuthHelper
def two_factor_skippable?
current_application_settings.require_two_factor_authentication &&
- !current_user.two_factor_enabled &&
+ !current_user.two_factor_enabled? &&
current_application_settings.two_factor_grace_period &&
!two_factor_grace_period_expired?
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index a9047ede8c5..f742922d926 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -30,7 +30,7 @@ module ButtonHelper
content_tag :a, protocol,
class: klass,
- href: @project.http_url_to_repo,
+ href: project.http_url_to_repo,
data: {
html: true,
placement: 'right',
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index cfad17dcacf..07e5c146844 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,7 +1,7 @@
module CiStatusHelper
- def ci_status_path(ci_commit)
- project = ci_commit.project
- builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
+ def ci_status_path(pipeline)
+ project = pipeline.project
+ builds_namespace_project_commit_path(project.namespace, project, pipeline.sha)
end
def ci_status_with_icon(status, target = nil)
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 14697f774cc..6b617e1730a 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -67,9 +67,9 @@ module DropdownsHelper
end
end
- def dropdown_filter(placeholder)
+ def dropdown_filter(placeholder, search_id: nil)
content_tag :div, class: "dropdown-input" do
- filter_output = search_field_tag nil, nil, class: "dropdown-input-field", placeholder: placeholder
+ filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder
filter_output << icon('search', class: "dropdown-input-search")
filter_output << icon('times', class: "dropdown-input-clear js-dropdown-input-clear", role: "button")
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 0a1b48af219..067a00660aa 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -108,7 +108,7 @@ module GitlabMarkdownHelper
def render_wiki_content(wiki_page)
case wiki_page.format
when :markdown
- markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki)
+ markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki, page_slug: wiki_page.slug)
when :asciidoc
asciidoc(wiki_page.content)
else
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index b1f0a765bb9..4cac69c6795 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -31,7 +31,7 @@ module GroupsHelper
if group && group.avatar.present?
group.avatar.url
else
- 'no_group_avatar.png'
+ image_path('no_group_avatar.png')
end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index fe84ee3de44..40d8ce8a1d3 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -8,14 +8,6 @@ module IssuablesHelper
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end
- def issuables_count(issuable)
- base_issuable_scope(issuable).maximum(:iid)
- end
-
- def next_issuable_for(issuable)
- 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)
@@ -45,10 +37,6 @@ module IssuablesHelper
end
end
- def prev_issuable_for(issuable)
- base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
- end
-
def user_dropdown_label(user_id, default_label)
return default_label if user_id.nil?
return "Unassigned" if user_id == "0"
@@ -96,5 +84,4 @@ module IssuablesHelper
issuable.open? ? :opened : :closed
end
end
-
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 173bdbb8654..72bd1fbbd81 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -145,16 +145,14 @@ module IssuesHelper
end
end
- def emoji_author_list(notes, current_user)
- list = notes.map do |note|
- note.author == current_user ? "me" : note.author.name
- end
-
- list.join(", ")
+ def award_user_list(awards, current_user)
+ awards.map do |award|
+ award.user == current_user ? 'me' : award.user.name
+ end.join(', ')
end
- def note_active_class(notes, current_user)
- if current_user && notes.pluck(:author_id).include?(current_user.id)
+ def award_active_class(awards, current_user)
+ if current_user && awards.find { |a| a.user_id == current_user.id }
"active"
else
""
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index c99b137cdaa..5074e645769 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -32,7 +32,7 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
- def link_to_label(label, project: nil, type: :issue, tooltip: true, &block)
+ def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
project ||= @project || label.project
link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
@@ -40,9 +40,9 @@ module LabelsHelper
label_name: [label.name])
if block_given?
- link_to link, &block
+ link_to link, class: css_class, &block
else
- link_to render_colored_label(label, tooltip: tooltip), link
+ link_to render_colored_label(label, tooltip: tooltip), link, class: css_class
end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 87fc2db6901..b3e6e468ecd 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -56,7 +56,7 @@ module MilestonesHelper
def milestone_remaining_days(milestone)
if milestone.expired?
- content_tag(:strong, 'expired')
+ content_tag(:strong, 'Past due')
elsif milestone.due_date
days = milestone.remaining_days
content = content_tag(:strong, days)
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index f685e547537..39831ce2e48 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -30,6 +30,8 @@ module NavHelper
else
"page-gutter right-sidebar-expanded"
end
+ elsif current_path?('builds#show')
+ "page-gutter build-sidebar right-sidebar-expanded"
end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 54ab9179efc..b8e64b3890a 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -31,6 +31,21 @@ module NotificationsHelper
end
end
+ def notification_description(level)
+ case level.to_sym
+ when :participating
+ 'You will only receive notifications from related resources'
+ when :mention
+ 'You will receive notifications only for comments in which you were @mentioned'
+ when :watch
+ 'You will receive notifications for any activity'
+ when :disabled
+ 'You will not get any notifications via email'
+ when :global
+ 'Use your global notification setting'
+ end
+ end
+
def notification_list_item(level, setting)
title = notification_title(level)
@@ -39,9 +54,10 @@ module NotificationsHelper
notification_title: title
}
- content_tag(:li, class: ('active' if setting.level == level)) do
- link_to '#', class: 'update-notification', data: data do
- notification_icon(level, title)
+ content_tag(:li, role: "menuitem") do
+ link_to '#', class: "update-notification #{('is-active' if setting.level == level)}", data: data do
+ link_output = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
+ link_output << content_tag(:span, notification_description(level), class: 'dropdown-menu-inner-content')
end
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 630e10ea892..d86f1999f5c 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -14,7 +14,8 @@ module SortingHelper
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
- sort_value_upvotes => sort_title_upvotes
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_priority => sort_title_priority
}
end
@@ -28,6 +29,10 @@ module SortingHelper
}
end
+ def sort_title_priority
+ 'Priority'
+ end
+
def sort_title_oldest_updated
'Oldest updated'
end
@@ -84,6 +89,10 @@ module SortingHelper
'Most popular'
end
+ def sort_value_priority
+ 'priority'
+ end
+
def sort_value_oldest_updated
'updated_asc'
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index b9d7edb4185..b4923fbb138 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -17,7 +17,9 @@ module TodosHelper
def todo_target_link(todo)
target = todo.target_type.titleize.downcase
- link_to "#{target} #{todo.target_reference}", todo_target_path(todo), { title: todo.target.title }
+ link_to "#{target} #{todo.target_reference}", todo_target_path(todo),
+ class: 'has-tooltip',
+ title: todo.target.title
end
def todo_target_path(todo)
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
new file mode 100644
index 00000000000..2bd0dbfd095
--- /dev/null
+++ b/app/helpers/workhorse_helper.rb
@@ -0,0 +1,24 @@
+# Helpers to send Git blobs, diffs or archives through Workhorse.
+# Workhorse will also serve files when using `send_file`.
+module WorkhorseHelper
+ # Send a Git blob through Workhorse
+ def send_git_blob(repository, blob)
+ headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
+ headers['Content-Disposition'] = 'inline'
+ headers['Content-Type'] = safe_content_type(blob)
+ head :ok # 'render nothing: true' messes up the Content-Type
+ end
+
+ # Send a Git diff through Workhorse
+ def send_git_diff(repository, diff_refs)
+ headers.store(*Gitlab::Workhorse.send_git_diff(repository, diff_refs))
+ headers['Content-Disposition'] = 'inline'
+ head :ok
+ end
+
+ # Archive a Git repository and send it through Workhorse
+ def send_git_archive(repository, ref:, format:)
+ headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+ head :ok
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 42f908aa344..a744f937918 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -113,7 +113,10 @@ class ApplicationSetting < ActiveRecord::Base
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
- sign_in_text: Settings.extra['sign_in_text'],
+ sign_in_text: nil,
+ after_sign_up_text: nil,
+ help_page_text: nil,
+ shared_runners_text: nil,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
new file mode 100644
index 00000000000..59c7d87f5df
--- /dev/null
+++ b/app/models/award_emoji.rb
@@ -0,0 +1,26 @@
+class AwardEmoji < ActiveRecord::Base
+ DOWNVOTE_NAME = "thumbsdown".freeze
+ UPVOTE_NAME = "thumbsup".freeze
+
+ include Participable
+
+ belongs_to :awardable, polymorphic: true
+ belongs_to :user
+
+ validates :awardable, :user, presence: true
+ validates :name, presence: true, inclusion: { in: Emoji.emojis_names }
+ validates :name, uniqueness: { scope: [:user, :awardable_type, :awardable_id] }
+
+ participant :user
+
+ scope :downvotes, -> { where(name: DOWNVOTE_NAME) }
+ scope :upvotes, -> { where(name: UPVOTE_NAME) }
+
+ def downvote?
+ self.name == DOWNVOTE_NAME
+ end
+
+ def upvote?
+ self.name == UPVOTE_NAME
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5e77fda70b9..6a64ca451f7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -45,8 +45,8 @@ module Ci
new_build.options = build.options
new_build.commands = build.commands
new_build.tag_list = build.tag_list
- new_build.gl_project_id = build.gl_project_id
- new_build.commit_id = build.commit_id
+ new_build.project = build.project
+ new_build.pipeline = build.pipeline
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
@@ -66,7 +66,7 @@ module Ci
# We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |build, block|
block.call
- build.commit.create_next_builds(build) if build.commit
+ build.pipeline.create_next_builds(build) if build.pipeline
end
after_transition any => [:success, :failed, :canceled] do |build|
@@ -80,7 +80,7 @@ module Ci
end
def retried?
- !self.commit.statuses.latest.include?(self)
+ !self.pipeline.statuses.latest.include?(self)
end
def retry
@@ -89,7 +89,7 @@ module Ci
def depends_on_builds
# Get builds of the same type
- latest_builds = self.commit.builds.latest
+ latest_builds = self.pipeline.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
@@ -114,16 +114,16 @@ module Ci
def merge_request
merge_requests = MergeRequest.includes(:merge_request_diff)
- .where(source_branch: ref, source_project_id: commit.gl_project_id)
+ .where(source_branch: ref, source_project_id: pipeline.gl_project_id)
.reorder(iid: :asc)
merge_requests.find do |merge_request|
- merge_request.commits.any? { |ci| ci.id == commit.sha }
+ merge_request.commits.any? { |ci| ci.id == pipeline.sha }
end
end
def project_id
- commit.project.id
+ pipeline.project_id
end
def project_name
@@ -194,7 +194,7 @@ module Ci
def trace_length
if raw_trace
- raw_trace.length
+ raw_trace.bytesize
else
0
end
@@ -216,7 +216,7 @@ module Ci
recreate_trace_dir
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
- File.open(path_to_trace, 'a') do |f|
+ File.open(path_to_trace, 'ab') do |f|
f.write(trace_part)
end
end
@@ -313,6 +313,7 @@ module Ci
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
+ project.running_or_pending_build_count(force: true)
end
def artifacts?
@@ -359,8 +360,8 @@ module Ci
end
def global_yaml_variables
- if commit.config_processor
- commit.config_processor.global_variables.map do |key, value|
+ if pipeline.config_processor
+ pipeline.config_processor.global_variables.map do |key, value|
{ key: key, value: value, public: true }
end
else
@@ -369,8 +370,8 @@ module Ci
end
def job_yaml_variables
- if commit.config_processor
- commit.config_processor.job_variables(name).map do |key, value|
+ if pipeline.config_processor
+ pipeline.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/pipeline.rb
index f22b573a94c..9b5b46f4928 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/pipeline.rb
@@ -1,12 +1,14 @@
module Ci
- class Commit < ActiveRecord::Base
+ class Pipeline < ActiveRecord::Base
extend Ci::Model
include Statuseable
+ self.table_name = 'ci_commits'
+
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'
+ has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
+ has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
+ has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha
validates_presence_of :status
@@ -21,7 +23,7 @@ module Ci
def self.stages
# We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries
- CommitStatus.where(commit: pluck(:id)).stages
+ CommitStatus.where(pipeline: pluck(:id)).stages
end
def project_id
@@ -47,7 +49,7 @@ module Ci
end
def short_sha
- Ci::Commit.truncate_sha(sha)
+ Ci::Pipeline.truncate_sha(sha)
end
def commit_data
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 872d5fb31de..b69ae37668c 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -3,7 +3,7 @@ module Ci
extend Ci::Model
belongs_to :trigger, class_name: 'Ci::Trigger'
- belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build'
serialize :variables
diff --git a/app/models/commit.rb b/app/models/commit.rb
index f96c7cb34d0..d69d518fadd 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -198,7 +198,7 @@ class Commit
end
def notes_with_associations
- notes.includes(:author, :project)
+ notes.includes(:author)
end
def method_missing(m, *args, &block)
@@ -214,13 +214,13 @@ class Commit
@raw.short_id(7)
end
- def ci_commits
- @ci_commits ||= project.ci_commits.where(sha: sha)
+ def pipelines
+ @pipeline ||= project.pipelines.where(sha: sha)
end
def status
return @status if defined?(@status)
- @status ||= ci_commits.status
+ @status ||= pipelines.status
end
def revert_branch_name
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index f774b6e0efb..e53c483b904 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -4,10 +4,10 @@ class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- belongs_to :commit, class_name: 'Ci::Commit', touch: true
+ belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
belongs_to :user
- validates :commit, presence: true
+ validates :pipeline, presence: true
validates_presence_of :name
@@ -44,18 +44,18 @@ class CommitStatus < ActiveRecord::Base
end
after_transition [:pending, :running] => :success do |commit_status|
- MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
+ MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end
after_transition any => :failed do |commit_status|
- MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.commit.project, nil).execute(commit_status)
+ MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status)
end
end
- delegate :sha, :short_sha, to: :commit
+ delegate :sha, :short_sha, to: :pipeline
def before_sha
- commit.before_sha || Gitlab::Git::BLANK_SHA
+ pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
def self.stages
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
new file mode 100644
index 00000000000..aa4b4201250
--- /dev/null
+++ b/app/models/concerns/awardable.rb
@@ -0,0 +1,81 @@
+module Awardable
+ extend ActiveSupport::Concern
+
+ included do
+ has_many :award_emoji, as: :awardable, dependent: :destroy
+
+ if self < Participable
+ participant :award_emoji
+ end
+ end
+
+ module ClassMethods
+ def order_upvotes_desc
+ order_votes_desc(AwardEmoji::UPVOTE_NAME)
+ end
+
+ def order_downvotes_desc
+ order_votes_desc(AwardEmoji::DOWNVOTE_NAME)
+ end
+
+ def order_votes_desc(emoji_name)
+ awardable_table = self.arel_table
+ awards_table = AwardEmoji.arel_table
+
+ join_clause = awardable_table.join(awards_table, Arel::Nodes::OuterJoin).on(
+ awards_table[:awardable_id].eq(awardable_table[:id]).and(
+ awards_table[:awardable_type].eq(self.name).and(
+ awards_table[:name].eq(emoji_name)
+ )
+ )
+ ).join_sources
+
+ joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC")
+ end
+ end
+
+ def grouped_awards(with_thumbs: true)
+ awards = award_emoji.group_by(&:name)
+
+ if with_thumbs
+ awards[AwardEmoji::UPVOTE_NAME] ||= []
+ awards[AwardEmoji::DOWNVOTE_NAME] ||= []
+ end
+
+ awards
+ end
+
+ def downvotes
+ award_emoji.downvotes.count
+ end
+
+ def upvotes
+ award_emoji.upvotes.count
+ end
+
+ def emoji_awardable?
+ true
+ end
+
+ def awarded_emoji?(emoji_name, current_user)
+ award_emoji.where(name: emoji_name, user: current_user).exists?
+ end
+
+ def create_award_emoji(name, current_user)
+ return unless emoji_awardable?
+
+ award_emoji.create(name: name, user: current_user)
+ end
+
+ def remove_award_emoji(name, current_user)
+ award_emoji.where(name: name, user: current_user).destroy_all
+ end
+
+ def toggle_award_emoji(emoji_name, current_user)
+ if awarded_emoji?(emoji_name, current_user)
+ remove_award_emoji(emoji_name, current_user)
+ else
+ create_award_emoji(emoji_name, current_user)
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 2326a395cb8..0ccd3474b81 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -10,13 +10,19 @@ module Issuable
include Mentionable
include Subscribable
include StripAttribute
+ include Awardable
included do
belongs_to :author, class_name: "User"
belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :milestone
- has_many :notes, as: :noteable, dependent: :destroy
+ has_many :notes, as: :noteable, dependent: :destroy do
+ def authors_loaded?
+ # We check first if we're loaded to not load unnecesarily.
+ loaded? && to_a.all? { |note| note.association(:author).loaded? }
+ end
+ end
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
has_many :todos, as: :target, dependent: :destroy
@@ -43,6 +49,7 @@ module Issuable
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 :inc_notes_with_associations, -> { includes(notes: :author) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
@@ -68,6 +75,14 @@ module Issuable
strip_attributes :title
acts_as_paranoid
+
+ after_save :update_assignee_cache_counts, if: :assignee_id_changed?
+
+ def update_assignee_cache_counts
+ # make sure we flush the cache for both the old *and* new assignee
+ User.find(assignee_id_was).update_cache_counts if assignee_id_was
+ assignee.update_cache_counts if assignee
+ end
end
module ClassMethods
@@ -96,38 +111,22 @@ module Issuable
where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
end
- def sort(method)
+ def sort(method, excluded_labels: [])
case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
+ when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
else
order_by(method)
end
end
- def order_downvotes_desc
- order_votes_desc('thumbsdown')
- end
-
- def order_upvotes_desc
- order_votes_desc('thumbsup')
- end
-
- def order_votes_desc(award_emoji_name)
- issuable_table = self.arel_table
- note_table = Note.arel_table
-
- join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
- note_table[:noteable_id].eq(issuable_table[:id]).and(
- note_table[:noteable_type].eq(self.name).and(
- note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
- )
- )
- ).join_sources
-
- joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
+ def order_labels_priority(excluded_labels: [])
+ select("#{table_name}.*, (#{highest_label_priority(excluded_labels).to_sql}) AS highest_priority").
+ group(arel_table[:id]).
+ reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
end
def with_label(title, sort = nil)
@@ -153,6 +152,20 @@ module Issuable
grouping_columns
end
+
+ private
+
+ def highest_label_priority(excluded_labels)
+ query = Label.select(Label.arel_table[:priority].minimum).
+ joins(:label_links).
+ where(label_links: { target_type: name }).
+ where("label_links.target_id = #{table_name}.id").
+ reorder(nil)
+
+ query.where.not(title: excluded_labels) if excluded_labels.present?
+
+ query
+ end
end
def today?
@@ -163,10 +176,6 @@ module Issuable
today? && created_at == updated_at
end
- def is_assigned?
- !!assignee_id
- end
-
def is_being_reassigned?
assignee_id_changed?
end
@@ -175,16 +184,14 @@ module Issuable
opened? || reopened?
end
- def downvotes
- notes.awards.where(note: "thumbsdown").count
- end
-
- def upvotes
- notes.awards.where(note: "thumbsup").count
- end
-
def user_notes_count
- notes.user.count
+ if notes.loaded?
+ # Use the in-memory association to select and count to avoid hitting the db
+ notes.to_a.count { |note| !note.system? }
+ else
+ # do the count query
+ notes.user.count
+ end
end
def subscribed_without_subscriptions?(user)
@@ -205,6 +212,10 @@ module Issuable
hook_data
end
+ def labels_array
+ labels.to_a
+ end
+
def label_names
labels.order('title ASC').pluck(:title)
end
@@ -240,7 +251,13 @@ module Issuable
end
def notes_with_associations
- notes.includes(:author, :project)
+ # If A has_many Bs, and B has_many Cs, and you do
+ # `A.includes(b: :c).each { |a| a.b.includes(:c) }`, sadly ActiveRecord
+ # will do the inclusion again. So, we check if all notes in the relation
+ # already have their authors loaded (possibly because the scope
+ # `inc_notes_with_associations` was used) and skip the inclusion if that's
+ # the case.
+ notes.authors_loaded? ? notes : notes.includes(:author)
end
def updated_tasks
diff --git a/app/models/issue.rb b/app/models/issue.rb
index bd0fbc96d18..235922710ad 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -75,10 +75,10 @@ class Issue < ActiveRecord::Base
@link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end
- def self.sort(method)
+ def self.sort(method, excluded_labels: [])
case method.to_s
when 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
+ when 'due_date_desc' then order_due_date_desc
else
super
end
diff --git a/app/models/label.rb b/app/models/label.rb
index e5ad11983be..49c352cc239 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -26,10 +26,20 @@ class Label < ActiveRecord::Base
format: { with: /\A[^&\?,]+\z/ },
uniqueness: { scope: :project_id }
+ before_save :nullify_priority
+
default_scope { order(title: :asc) }
scope :templates, -> { where(template: true) }
+ def self.prioritized
+ where.not(priority: nil).reorder(:priority, :title)
+ end
+
+ def self.unprioritized
+ where(priority: nil)
+ end
+
alias_attribute :name, :title
def self.reference_prefix
@@ -118,4 +128,8 @@ class Label < ActiveRecord::Base
id
end
end
+
+ def nullify_priority
+ self.priority = nil if priority.blank?
+ end
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index bbefc911b29..95fd510eb3a 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -110,6 +110,10 @@ class LegacyDiffNote < Note
@active
end
+ def award_emoji_supported?
+ false
+ end
+
private
def find_diff
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 722c258244c..7b8858b24d6 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -260,19 +260,20 @@ class MergeRequest < ActiveRecord::Base
end
def mergeable?
- return false unless open? && !work_in_progress? && !broken?
+ return false unless mergeable_state?
check_if_can_be_merged
can_be_merged?
end
- def gitlab_merge_status
- if work_in_progress?
- "work_in_progress"
- else
- merge_status_name
- end
+ def mergeable_state?
+ return false unless open?
+ return false if work_in_progress?
+ return false if broken?
+ return false unless mergeable_ci_state?
+
+ true
end
def can_cancel_merge_when_build_succeeds?(current_user)
@@ -313,13 +314,6 @@ class MergeRequest < ActiveRecord::Base
)
end
- # Returns the raw diff for this merge request
- #
- # see "git diff"
- def to_diff
- target_project.repository.diff_text(diff_base_commit.sha, source_sha)
- end
-
# Returns the commit as a series of email patches.
#
# see "git format-patch"
@@ -488,6 +482,12 @@ class MergeRequest < ActiveRecord::Base
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end
+ def mergeable_ci_state?
+ return true unless project.only_allow_merge_if_build_succeeds?
+
+ !pipeline || pipeline.success?
+ end
+
def state_human_name
if merged?
"Merged"
@@ -579,8 +579,8 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
- def ci_commit
- @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
+ def pipeline
+ @pipeline ||= source_project.pipeline(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
diff --git a/app/models/note.rb b/app/models/note.rb
index c21981ead84..585d8c4ad84 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -3,6 +3,7 @@ class Note < ActiveRecord::Base
include Gitlab::CurrentSettings
include Participable
include Mentionable
+ include Awardable
default_value_for :system, false
@@ -21,11 +22,8 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true
delegate :title, to: :noteable, allow_nil: true
- before_validation :set_award!
-
validates :note, :project, presence: true
- validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
- validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
+
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -43,8 +41,6 @@ class Note < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader
# Scopes
- scope :awards, ->{ where(is_award: true) }
- scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) }
@@ -109,19 +105,6 @@ class Note < ActiveRecord::Base
found_notes.where('issues.confidential IS NULL OR issues.confidential IS FALSE')
end
end
-
- def grouped_awards
- notes = {}
-
- awards.select(:note).distinct.map do |note|
- notes[note.note] = where(note: note.note)
- end
-
- notes["thumbsup"] ||= Note.none
- notes["thumbsdown"] ||= Note.none
-
- notes
- end
end
def cross_reference?
@@ -205,44 +188,24 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
- def downvote?
- is_award && note == "thumbsdown"
- end
-
- def upvote?
- is_award && note == "thumbsup"
- end
-
def editable?
- !system? && !is_award
+ !system?
end
def cross_reference_not_visible_for?(user)
cross_reference? && referenced_mentionables(user).empty?
end
- # Checks if note is an award added as a comment
- #
- # If note is an award, this method sets is_award to true
- # and changes content of the note to award name.
- #
- # Method is executed as a before_validation callback.
- #
- def set_award!
- return unless awards_supported? && contains_emoji_only?
-
- self.is_award = true
- self.note = award_emoji_name
+ def award_emoji?
+ award_emoji_supported? && contains_emoji_only?
end
- private
-
def clear_blank_line_code!
self.line_code = nil if self.line_code.blank?
end
- def awards_supported?
- (for_issue? || for_merge_request?) && !diff_note?
+ def award_emoji_supported?
+ noteable.is_a?(Awardable)
end
def contains_emoji_only?
@@ -251,6 +214,6 @@ class Note < ActiveRecord::Base
def award_emoji_name
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
- AwardEmoji.normilize_emoji_name(original_name)
+ Gitlab::AwardEmoji.normalize_emoji_name(original_name)
end
end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 5001738f411..17fb15b08df 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,5 +1,5 @@
class NotificationSetting < ActiveRecord::Base
- enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 }
+ enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0 }
default_value_for :level, NotificationSetting.levels[:global]
diff --git a/app/models/project.rb b/app/models/project.rb
index 525a82c7534..e2f7ffe493c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
- has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+ has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -253,20 +253,69 @@ class Project < ActiveRecord::Base
non_archived.where(table[:name].matches(pattern))
end
- def find_with_namespace(id)
- namespace_path, project_path = id.split('/', 2)
+ # Finds a single project for the given path.
+ #
+ # path - The full project path (including namespace path).
+ #
+ # Returns a Project, or nil if no project could be found.
+ def find_with_namespace(path)
+ where_paths_in([path]).reorder(nil).take
+ end
- return nil if !namespace_path || !project_path
+ # Builds a relation to find multiple projects by their full paths.
+ #
+ # Each path must be in the following format:
+ #
+ # namespace_path/project_path
+ #
+ # For example:
+ #
+ # gitlab-org/gitlab-ce
+ #
+ # Usage:
+ #
+ # Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
+ #
+ # This would return the projects with the full paths matching the values
+ # given.
+ #
+ # paths - An Array of full paths (namespace path + project path) for which
+ # to find the projects.
+ #
+ # Returns an ActiveRecord::Relation.
+ def where_paths_in(paths)
+ wheres = []
+ cast_lower = Gitlab::Database.postgresql?
+
+ paths.each do |path|
+ namespace_path, project_path = path.split('/', 2)
+
+ next unless namespace_path && project_path
+
+ namespace_path = connection.quote(namespace_path)
+ project_path = connection.quote(project_path)
+
+ where = "(namespaces.path = #{namespace_path}
+ AND projects.path = #{project_path})"
+
+ if cast_lower
+ where = "(
+ #{where}
+ OR (
+ LOWER(namespaces.path) = LOWER(#{namespace_path})
+ AND LOWER(projects.path) = LOWER(#{project_path})
+ )
+ )"
+ end
- # Use of unscoped ensures we're not secretly adding any ORDER BYs, which
- # have a negative impact on performance (and aren't needed for this
- # query).
- projects = unscoped.
- joins(:namespace).
- iwhere('namespaces.path' => namespace_path)
+ wheres << where
+ end
- projects.find_by('projects.path' => project_path) ||
- projects.iwhere('projects.path' => project_path).take
+ if wheres.empty?
+ none
+ else
+ joins(:namespace).where(wheres.join(' OR '))
+ end
end
def visibility_levels
@@ -523,9 +572,21 @@ class Project < ActiveRecord::Base
end
def external_issue_tracker
- return @external_issue_tracker if defined?(@external_issue_tracker)
- @external_issue_tracker ||=
- services.issue_trackers.active.without_defaults.first
+ if has_external_issue_tracker.nil? # To populate existing projects
+ cache_has_external_issue_tracker
+ end
+
+ if has_external_issue_tracker?
+ return @external_issue_tracker if defined?(@external_issue_tracker)
+
+ @external_issue_tracker = services.external_issue_trackers.first
+ else
+ nil
+ end
+ end
+
+ def cache_has_external_issue_tracker
+ update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
end
def can_have_issues_tracker_id?
@@ -930,12 +991,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
- def ci_commit(sha, ref)
- ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
+ def pipeline(sha, ref)
+ pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_ci_commit(sha, ref)
- ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
+ def ensure_pipeline(sha, ref)
+ pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref)
end
def enable_ci
@@ -1011,4 +1072,22 @@ class Project < ActiveRecord::Base
update_attribute(:pending_delete, true)
end
+
+ def running_or_pending_build_count(force: false)
+ Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
+ builds.running_or_pending.count(:all)
+ end
+ end
+
+ def mark_import_as_failed(error_message)
+ original_errors = errors.dup
+ sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
+
+ import_fail
+ update_column(:import_error, sanitized_message)
+ rescue ActiveRecord::ActiveRecordError => e
+ Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ ensure
+ @errors = original_errors
+ end
end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 2e5e854fc5e..58cb720c3c1 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -83,7 +83,7 @@ class IrkerService < Service
self.channels = recipients.split(/\s+/).map do |recipient|
format_channel(recipient)
end
- channels.reject! &:nil?
+ channels.reject!(&:nil?)
end
def format_channel(recipient)
diff --git a/app/models/service.rb b/app/models/service.rb
index de3fd24584a..bf352397509 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -16,6 +16,7 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties
after_commit :reset_updated_properties
+ after_commit :cache_project_has_external_issue_tracker
belongs_to :project
has_one :service_hook
@@ -34,6 +35,7 @@ class Service < ActiveRecord::Base
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) }
+ scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
default_value_for :category, 'common'
@@ -192,4 +194,12 @@ class Service < ActiveRecord::Base
service.project_id = project_id
service if service.save
end
+
+ private
+
+ def cache_project_has_external_issue_tracker
+ if project && !project.destroyed?
+ project.cache_has_external_issue_tracker
+ end
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 407697b745c..f8034cb5e6b 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -102,7 +102,7 @@ class Snippet < ActiveRecord::Base
end
def notes_with_associations
- notes.includes(:author, :project)
+ notes.includes(:author)
end
class << self
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
new file mode 100644
index 00000000000..00b19686d48
--- /dev/null
+++ b/app/models/u2f_registration.rb
@@ -0,0 +1,40 @@
+# Registration information for U2F (universal 2nd factor) devices, like Yubikeys
+
+class U2fRegistration < ActiveRecord::Base
+ belongs_to :user
+
+ def self.register(user, app_id, json_response, challenges)
+ u2f = U2F::U2F.new(app_id)
+ registration = self.new
+
+ begin
+ response = U2F::RegisterResponse.load_from_json(json_response)
+ registration_data = u2f.register!(challenges, response)
+ registration.update(certificate: registration_data.certificate,
+ key_handle: registration_data.key_handle,
+ public_key: registration_data.public_key,
+ counter: registration_data.counter,
+ user: user)
+ rescue JSON::ParserError, NoMethodError, ArgumentError
+ registration.errors.add(:base, 'Your U2F device did not send a valid JSON response.')
+ rescue U2F::Error => e
+ registration.errors.add(:base, e.message)
+ end
+
+ registration
+ end
+
+ def self.authenticate(user, app_id, json_response, challenges)
+ response = U2F::SignResponse.load_from_json(json_response)
+ registration = user.u2f_registrations.find_by_key_handle(response.key_handle)
+ u2f = U2F::U2F.new(app_id)
+
+ if registration
+ u2f.authenticate!(challenges, response, Base64.decode64(registration.public_key), registration.counter)
+ registration.update(counter: response.counter)
+ true
+ end
+ rescue JSON::ParserError, NoMethodError, ArgumentError, U2F::Error
+ false
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 15b6cbc2255..e0987e07e1f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -27,7 +27,6 @@ class User < ActiveRecord::Base
devise :two_factor_authenticatable,
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
serialize :otp_backup_codes, JSON
@@ -51,6 +50,7 @@ class User < ActiveRecord::Base
has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy
has_many :identities, dependent: :destroy, autosave: true
+ has_many :u2f_registrations, dependent: :destroy
# Groups
has_many :members, dependent: :destroy
@@ -84,6 +84,7 @@ class User < ActiveRecord::Base
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
+ has_many :award_emoji, as: :awardable, dependent: :destroy
#
# Validations
@@ -174,8 +175,16 @@ class User < ActiveRecord::Base
scope :active, -> { with_state(:active) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
- scope :with_two_factor, -> { where(two_factor_enabled: true) }
- scope :without_two_factor, -> { where(two_factor_enabled: false) }
+
+ def self.with_two_factor
+ joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
+ where("u2f.id IS NOT NULL OR otp_required_for_login = ?", true).distinct(arel_table[:id])
+ end
+
+ def self.without_two_factor
+ joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
+ where("u2f.id IS NULL AND otp_required_for_login = ?", false)
+ end
#
# Class methods
@@ -322,14 +331,29 @@ class User < ActiveRecord::Base
end
def disable_two_factor!
- update_attributes(
- two_factor_enabled: false,
- encrypted_otp_secret: nil,
- encrypted_otp_secret_iv: nil,
- encrypted_otp_secret_salt: nil,
- otp_grace_period_started_at: nil,
- otp_backup_codes: nil
- )
+ transaction do
+ update_attributes(
+ otp_required_for_login: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_grace_period_started_at: nil,
+ otp_backup_codes: nil
+ )
+ self.u2f_registrations.destroy_all
+ end
+ end
+
+ def two_factor_enabled?
+ two_factor_otp_enabled? || two_factor_u2f_enabled?
+ end
+
+ def two_factor_otp_enabled?
+ self.otp_required_for_login?
+ end
+
+ def two_factor_u2f_enabled?
+ self.u2f_registrations.exists?
end
def namespace_uniq
@@ -776,6 +800,23 @@ class User < ActiveRecord::Base
notification_settings.find_or_initialize_by(source: source)
end
+ def assigned_open_merge_request_count(force: false)
+ Rails.cache.fetch(['users', id, 'assigned_open_merge_request_count'], force: force) do
+ assigned_merge_requests.opened.count
+ end
+ end
+
+ def assigned_open_issues_count(force: false)
+ Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do
+ assigned_issues.opened.count
+ end
+ end
+
+ def update_cache_counts
+ assigned_open_merge_request_count(force: true)
+ assigned_open_issues_count(force: true)
+ end
+
private
def projects_union
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 18274ce24e2..64bcdac5c65 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -1,11 +1,11 @@
module Ci
class CreateBuildsService
- def initialize(commit)
- @commit = commit
+ def initialize(pipeline)
+ @pipeline = pipeline
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)
+ builds_attrs = config_processor.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
@@ -21,8 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
- unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
- trigger_request: trigger_request, name: build_attrs[:name])
+ unless @pipeline.builds.find_by(ref: @pipeline.ref, tag: @pipeline.tag,
+ trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
@@ -31,13 +31,13 @@ module Ci
:stage,
:stage_idx)
- build_attrs.merge!(ref: @commit.ref,
- tag: @commit.tag,
+ build_attrs.merge!(ref: @pipeline.ref,
+ tag: @pipeline.tag,
trigger_request: trigger_request,
user: user,
- project: @commit.project)
+ project: @pipeline.project)
- @commit.builds.create!(build_attrs)
+ @pipeline.builds.create!(build_attrs)
end
end
end
@@ -45,7 +45,7 @@ module Ci
private
def config_processor
- @config_processor ||= @commit.config_processor
+ @config_processor ||= @pipeline.config_processor
end
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 5bc0c31cb42..a7751b8effc 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -1,7 +1,7 @@
module Ci
class CreatePipelineService < BaseService
def execute
- pipeline = project.ci_commits.new(params)
+ pipeline = project.pipelines.new(params)
unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found')
@@ -19,7 +19,7 @@ module Ci
end
begin
- Ci::Commit.transaction do
+ Ci::Pipeline.transaction do
pipeline.sha = commit.id
unless pipeline.config_processor
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 993acf11db9..1e629cf119a 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.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
+ pipeline = project.pipelines.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
- commit: ci_commit,
+ pipeline: pipeline,
)
- if ci_commit.create_builds(nil, trigger_request)
+ if pipeline.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 3018f27ec05..75d847d5bee 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -3,9 +3,9 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
- 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)
+ pipelines = project.pipelines.where(sha: sha)
+ pipelines = pipelines.where(ref: opts[:ref]) if opts[:ref]
+ image_name = image_for_status(pipelines.status)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 5b6fefe669e..418f5cf8091 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -18,23 +18,23 @@ class CreateCommitBuildsService
return false
end
- commit = Ci::Commit.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
+ pipeline = Ci::Pipeline.new(project: project, 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
+ # Skip creating pipeline when no gitlab-ci.yml is found
+ unless pipeline.ci_yaml_file
return false
end
- # Create a new ci_commit
- commit.save!
+ # Create a new pipeline
+ pipeline.save!
# Skip creating builds for commits that have [ci skip]
- unless commit.skip_ci?
+ unless pipeline.skip_ci?
# Create builds for commit
- commit.create_builds(user)
+ pipeline.create_builds(user)
end
- commit.touch
- commit
+ pipeline.touch
+ pipeline
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2b16089df1b..e3dc569152c 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -45,6 +45,8 @@ class IssuableBaseService < BaseService
unless can?(current_user, ability, project)
params.delete(:milestone_id)
+ params.delete(:add_label_ids)
+ params.delete(:remove_label_ids)
params.delete(:label_ids)
params.delete(:assignee_id)
end
@@ -67,10 +69,34 @@ class IssuableBaseService < BaseService
end
def filter_labels
- return if params[:label_ids].to_a.empty?
+ if params[:add_label_ids].present? || params[:remove_label_ids].present?
+ params.delete(:label_ids)
+
+ filter_labels_in_param(:add_label_ids)
+ filter_labels_in_param(:remove_label_ids)
+ else
+ filter_labels_in_param(:label_ids)
+ end
+ end
+
+ def filter_labels_in_param(key)
+ return if params[key].to_a.empty?
- params[:label_ids] =
- project.labels.where(id: params[:label_ids]).pluck(:id)
+ params[key] = project.labels.where(id: params[key]).pluck(:id)
+ end
+
+ def update_issuable(issuable, attributes)
+ issuable.with_transaction_returning_status do
+ add_label_ids = attributes.delete(:add_label_ids)
+ remove_label_ids = attributes.delete(:remove_label_ids)
+
+ issuable.label_ids |= add_label_ids if add_label_ids
+ issuable.label_ids -= remove_label_ids if remove_label_ids
+
+ issuable.assign_attributes(attributes.merge(updated_by: current_user))
+
+ issuable.save
+ end
end
def update(issuable)
@@ -78,7 +104,7 @@ class IssuableBaseService < BaseService
filter_params
old_labels = issuable.labels.to_a
- if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
+ if params.present? && update_issuable(issuable, params)
issuable.reset_events_cache
handle_common_system_notes(issuable, old_labels: old_labels)
handle_changes(issuable, old_labels: old_labels)
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
index de8387c4900..15825b81685 100644
--- a/app/services/issues/bulk_update_service.rb
+++ b/app/services/issues/bulk_update_service.rb
@@ -4,9 +4,9 @@ module Issues
issues_ids = params.delete(:issues_ids).split(",")
issue_params = params
- issue_params.delete(:state_event) unless issue_params[:state_event].present?
- issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present?
- issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present?
+ %i(state_event milestone_id assignee_id add_label_ids remove_label_ids).each do |key|
+ issue_params.delete(key) unless issue_params[key].present?
+ end
issues = Issue.where(id: issues_ids)
issues.each do |issue|
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index e61628086f0..ab667456db7 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -24,6 +24,7 @@ module Issues
@new_issue = create_new_issue
rewrite_notes
+ rewrite_award_emoji
add_note_moved_from
# Old issue tasks
@@ -72,6 +73,14 @@ module Issues
end
end
+ def rewrite_award_emoji
+ @old_issue.award_emoji.each do |award|
+ new_award = award.dup
+ new_award.awardable = @new_issue
+ new_award.save
+ end
+ end
+
def rewrite_content(content)
return unless content
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 9d7fca6882d..bc93ba2552d 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -55,12 +55,12 @@ module MergeRequests
def each_merge_request(commit_status)
merge_request_from(commit_status).each do |merge_request|
- ci_commit = merge_request.ci_commit
+ pipeline = merge_request.pipeline
- next unless ci_commit
- next unless ci_commit.sha == commit_status.sha
+ next unless pipeline
+ next unless pipeline.sha == commit_status.sha
- yield merge_request, ci_commit
+ yield merge_request, pipeline
end
end
end
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
index 8fd6a4ea1f6..12edfb2d671 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -20,10 +20,10 @@ module MergeRequests
# Triggers the automatic merge of merge_request once the build succeeds
def trigger(commit_status)
- each_merge_request(commit_status) do |merge_request, ci_commit|
+ each_merge_request(commit_status) do |merge_request, pipeline|
next unless merge_request.merge_when_build_succeeds?
next unless merge_request.mergeable?
- next unless ci_commit.success?
+ next unless pipeline.success?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2bb312bb252..02fca5c0ea3 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,6 +5,13 @@ module Notes
note.author = current_user
note.system = false
+ if note.award_emoji?
+ noteable = note.noteable
+ todo_service.new_award_emoji(noteable, current_user)
+
+ return noteable.create_award_emoji(note.award_emoji_name, current_user)
+ end
+
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index e818f58d13c..534c48aefff 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -8,7 +8,7 @@ module Notes
def execute
# Skip system notes, like status changes and cross-references and awards
- unless @note.system || @note.is_award
+ unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
@note.create_cross_references!
execute_note_hooks
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 42ec1ac9e1a..91ca82ed3b7 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -130,8 +130,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed')
- return true if note.cross_reference? && note.system == true
- return true if note.is_award
+ return true if note.cross_reference? && note.system?
target = note.noteable
diff --git a/app/services/oauth2/access_token_validation_service.rb b/app/services/oauth2/access_token_validation_service.rb
index 6194f6ce91e..264fdccde8f 100644
--- a/app/services/oauth2/access_token_validation_service.rb
+++ b/app/services/oauth2/access_token_validation_service.rb
@@ -22,6 +22,7 @@ module Oauth2::AccessTokenValidationService
end
protected
+
# True if the token's scope is a superset of required scopes,
# or the required scopes is empty.
def sufficient_scope?(token, scopes)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 6728fabea1e..61cac5419ad 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -56,14 +56,14 @@ module Projects
after_create_actions if @project.persisted?
- @project.add_import_job if @project.import?
-
+ if @project.errors.empty?
+ @project.add_import_job if @project.import?
+ else
+ fail(error: @project.errors.full_messages.join(', '))
+ end
@project
rescue => e
- message = "Unable to save project: #{e.message}"
- Rails.logger.error(message)
- @project.errors.add(:base, message) if @project
- @project
+ fail(error: e.message)
end
protected
@@ -103,5 +103,19 @@ module Projects
end
end
end
+
+ def fail(error:)
+ message = "Unable to save project. Error: #{error}"
+ message << "Project ID: #{@project.id}" if @project && @project.id
+
+ Rails.logger.error(message)
+
+ if @project && @project.import?
+ @project.errors.add(:base, message)
+ @project.mark_import_as_failed(message)
+ end
+
+ @project
+ end
end
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index ef15ef6a473..c4838d31f2f 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -39,7 +39,7 @@ module Projects
begin
gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
rescue Gitlab::Shell::Error => e
- raise Error, e.message
+ raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 4bf4e144727..8e03ff8ddde 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -20,7 +20,7 @@ class TodoService
# * mark all pending todos related to the issue for the current user as done
#
def update_issue(issue, current_user)
- create_mention_todos(issue.project, issue, current_user)
+ update_issuable(issue, current_user)
end
# When close an issue we should:
@@ -53,7 +53,7 @@ class TodoService
# * create a todo for each mentioned user on merge request
#
def update_merge_request(merge_request, current_user)
- create_mention_todos(merge_request.project, merge_request, current_user)
+ update_issuable(merge_request, current_user)
end
# When close a merge request we should:
@@ -122,6 +122,14 @@ class TodoService
handle_note(note, current_user)
end
+ # When an emoji is awarded we should:
+ #
+ # * mark all pending todos related to the awardable for the current user as done
+ #
+ def new_award_emoji(awardable, current_user)
+ mark_pending_todos_as_done(awardable, current_user)
+ end
+
# When marking pending todos as done we should:
#
# * mark all pending todos related to the target for the current user as done
@@ -145,6 +153,13 @@ class TodoService
create_mention_todos(issuable.project, issuable, author)
end
+ def update_issuable(issuable, author)
+ # Skip toggling a task list item in a description
+ return if issuable.tasks? && issuable.updated_tasks.any?
+
+ create_mention_todos(issuable.project, issuable, author)
+ end
+
def handle_note(note, author)
# Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet?
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index f149f9eb431..c883e8f97da 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -155,6 +155,11 @@
= f.text_area :sign_in_text, class: 'form-control', rows: 4
.help-block Markdown enabled
.form-group
+ = f.label :after_sign_up_text, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_area :after_sign_up_text, class: 'form-control', rows: 4
+ .help-block Markdown enabled
+ .form-group
= f.label :help_page_text, class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :help_page_text, class: 'form-control', rows: 4
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index c3784bf7192..e049b40bfab 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -99,8 +99,8 @@
%td.build-link
- if project
- = link_to ci_status_path(build.commit) do
- %strong #{build.commit.short_sha}
+ = link_to ci_status_path(build.pipeline) do
+ %strong #{build.pipeline.short_sha}
%td.timestamp
- if build.finished_at
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
new file mode 100644
index 00000000000..02efcecc889
--- /dev/null
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -0,0 +1,15 @@
+- grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
+.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) } }
+ - awards_sort(grouped_emojis).each do |emoji, awards|
+ %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", class: (award_active_class(awards, current_user)), data: { placement: "bottom", title: award_user_list(awards, current_user) } }
+ = emoji_icon(emoji, sprite: false)
+ %span.award-control-text.js-counter
+ = awards.count
+
+ - if current_user
+ .award-menu-holder.js-award-holder
+ %button.btn.award-control.js-add-award{ type: "button" }
+ = icon('smile-o', class: "award-control-icon award-control-icon-normal")
+ = icon('spinner spin', class: "award-control-icon award-control-icon-loading")
+ %span.award-control-text
+ Add
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 3d17f74b709..23c145ebbb4 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -9,5 +9,4 @@
- if current_user.can_create_group?
.nav-controls
= link_to new_group_path, class: "btn btn-new" do
- = icon('plus')
New Group
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 9da3fcbd986..d35f332e1e0 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -18,5 +18,4 @@
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
- = icon('plus')
New Project
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
index 3c3830a3f10..73c3a3dd2eb 100644
--- a/app/views/devise/confirmations/almost_there.haml
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -3,6 +3,9 @@
Almost there...
%p.lead
Please check your email to confirm your account
+- if after_sign_up_text.present?
+ .well-confirmation.text-center
+ = markdown(after_sign_up_text)
%p.confirmation-content.text-center
No confirmation email received? Please check your spam folder or
.append-bottom-20.prepend-top-20.text-center
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index 8c6a1552a53..a373f61bd3c 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -1,11 +1,19 @@
%div
.login-box
.login-heading
- %h3 Two-factor Authentication
+ %h3 Two-Factor Authentication
.login-body
- = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.hidden_field :remember_me, value: params[resource_name][:remember_me]
- = 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"
+ - if @user.two_factor_otp_enabled?
+ %h5 Authenticate via Two-Factor App
+ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
+ - resource_params = params[resource_name].presence || params
+ = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
+ = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off'
+ %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"
+
+ - if @user.two_factor_u2f_enabled?
+
+ %hr
+ = render "u2f/authenticate"
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 510215bb8cd..905a8dbcd84 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -16,7 +16,7 @@
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters"
%div
- if current_application_settings.recaptcha_enabled
= recaptcha_tags
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
index 3443a8e2307..97401a2e618 100644
--- a/app/views/emojis/index.html.haml
+++ b/app/views/emojis/index.html.haml
@@ -1,9 +1,9 @@
.emoji-menu
.emoji-menu-content
= text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
- - AwardEmoji.emoji_by_category.each do |category, emojis|
+ - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
%h5.emoji-menu-title
- = AwardEmoji::CATEGORIES[category]
+ = Gitlab::AwardEmoji::CATEGORIES[category]
%ul.clearfix.emoji-menu-list
- emojis.each do |emoji|
%li.pull-left.text-center.emoji-menu-list-item
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index c7f29f2fc0e..2e2403347c1 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,10 +1,14 @@
.event-title
%span.author_name= link_to_author event
%span.event_label{class: event.action_name}
- = event_action_name(event)
-
- if event.target
- %strong= link_to event.target.reference_link_text, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
+ = event.action_name
+ %strong
+ = link_to [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title do
+ = event.target_type.titleize.downcase
+ = event.target.reference_link_text
+ - else
+ = event_action_name(event)
= event_preposition(event)
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 60234be8f83..6bb542e658d 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -34,9 +34,9 @@
%strong.member-access-level= member.human_access
- if show_controls
- if can?(current_user, :update_group_member, member)
- = button_tag class: "btn-xs btn js-toggle-button",
+ = button_tag class: "btn-xs btn btn-grouped inline js-toggle-button",
title: 'Edit access level', type: 'button' do
- %i.fa.fa-pencil-square-o
+ = icon('pencil')
- if can?(current_user, :destroy_group_member, member)
&nbsp;
@@ -46,7 +46,7 @@
Leave
- else
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
- %i.fa.fa-minus.fa-inverse
+ = icon('trash')
.edit-member.hide.js-toggle-content
%br
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 7d9d27ae1fc..ca6c4326d1c 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -39,9 +39,8 @@
.col-md-6
.form-group
= f.label :due_date, "Due Date", class: "control-label"
- .col-sm-10= f.hidden_field :due_date
.col-sm-10
- .datepicker
+ = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions
= f.submit 'Create Milestone', class: "btn-create btn"
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 77c297255b8..85635bc4616 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -5,7 +5,7 @@
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
.cover-block.groups-cover-block
- .container-fluid.container-limited
+ %div{ class: (container_class) }
= link_to group_icon(@group), target: '_blank' do
= image_tag group_icon(@group), class: "avatar group-avatar s70"
.group-info
@@ -35,7 +35,6 @@
= render 'shared/projects/dropdown'
- if can? current_user, :create_projects, @group
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
- = icon('plus')
New Project
.tab-content
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 70e88da7aae..01648047ce2 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -24,7 +24,7 @@
%td Show/hide this dialog
%tr
%td.shortcut
- - if browser.mac?
+ - if browser.platform.mac?
.key &#8984; shift p
- else
.key ctrl shift p
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 5b7f11440c1..6c4a9d68d1f 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -4,6 +4,10 @@
%i.fa.fa-github
Import projects from GitHub
+%p
+ %i.fa.fa-warning
+ To import GitHub pull requests, any pull request source branches that had been deleted are temporarily restored on GitHub. To prevent any connected CI services from being overloaded with dozens of irrelevant branches being created and deleted again, GitHub webhooks are temporarily disabled during the import process.
+
%p.light
Select projects you want to import.
%hr
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index b30fb0a5da9..e0ed657919e 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -35,8 +35,6 @@
= csrf_meta_tags
- = include_gon
-
- unless browser.safari?
%meta{name: 'referrer', content: 'origin-when-cross-origin'}
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1e961853c70..261038ef940 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,11 +1,9 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- .header-logo
- %a#logo
- = brand_header_logo
- = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
- .gitlab-text-container
- %h3 GitLab
+ = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
+ .header-logo
+ #logo
+ = brand_header_logo
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
@@ -17,10 +15,8 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- = link_to current_user, class: 'sidebar-user', title: "Profile" do
- = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
- .username
- = current_user.username
+ = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
+ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s46'
- if defined?(nav) && nav
.layout-nav
.container-fluid
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 6b208c3d0bb..b49207fc315 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -6,11 +6,8 @@
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
- .search-location-badge
- - if label.present?
- %span.location-badge
- %i.location-text
- = label
+ - if label.present?
+ .location-badge= label
.search-input-wrap
.dropdown{ data: {url: search_autocomplete_path } }
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index e4d1c773d03..2b86b289bbe 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,6 +2,8 @@
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{user_application_theme}", 'data-page' => body_data_page}
+ = Gon::Base.render_data
+
-# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
= yield :scripts_body_top
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index f08cb0a5428..3d28eec84ef 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body.ui_charcoal.login-page.application.navless
+ = Gon::Base.render_data
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index 7c061dd531f..6bd427b02ac 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body.ui_charcoal.login-page.application.navless
+ = Gon::Base.render_data
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 915acc4612e..7fbe065df00 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -2,6 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{user_application_theme} application navless"}
+ = Gon::Base.render_data
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index f292730fe45..de2276e75e4 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -2,106 +2,102 @@
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw')
- %span
+ .nav-link-text
Overview
= nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects' do
= icon('cube fw')
- %span
+ .nav-link-text
Projects
= nav_link(controller: :users) do
= link_to admin_users_path, title: 'Users' do
= icon('user fw')
- %span
+ .nav-link-text
Users
= nav_link(controller: :groups) do
= link_to admin_groups_path, title: 'Groups' do
= icon('group fw')
- %span
+ .nav-link-text
Groups
= nav_link(controller: :deploy_keys) do
= link_to admin_deploy_keys_path, title: 'Deploy Keys' do
= icon('key fw')
- %span
+ .nav-link-text
Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
- %span
+ .nav-link-text
Runners
- %span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
- %span
+ .nav-link-text
Builds
- %span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
- %span
+ .nav-link-text
Logs
= nav_link(controller: :health_check) do
= link_to admin_health_check_path, title: 'Health Check' do
= icon('medkit fw')
- %span
+ .nav-link-text
Health Check
= nav_link(controller: :broadcast_messages) do
= link_to admin_broadcast_messages_path, title: 'Messages' do
= icon('bullhorn fw')
- %span
+ .nav-link-text
Messages
= nav_link(controller: :hooks) do
= link_to admin_hooks_path, title: 'Hooks' do
= icon('external-link fw')
- %span
+ .nav-link-text
Hooks
= nav_link(controller: :background_jobs) do
= link_to admin_background_jobs_path, title: 'Background Jobs' do
= icon('cog fw')
- %span
+ .nav-link-text
Background Jobs
= nav_link(controller: :appearances) do
= link_to admin_appearances_path, title: 'Appearances' do
= icon('image')
- %span
+ .nav-link-text
Appearance
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do
= icon('cloud fw')
- %span
+ .nav-link-text
Applications
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path, title: 'Service Templates' do
= icon('copy fw')
- %span
+ .nav-link-text
Service Templates
= nav_link(controller: :labels) do
= link_to admin_labels_path, title: 'Labels' do
= icon('tags fw')
- %span
+ .nav-link-text
Labels
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
= icon('exclamation-circle fw')
- %span
+ .nav-link-text
Abuse Reports
- %span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
- %span
+ .nav-link-text
Spam Logs
- %span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
- %span
+ .nav-link-text
Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 43532b0c155..b73fde7797f 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,55 +1,51 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- = icon('bookmark fw')
- %span
+ = navbar_icon('project')
+ .nav-link-text
Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw')
- %span
+ .nav-link-text
Todos
- %span.count.todos-pending-count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- = icon('dashboard fw')
- %span
+ = navbar_icon('activity')
+ .nav-link-text
Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
- = icon('group fw')
- %span
+ = navbar_icon('group')
+ .nav-link-text
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
- = icon('clock-o fw')
- %span
+ = navbar_icon('milestones')
+ .nav-link-text
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- = icon('exclamation-circle fw')
- %span
+ = navbar_icon('issues')
+ .nav-link-text
Issues
- %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- = icon('tasks fw')
- %span
+ = navbar_icon('mr')
+ .nav-link-text
Merge Requests
- %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
- %span
+ .nav-link-text
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
- %span
+ .nav-link-text
Help
-
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
- %span
+ .nav-link-text
Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 3b40006a0cc..46fcf1545f2 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -2,20 +2,20 @@
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects' do
= icon('bookmark fw')
- %span
+ .nav-link-text
Projects
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to explore_groups_path, title: 'Groups' do
= icon('group fw')
- %span
+ .nav-link-text
Groups
= nav_link(controller: :snippets) do
= link_to explore_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
- %span
+ .nav-link-text
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
= icon('question-circle fw')
- %span
+ .nav-link-text
Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index de15add3617..9cbee0aa363 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -5,36 +5,36 @@
.fade-left
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
- = icon('group fw')
+ = navbar_icon('group')
%span
Group
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
- = icon('dashboard fw')
+ = navbar_icon('activity')
%span
Activity
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do
- = icon('clock-o fw')
+ = navbar_icon('milestones')
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues' do
- = icon('exclamation-circle fw')
+ = navbar_icon('issues')
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%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')
+ = navbar_icon('mr')
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute
%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')
+ = navbar_icon('members')
%span
Members
.fade-right
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 2efc6c48a48..09d9f0184be 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -10,11 +10,12 @@
= icon('gear fw')
%span
Account
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
- = icon('cloud fw')
- %span
- Applications
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ = icon('cloud fw')
+ %span
+ Applications
= nav_link(controller: :emails) do
= link_to profile_emails_path, title: 'Emails' do
= icon('envelope-o fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 2c9b9006668..79cc5c02888 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -24,32 +24,27 @@
.fade-left
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
- = icon('bookmark fw')
+ = navbar_icon('project')
%span
Project
+
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
- = icon('dashboard fw')
+ = navbar_icon('activity')
%span
Activity
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
- = icon('files-o fw')
- %span
- Files
- - if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
- = icon('history fw')
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
+ = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
+ = icon('code fw')
%span
- Commits
+ Code
- if project_nav_tab? :pipelines
= nav_link(controller: :pipelines) do
= link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- = icon('ship fw')
+ = navbar_icon('pipelines')
%span
Pipelines
@@ -58,7 +53,7 @@
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
= icon('hdd-o fw')
%span
- Container Registry
+ Registry
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
@@ -67,17 +62,10 @@
%span
Graphs
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
- = icon('clock-o fw')
- %span
- Milestones
-
- if project_nav_tab? :issues
- = nav_link(controller: :issues) do
+ = nav_link(controller: [:issues, :labels, :milestones]) do
= link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
- = icon('exclamation-circle fw')
+ = navbar_icon('issues')
%span
Issues
- if @project.default_issues_tracker?
@@ -86,22 +74,15 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
- = icon('tasks fw')
+ = navbar_icon('mr')
%span
Merge Requests
%span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
- = icon('tags fw')
- %span
- Labels
-
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
- = icon('book fw')
+ = navbar_icon('wiki')
%span
Wiki
@@ -129,4 +110,9 @@
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
Builds
+ -# Shortcut to commits page
+ - if project_nav_tab? :commits
+ %li.hidden
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+ Commits
.fade-right
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 20d6cdf7246..2049b204956 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -5,8 +5,8 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- - if @project_wiki
- - markdown_preview_path = namespace_project_wikis_markdown_preview_path(project.namespace, project)
+ - if @project_wiki && @page
+ - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
- else
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
index 81d65037312..4bf7c1f4d64 100644
--- a/app/views/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -10,7 +10,7 @@
%p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
- Author: #{@build.commit.git_author_name}
+ Author: #{@build.pipeline.git_author_name}
%p
Branch: #{@build.ref}
%p
@@ -18,7 +18,7 @@
%p
Job: #{@build.name}
%p
- Message: #{@build.commit.git_commit_message}
+ Message: #{@build.pipeline.git_commit_message}
%p
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb
index 675acea60a1..9d497983498 100644
--- a/app/views/notify/build_fail_email.text.erb
+++ b/app/views/notify/build_fail_email.text.erb
@@ -1,11 +1,11 @@
Build failed for <%= @project.name %>
Status: <%= @build.status %>
-Commit: <%= @build.commit.short_sha %>
-Author: <%= @build.commit.git_author_name %>
+Commit: <%= @build.pipeline.short_sha %>
+Author: <%= @build.pipeline.git_author_name %>
Branch: <%= @build.ref %>
Stage: <%= @build.stage %>
Job: <%= @build.name %>
-Message: <%= @build.commit.git_commit_message %>
+Message: <%= @build.pipeline.git_commit_message %>
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
index 5d247eb4cf2..252a5b7152c 100644
--- a/app/views/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -10,7 +10,7 @@
%p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p
- Author: #{@build.commit.git_author_name}
+ Author: #{@build.pipeline.git_author_name}
%p
Branch: #{@build.ref}
%p
@@ -18,7 +18,7 @@
%p
Job: #{@build.name}
%p
- Message: #{@build.commit.git_commit_message}
+ Message: #{@build.pipeline.git_commit_message}
%p
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb
index 747da44acae..c5ed4f84861 100644
--- a/app/views/notify/build_success_email.text.erb
+++ b/app/views/notify/build_success_email.text.erb
@@ -1,11 +1,11 @@
Build successful for <%= @project.name %>
Status: <%= @build.status %>
-Commit: <%= @build.commit.short_sha %>
-Author: <%= @build.commit.git_author_name %>
+Commit: <%= @build.pipeline.short_sha %>
+Author: <%= @build.pipeline.git_author_name %>
Branch: <%= @build.ref %>
Stage: <%= @build.stage %>
Job: <%= @build.name %>
-Message: <%= @build.commit.git_commit_message %>
+Message: <%= @build.pipeline.git_commit_message %>
Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 01ac8161945..3d2a245ecbd 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -11,7 +11,7 @@
%p
Your private token is used to access application resources without authentication.
.col-lg-9
- = form_for @user, url: reset_private_token_profile_path, method: :put, html: {class: "private-token"} do |f|
+ = form_for @user, url: reset_private_token_profile_path, method: :put, html: { class: "private-token" } do |f|
%p.cgray
- if current_user.private_token
= label_tag "token", "Private token", class: "label-light"
@@ -29,21 +29,22 @@
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
- Two-factor Authentication
+ Two-Factor Authentication
%p
- Increase your account's security by enabling two-factor authentication (2FA).
+ Increase your account's security by enabling Two-Factor Authentication (2FA).
.col-lg-9
%p
- Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
- - if !current_user.two_factor_enabled?
- %p
- Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
- More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
- .append-bottom-10
- = link_to 'Enable two-factor authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
+ Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
+ - if current_user.two_factor_enabled?
+ = link_to 'Manage Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-info'
+ = link_to 'Disable', profile_two_factor_auth_path,
+ method: :delete,
+ data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
+ class: 'btn btn-danger'
- else
- = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-danger',
- data: { confirm: 'Are you sure?' }
+ .append-bottom-10
+ = link_to 'Enable Two-Factor Authentication', profile_two_factor_auth_path, class: 'btn btn-success'
+
%hr
- if button_based_providers.any?
.row.prepend-top-default
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
deleted file mode 100644
index 69fc81cb45c..00000000000
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-- page_title 'Two-factor Authentication', 'Account'
-
-.row.prepend-top-default
- .col-lg-3
- %h4.prepend-top-0
- Two-factor Authentication (2FA)
- %p
- Increase your account's security by enabling two-factor authentication (2FA).
- .col-lg-9
- %p
- Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
- More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
- .row.append-bottom-10
- .col-md-3
- = raw @qr_code
- .col-md-9
- .account-well
- %p.prepend-top-0.append-bottom-0
- Can't scan the code?
- %p.prepend-top-0.append-bottom-0
- To add the entry manually, provide the following details to the application on your phone.
- %p.prepend-top-0.append-bottom-0
- Account:
- = current_user.email
- %p.prepend-top-0.append-bottom-0
- Key:
- = current_user.otp_secret.scan(/.{4}/).join(' ')
- %p.two-factor-new-manual-content
- Time based: Yes
- = form_tag profile_two_factor_auth_path, method: :post do |f|
- - if @error
- .alert.alert-danger
- = @error
- .form-group
- = label_tag :pin_code, nil, class: "label-light"
- = text_field_tag :pin_code, nil, class: "form-control", required: true
- .prepend-top-default
- = submit_tag 'Enable two-factor authentication', class: 'btn btn-success'
- = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch, class: 'btn btn-cancel' if two_factor_skippable?
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
new file mode 100644
index 00000000000..ce76cb73c9c
--- /dev/null
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -0,0 +1,69 @@
+- page_title 'Two-Factor Authentication', 'Account'
+- header_title "Two-Factor Authentication", profile_two_factor_auth_path
+
+.row.prepend-top-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Register Two-Factor Authentication App
+ %p
+ Use an app on your mobile device to enable two-factor authentication (2FA).
+ .col-lg-9
+ - if current_user.two_factor_otp_enabled?
+ = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page."
+ - else
+ %p
+ Download the Google Authenticator application from App Store or Google Play Store and scan this code.
+ More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
+ .row.append-bottom-10
+ .col-md-3
+ = raw @qr_code
+ .col-md-9
+ .account-well
+ %p.prepend-top-0.append-bottom-0
+ Can't scan the code?
+ %p.prepend-top-0.append-bottom-0
+ To add the entry manually, provide the following details to the application on your phone.
+ %p.prepend-top-0.append-bottom-0
+ Account:
+ = current_user.email
+ %p.prepend-top-0.append-bottom-0
+ Key:
+ = current_user.otp_secret.scan(/.{4}/).join(' ')
+ %p.two-factor-new-manual-content
+ Time based: Yes
+ = form_tag profile_two_factor_auth_path, method: :post do |f|
+ - if @error
+ .alert.alert-danger
+ = @error
+ .form-group
+ = label_tag :pin_code, nil, class: "label-light"
+ = text_field_tag :pin_code, nil, class: "form-control", required: true
+ .prepend-top-default
+ = submit_tag 'Register with Two-Factor App', class: 'btn btn-success'
+
+%hr
+
+.row.prepend-top-default
+
+ .col-lg-3
+ %h4.prepend-top-0
+ Register Universal Two-Factor (U2F) Device
+ %p
+ Use a hardware device to add the second factor of authentication.
+ %p
+ As U2F devices are only supported by a few browsers, it's recommended that you set up a
+ two-factor authentication app as well as a U2F device so you'll always be able to log in
+ using an unsupported browser.
+ .col-lg-9
+ %p
+ - if @registration_key_handles.present?
+ = icon "check inverse", base: "circle", class: "text-success", text: "You have #{pluralize(@registration_key_handles.size, 'U2F device')} registered with GitLab."
+ - if @u2f_registration.errors.present?
+ = form_errors(@u2f_registration)
+ = render "u2f/register"
+
+- if two_factor_skippable?
+ :javascript
+ var button = "<a class='btn btn-xs btn-warning pull-right' data-method='patch' href='#{skip_profile_two_factor_auth_path}'>Configure it later</a>";
+ $(".flash-alert").append(button);
+
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 57c3d1b0a65..f5bc1b4e409 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,6 +1,6 @@
- empty_repo = @project.empty_repo?
.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)}
- .container-fluid.container-limited
+ %div{ class: (container_class) }
.row
.project-image-container
= project_icon(@project, alt: '', class: 'project-avatar avatar s70')
@@ -29,10 +29,10 @@
.project-clone-holder
= render "shared/clone_panel"
- .project-repo-buttons.btn-group.project-right-buttons
- = render "projects/buttons/download"
- = render 'projects/buttons/dropdown'
- = render 'projects/buttons/notifications'
+ .project-repo-buttons.btn-group.project-right-buttons
+ = render "projects/buttons/download"
+ = render 'projects/buttons/dropdown'
+ = render 'projects/buttons/notifications'
:javascript
new Star();
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 81afea2c60a..28a28282fd3 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -7,6 +7,12 @@
%li
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
+
+ - if defined?(@issue) && @issue.confidential?
+ %li.confidential-issue-warning
+ = icon('warning')
+ %span This is a confidential issue. Your comment will not be visible to the public.
+
%li.pull-right
%button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
Go full screen
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
new file mode 100644
index 00000000000..da522b53417
--- /dev/null
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -0,0 +1,11 @@
+%fieldset.builds-feature
+ %h5.prepend-top-0
+ Merge Requests
+ .form-group
+ .checkbox
+ = f.label :only_allow_merge_if_build_succeeds do
+ = f.check_box :only_allow_merge_if_build_succeeds
+ %strong Only allow merge requests to be merged if the build succeeds
+ .help-block
+ Builds need to be configured to enable this feature.
+ = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index ede01dcc1aa..539d07d634a 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,4 +1,5 @@
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
+- header_title project_title(@project, "Builds", project_builds_path(@project))
.top-block.row-content-block.clearfix
.pull-right
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 57e507e68c8..87c732626a6 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -21,12 +21,10 @@
.controls.hidden-xs
- if create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
- = icon('plus')
Merge Request
- if branch.name != @repository.root_ref
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-xs', method: :post, title: "Compare" do
- = icon("exchange")
Compare
- if can_remove_branch?(@project, branch.name)
diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml
deleted file mode 100644
index a21ddaf4930..00000000000
--- a/app/views/projects/branches/destroy.js.haml
+++ /dev/null
@@ -1 +0,0 @@
-$('.js-totalbranch-count').html("#{@repository.branch_count}")
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 08148b1a18b..e0367c40272 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,32 +1,34 @@
+- @no_container = true
- page_title "Branches"
= render "projects/commits/head"
-.row-content-block
- .pull-right
+
+%div{ class: (container_class) }
+ .top-area
+ .nav-text
+ Protected branches can be managed in project settings
+
- if can? current_user, :push_code, @project
- = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
- = icon('plus')
- New branch
- &nbsp;
- .dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- - if @sort.present?
- = @sort.humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- = link_to namespace_project_branches_path(sort: nil) do
- Name
- = link_to namespace_project_branches_path(sort: 'recently_updated') do
- = sort_title_recently_updated
- = link_to namespace_project_branches_path(sort: 'last_updated') do
- = sort_title_oldest_updated
- .oneline
- Protected branches can be managed in project settings
-- unless @branches.empty?
- %ul.content-list.all-branches
- - @branches.each do |branch|
- = render "projects/branches/branch", branch: branch
- = paginate @branches, theme: 'gitlab'
+ .nav-controls
+ = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ New branch
+ .dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light
+ - if @sort.present?
+ = @sort.humanize
+ - else
+ Name
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ = link_to namespace_project_branches_path(sort: nil) do
+ Name
+ = link_to namespace_project_branches_path(sort: 'recently_updated') do
+ = sort_title_recently_updated
+ = link_to namespace_project_branches_path(sort: 'last_updated') do
+ = sort_title_oldest_updated
+ - unless @branches.empty?
+ %ul.content-list.all-branches
+ - @branches.each do |branch|
+ = render "projects/branches/branch", branch: branch
+ = paginate @branches, theme: 'gitlab'
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
new file mode 100644
index 00000000000..51b5bd9db42
--- /dev/null
+++ b/app/views/projects/builds/_header.html.haml
@@ -0,0 +1,16 @@
+.content-block.build-header
+ = ci_status_with_icon(@build.status)
+ Build
+ %strong ##{@build.id}
+ for commit
+ = link_to ci_status_path(@build.pipeline) do
+ %strong= @build.pipeline.short_sha
+ from
+ = link_to namespace_project_commits_path(@project.namespace, @project, @build.ref) do
+ %code
+ = @build.ref
+ - if @build.user
+ = render "user"
+ = time_ago_with_tooltip(@build.created_at)
+ %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
+ = icon('angle-double-left')
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
new file mode 100644
index 00000000000..5d931389dfb
--- /dev/null
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -0,0 +1,93 @@
+%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
+ .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
+ Build
+ %strong ##{@build.id}
+ %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
+ = icon('angle-double-right')
+ - if @build.coverage
+ .block.block-first
+ .title
+ Test coverage
+ %p.build-detail-row
+ #{@build.coverage}%
+
+ - if can?(current_user, :read_build, @project) && @build.artifacts?
+ .block{ class: ("block-first" if !@build.coverage) }
+ .title
+ Build artifacts
+ .btn-group.btn-group-justified{ role: :group }
+ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+ Download
+
+ - if @build.artifacts_metadata?
+ = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+ Browse
+
+ .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && @build.artifacts?)) }
+ .title
+ Build details
+ - if @build.retryable?
+ = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
+ - if @build.merge_request
+ %p.build-detail-row
+ %span.build-light-text Merge Request:
+ = link_to "#{@build.merge_request.to_reference}", merge_request_path(@build.merge_request)
+ - if @build.duration
+ %p.build-detail-row
+ %span.build-light-text Duration:
+ #{duration_in_words(@build.finished_at, @build.started_at)}
+ - if @build.finished_at
+ %p.build-detail-row
+ %span.build-light-text Finished:
+ #{time_ago_with_tooltip(@build.finished_at)}
+ - if @build.erased_at
+ %p.build-detail-row
+ %span.build-light-text Erased:
+ #{time_ago_with_tooltip(@build.erased_at)}
+ %p.build-detail-row
+ %span.build-light-text Runner:
+ - if @build.runner && current_user && current_user.admin
+ = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
+ - elsif @build.runner
+ \##{@build.runner.id}
+ .btn-group.btn-group-justified{ role: :group }
+ - if @build.has_trace?
+ = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default'
+ - if @build.active?
+ = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post
+ - if can?(current_user, :update_build, @project) && @build.erasable?
+ = link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
+ class: "btn btn-sm btn-default", method: :post,
+ data: { confirm: "Are you sure you want to erase this build?" } do
+ Erase
+
+ - if @build.trigger_request
+ .build-widget
+ %h4.title
+ Trigger
+
+ %p
+ %span.build-light-text Token:
+ #{@build.trigger_request.trigger.short_token}
+
+ - if @build.trigger_request.variables
+ %p
+ %span.build-light-text Variables:
+
+ %code
+ - @build.trigger_request.variables.each do |key, value|
+ #{key}=#{value}
+
+ .block
+ .title
+ Commit message
+ %p.build-light-text.append-bottom-0
+ #{@build.pipeline.git_commit_message}
+
+ - if @build.tags.any?
+ .block
+ .title
+ Tags
+ - @build.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml
new file mode 100644
index 00000000000..2642de8021d
--- /dev/null
+++ b/app/views/projects/builds/_user.html.haml
@@ -0,0 +1,4 @@
+by
+%a{ href: user_path(@build.user) }
+ = image_tag avatar_icon(@build.user, 24), class: "avatar s24"
+ %strong= @build.user.to_reference
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 818d5d28f04..181547316aa 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,62 +1,63 @@
+- @no_container = true
- page_title "Builds"
= render "projects/pipelines/head"
-.top-area
- %ul.nav-links
- %li{class: ('active' if @scope.nil?)}
- = link_to project_builds_path(@project) do
- All
- %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
- %span.badge.js-running-count
- = number_with_delimiter(@all_builds.running_or_pending.count(:id))
-
- %li{class: ('active' if @scope == 'finished')}
- = link_to project_builds_path(@project, scope: :finished) do
- Finished
- %span.badge.js-running-count
- = number_with_delimiter(@all_builds.finished.count(:id))
-
- .nav-controls
- - if can?(current_user, :update_build, @project)
- - if @all_builds.running_or_pending.any?
- = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
- data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-
- - unless @repository.gitlab_ci_yml
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
-
- = link_to ci_lint_path, class: 'btn btn-default' do
- = icon('wrench')
- %span CI Lint
-
-%ul.content-list
- - if @builds.blank?
- %li
- .nothing-here-block No builds to show
- - else
- .table-holder
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Commit
- %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, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
-
- = paginate @builds, theme: 'gitlab'
+%div{ class: (container_class) }
+ .top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_builds_path(@project) do
+ All
+ %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
+ %span.badge.js-running-count
+ = number_with_delimiter(@all_builds.running_or_pending.count(:id))
+
+ %li{class: ('active' if @scope == 'finished')}
+ = link_to project_builds_path(@project, scope: :finished) do
+ Finished
+ %span.badge.js-running-count
+ = number_with_delimiter(@all_builds.finished.count(:id))
+
+ .nav-controls
+ - if can?(current_user, :update_build, @project)
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
+ data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+
+ - unless @repository.gitlab_ci_yml
+ = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+
+ = link_to ci_lint_path, class: 'btn btn-default' do
+ %span CI Lint
+
+ %ul.content-list
+ - if @builds.blank?
+ %li
+ .nothing-here-block No builds to show
+ - else
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Commit
+ %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, 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 16017c994ba..a26f8aeb315 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,19 +1,11 @@
- page_title "#{@build.name} (##{@build.id})", "Builds"
- trace_with_state = @build.trace_with_state
+- header_title project_title(@project, "Builds", project_builds_path(@project))
.build-page
- .row-content-block.top-block
- Build ##{@build.id} for commit
- %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
- from
- = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
- - merge_request = @build.merge_request
- - if merge_request
- via
- = link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
+ = render "header"
- #up-build-trace
- - builds = @build.commit.builds.latest.to_a
+ - builds = @build.pipeline.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
@@ -33,18 +25,6 @@
&middot;
%i.fa.fa-warning
This build was retried.
-
- .row-content-block.middle-block
- .build-head
- .clearfix
- = ci_status_with_icon(@build.status)
- - if @build.duration
- %span
- %i.fa.fa-time
- #{duration_in_words(@build.finished_at, @build.started_at)}
- .pull-right
- #{time_ago_with_tooltip(@build.finished_at) if @build.finished_at}
-
- if @build.stuck?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning
@@ -64,158 +44,27 @@
= link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
Runners page
- .row.prepend-top-default
- .col-md-9
- .clearfix
- - if @build.active?
- .autoscroll-container
- %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
- .clearfix
+ .prepend-top-default
+ - if @build.active?
+ .autoscroll-container
+ %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
#js-build-scroll.scroll-controls
- = link_to '#up-build-trace', class: 'btn' do
+ = link_to '#build-trace', class: 'btn' do
%i.fa.fa-angle-up
= link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down
+ - if @build.erased?
+ .erased.alert.alert-warning
+ - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
+ Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
+ - else
+ %pre.build-trace#build-trace
+ %code.bash.js-build-output
+ = icon("refresh spin", class: "js-build-refresh")
- - if @build.erased?
- .erased.alert.alert-warning
- - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
- Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
- - else
- %pre.trace#build-trace
- %code.bash
- = preserve do
- = raw trace_with_state[:html]
- - if @build.active?
- %i{:class => "fa fa-refresh fa-spin"}
-
- %div#down-build-trace
-
- .col-md-3
- - if @build.coverage
- .build-widget
- %h4.title
- Test coverage
- %h1 #{@build.coverage}%
-
- - if can?(current_user, :read_build, @project) && @build.artifacts?
- .build-widget.artifacts
- %h4.title Build artifacts
- .center
- .btn-group{ role: :group }
- = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
- = icon('download')
- Download
-
- - if @build.artifacts_metadata?
- = link_to browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary' do
- = icon('folder-open')
- Browse
-
- .build-widget.build-controls
- %h4.title
- Build ##{@build.id}
- - if can?(current_user, :update_build, @project)
- .center
- .btn-group{ role: :group }
- - if @build.active?
- = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger', method: :post
- - elsif @build.retryable?
- = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post
-
- - if @build.erasable?
- = link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
- class: 'btn btn-sm btn-warning', method: :post,
- 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
- %p
- %span.attr-name Duration:
- #{duration_in_words(@build.finished_at, @build.started_at)}
- %p
- %span.attr-name Created:
- #{time_ago_with_tooltip(@build.created_at)}
- - if @build.finished_at
- %p
- %span.attr-name Finished:
- #{time_ago_with_tooltip(@build.finished_at)}
- - if @build.erased_at
- %p
- %span.attr-name Erased:
- #{time_ago_with_tooltip(@build.erased_at)}
- %p
- %span.attr-name Runner:
- - if @build.runner && current_user && current_user.admin
- = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- - elsif @build.runner
- \##{@build.runner.id}
-
- - if @build.trigger_request
- .build-widget
- %h4.title
- Trigger
-
- %p
- %span.attr-name Token:
- #{@build.trigger_request.trigger.short_token}
-
- - if @build.trigger_request.variables
- %p
- %span.attr-name Variables:
-
- %code
- - @build.trigger_request.variables.each do |key, value|
- #{key}=#{value}
-
- .build-widget
- %h4.title
- Commit
- .pull-right
- %small
- = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
- %p
- %span.attr-name Branch:
- = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
- %p
- %span.attr-name Author:
- #{@build.commit.git_author_name}
- %p
- %span.attr-name Message:
- #{@build.commit.git_commit_message}
-
- - if @build.tags.any?
- .build-widget
- %h4.title
- Tags
- - @build.tag_list.each do |tag|
- %span.label.label-primary
- = tag
-
- - if @builds.present?
- .build-widget
- %h4.title #{pluralize(@builds.count(:id), "other build")} for
- = succeed ":" do
- = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
- %table.table.builds
- - @builds.each_with_index do |build, i|
- %tr.build
- %td
- = ci_icon_for_status(build.status)
- %td
- = link_to namespace_project_build_path(@project.namespace, @project, build) do
- - if build.name
- = build.name
- - else
- %span ##{build.id}
-
- %td.status= build.status
+ #down-build-trace
+= render "sidebar"
- :javascript
- new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
+:javascript
+ new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 1d05da50581..3b97dc9328f 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -2,10 +2,10 @@
= form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f|
= f.hidden_field :level
.dropdown
- %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"}
+ %button.btn.btn-default.notifications-btn#notifications-button{ data: { toggle: "dropdown" }, aria: { haspopup: "true", expanded: "false" } }
= icon('bell')
= notification_title(@notification_setting.level)
= icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
+ %ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-align-right.dropdown-menu-selectable.dropdown-menu-large{ role: "menu" }
- NotificationSetting.levels.each do |level|
= notification_list_item(level.first, @notification_setting)
diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 5e3a4123a8e..a0ffa065067 100644
--- a/app/views/projects/ci/commits/_commit.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,55 +1,55 @@
-- status = commit.status
+- status = pipeline.status
%tr.commit
%td.commit-link
- = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{status}" do
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: "ci-status ci-#{status}" do
= ci_icon_for_status(status)
- %strong ##{commit.id}
+ %strong ##{pipeline.id}
%td
%div.branch-commit
- - if commit.ref
- = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace"
+ - if pipeline.ref
+ = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace"
&middot;
- = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace"
+ = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
&nbsp;
- - if commit.tag?
+ - if pipeline.tag?
%span.label.label-primary tag
- - elsif commit.latest?
+ - elsif pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- - if commit.triggered?
+ - if pipeline.triggered?
%span.label.label-primary triggered
- - if commit.yaml_errors.present?
- %span.label.label-danger.has-tooltip{ title: "#{commit.yaml_errors}" } yaml invalid
- - if commit.builds.any?(&:stuck?)
+ - if pipeline.yaml_errors.present?
+ %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid
+ - if pipeline.builds.any?(&:stuck?)
%span.label.label-warning stuck
%p.commit-title
- - if commit_data = commit.commit_data
+ - if commit_data = pipeline.commit_data
= link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
- - stages_status = commit.statuses.stages_status
+ - stages_status = pipeline.statuses.stages_status
- stages.each do |stage|
%td
- status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status
- = link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
= ci_icon_for_status(status)
- else
.light.has-tooltip{ title: tooltip }
\-
%td
- - if commit.started_at && commit.finished_at
+ - if pipeline.started_at && pipeline.finished_at
%p.duration
- #{duration_in_words(commit.finished_at, commit.started_at)}
+ #{duration_in_words(pipeline.finished_at, pipeline.started_at)}
%td
.controls.hidden-xs.pull-right
- - artifacts = commit.builds.latest.select { |b| b.artifacts? }
+ - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present?
.dropdown.inline.build-artifacts
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -63,9 +63,9 @@
%span #{build.name}
- if can?(current_user, :update_pipeline, @project)
- - if commit.retryable?
- = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+ - if pipeline.retryable?
+ = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat")
- - if commit.cancelable?
- = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ - if pipeline.cancelable?
+ = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
= icon("remove")
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 7f7a15aa214..a508382578a 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,2 +1,2 @@
-- @ci_commits.each do |ci_commit|
- = render "ci_commit", ci_commit: ci_commit, pipeline_details: true
+- @pipelines.each do |pipeline|
+ = render "pipeline", pipeline: pipeline, pipeline_details: true
diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml
deleted file mode 100644
index 32ff4d30977..00000000000
--- a/app/views/projects/commit/_ci_commit.html.haml
+++ /dev/null
@@ -1,52 +0,0 @@
-.row-content-block.build-content.middle-block
- .pull-right
- - if can?(current_user, :update_pipeline, ci_commit.project)
- - if ci_commit.builds.latest.failed.any?(&:retryable?)
- = link_to "Retry failed", retry_namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), class: 'btn btn-grouped btn-primary', method: :post
-
- - if ci_commit.builds.running_or_pending.any?
- = link_to "Cancel running", cancel_namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
-
- .oneline.clearfix
- - if defined?(pipeline_details) && pipeline_details
- Pipeline
- = link_to "##{ci_commit.id}", namespace_project_pipeline_path(ci_commit.project.namespace, ci_commit.project, ci_commit.id), class: "monospace"
- with
- = pluralize ci_commit.statuses.count(:id), "build"
- - if ci_commit.ref
- for
- = link_to ci_commit.ref, namespace_project_commits_path(ci_commit.project.namespace, ci_commit.project, ci_commit.ref), class: "monospace"
- - 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
- 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 Name
- %th Tags
- %th Duration
- %th Finished at
- - if ci_commit.project.build_coverage_enabled?
- %th Coverage
- %th
- - ci_commit.statuses.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: ci_commit.statuses.where(stage: stage)
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 6674d58417b..b117517c0dd 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -53,13 +53,13 @@
- if @commit.status
.commit-info-row
Builds for
- = pluralize(@commit.ci_commits.count, 'pipeline')
+ = pluralize(@commit.pipelines.count, 'pipeline')
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status-link ci-status-icon-#{@commit.status}" do
= ci_icon_for_status(@commit.status)
= ci_label_for_status(@commit.status)
- - if @commit.ci_commits.duration
+ - if @commit.pipelines.duration
in
- = time_interval_in_words @commit.ci_commits.duration
+ = time_interval_in_words @commit.pipelines.duration
.commit-box.content-block
%h3.commit-title
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
new file mode 100644
index 00000000000..0411137b7c6
--- /dev/null
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -0,0 +1,52 @@
+.row-content-block.build-content.middle-block
+ .pull-right
+ - if can?(current_user, :update_pipeline, pipeline.project)
+ - if pipeline.builds.latest.failed.any?(&:retryable?)
+ = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post
+
+ - if pipeline.builds.running_or_pending.any?
+ = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
+
+ .oneline.clearfix
+ - if defined?(pipeline_details) && pipeline_details
+ Pipeline
+ = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
+ with
+ = pluralize pipeline.statuses.count(:id), "build"
+ - if pipeline.ref
+ for
+ = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
+ - if defined?(link_to_commit) && link_to_commit
+ for commit
+ = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace"
+ - if pipeline.duration
+ in
+ = time_interval_in_words pipeline.duration
+
+- if pipeline.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - pipeline.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 pipeline.project.builds_enabled? && !pipeline.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 Name
+ %th Tags
+ %th Duration
+ %th Finished at
+ - if pipeline.project.build_coverage_enabled?
+ %th Coverage
+ %th
+ - pipeline.statuses.stages.each do |stage|
+ = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index d1bd76ab529..a72e8ba73ad 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,24 +1,28 @@
-%ul.nav-links
- = 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)
+.scrolling-tabs-container
+ %ul.nav-links.sub-nav.scrolling-tabs
+ %div{ class: (container_class) }
+ .fade-left
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_files_path(@project) do
+ Files
- = nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
- Network
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ Commits
- = nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
- Compare
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ Network
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- Branches
- %span.badge.js-totalbranch-count= @repository.branch_count
+ = nav_link(controller: :compare) do
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ Compare
- = nav_link(controller: [:tags, :releases]) do
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- Tags
- %span.badge.js-totaltags-count= @repository.tag_count
+ = nav_link(html_options: {class: branches_tab_class}) do
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ Branches
+
+ = nav_link(controller: [:tags, :releases]) do
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ Tags
+ .fade-right
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 2c21923ed4f..76ba0bea36d 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,3 +1,5 @@
+- @no_container = true
+
- page_title "Commits", @ref
= content_for :meta_tags do
- if current_user
@@ -5,37 +7,38 @@
= render "head"
-.row-content-block.second-block
- .tree-ref-holder
- = render 'shared/ref_switcher', destination: 'commits'
+%div{ class: (container_class) }
+ .row-content-block.second-block.content-component-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'commits'
+
+ .block-controls.hidden-xs.hidden-sm
+ - if @merge_request.present?
+ .control
+ = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
+ - elsif create_mr_button?(@repository.root_ref, @ref)
+ .control
+ = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
+ = icon('plus')
+ Create Merge Request
- .block-controls.hidden-xs.hidden-sm
- - if @merge_request.present?
- .control
- = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- - elsif create_mr_button?(@repository.root_ref, @ref)
.control
- = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
- = icon('plus')
- Create Merge Request
+ = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'pull-left commits-search-form') do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input', spellcheck: false }
- .control
- = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'pull-left commits-search-form') do
- = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input', spellcheck: false }
-
- - if current_user && current_user.private_token
- .control
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do
- = icon("rss")
+ - if current_user && current_user.private_token
+ .control
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do
+ = icon("rss")
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
-%div{id: dom_id(@project)}
- #commits-list.content_list= render "commits", project: @project
-.clear
-= spinner
+ %div{id: dom_id(@project)}
+ #commits-list.content_list= render "commits", project: @project
+ .clear
+ = spinner
:javascript
CommitsList.init(#{@limit});
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 0b8ed23b305..c322942aeba 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,16 +1,18 @@
+- @no_container = true
- page_title "Compare"
= render "projects/commits/head"
-.row-content-block
- Compare branches, tags or commit ranges.
- %br
- Fill input field with commit id like
- %code.label-branch 4eedf23
- or branch/tag name like
- %code.label-branch master
- and press compare button for the commits list and a code diff.
- %br
- Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
+%div{ class: (container_class) }
+ .row-content-block.second-block.content-component-block
+ Compare branches, tags or commit ranges.
+ %br
+ Fill input field with commit id like
+ %code.label-branch 4eedf23
+ or branch/tag name like
+ %code.label-branch master
+ and press compare button for the commits list and a code diff.
+ %br
+ Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
-.prepend-top-20
- = render "form"
+ .prepend-top-20
+ = render "form"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 18b125ff9d4..8449fe1e4e0 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -84,6 +84,8 @@
%br
%span.descr Enable Container Registry for this repository
%hr
+ = render 'merge_request_settings', f: f
+ %hr
= render 'builds_settings', f: f
%hr
%fieldset.features.append-bottom-default
diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml
index 4b12e5f2da1..edc4f7b079f 100644
--- a/app/views/projects/graphs/ci/_overall.haml
+++ b/app/views/projects/graphs/ci/_overall.haml
@@ -16,4 +16,4 @@
%li
Commits covered:
%strong
- = @project.ci_commits.count(:all)
+ = @project.pipelines.count(:all)
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 917a0b805b1..8faad351463 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -1,91 +1 @@
-- 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)
-
- .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
- %div
- = f.check_box :wiki_page_events, class: 'pull-left'
- .prepend-left-20
- = f.label :wiki_page_events, class: 'label-light append-bottom-0' do
- Wiki Page events
- %p.light
- This url will be triggered when a wiki page is created/updated
- .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})
- - if @hooks.any?
- %ul.well-list
- - @hooks.each do |hook|
- = render "project_hook", hook: hook
- - else
- %p.settings-message.text-center.append-bottom-0
- No webhooks found, add one in the form above.
+= render 'shared/web_hooks/form', hook: @hook, hooks: @hooks, url_components: [@project.namespace.becomes(Namespace), @project]
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
new file mode 100644
index 00000000000..166dae248b6
--- /dev/null
+++ b/app/views/projects/issues/_head.html.haml
@@ -0,0 +1,25 @@
+%ul.nav-links.sub-nav
+ %div{ class: (container_class) }
+ - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+ = nav_link(controller: :issues) do
+ = link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do
+ %span
+ Issues
+
+ - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ %span
+ Merge Requests
+
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %span
+ Labels
+
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %span
+ Milestones
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 78f64150601..79b14819865 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,4 +1,4 @@
-%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) }
+%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } }
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
@@ -27,7 +27,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = issue.notes.user.nonawards.count
+ - note_count = issue.notes.user.count
%li
= link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do
= icon('comments')
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 2f9dc867d0d..75f36579b11 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -2,12 +2,12 @@
%h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request')
%ul.unstyled-list
- - has_any_ci = @merge_requests.any?(&:ci_commit)
+ - has_any_ci = @merge_requests.any?(&:pipeline)
- @merge_requests.each do |merge_request|
%li
%span.merge-request-ci-status
- - if merge_request.ci_commit
- = render_pipeline_status(merge_request.ci_commit)
+ - if merge_request.pipeline
+ = render_pipeline_status(merge_request.pipeline)
- elsif has_any_ci
= icon('blank fw')
%span.merge-request-id
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 469429ccf3c..e93b7e0d66d 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,13 +1,13 @@
- if can?(current_user, :push_code, @project)
.pull-right
#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
+ = 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
+ = icon('spinner spin')
Checking branches
- .available(style="display: none")
- %i.fa.fa-code-fork
+ .available.hide
New branch
- .unavailable(style="display: none")
- %i.fa.fa-exclamation-triangle
+ .unavailable.hide
+ = icon('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 5f9d2919982..b9bb6fe559d 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -5,10 +5,10 @@
- @related_branches.each do |branch|
%li
- sha = @project.repository.find_branch(branch).target
- - ci_commit = @project.ci_commit(sha, branch) if sha
- - if ci_commit
+ - pipeline = @project.pipeline(sha, branch) if sha
+ - if ci_copipelinemmit
%span.related-branch-ci-status
- = render_pipeline_status(ci_commit)
+ = render_pipeline_status(pipeline)
%span.related-branch-info
%strong
= link_to namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "label-branch" do
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 19a6f4a91f6..cd876b5ea62 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,24 +1,26 @@
+- @no_container = true
- page_title "Issues"
+= render "projects/issues/head"
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
-.top-area
- = render 'shared/issuable/nav', type: :issues
- .nav-controls
- - if current_user
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
- = icon('rss')
- %span.icon-label
- Subscribe
+%div{ class: (container_class) }
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = icon('rss')
+ %span.icon-label
+ Subscribe
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- - if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
- = icon('plus')
- New Issue
+ - if can? current_user, :create_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ New Issue
-= render 'shared/issuable/filter', type: :issues
+ = render 'shared/issuable/filter', type: :issues
-.issues-holder
- = render "issues"
+ .issues-holder
+ = render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index f3b0469b7d4..9b6a97c0959 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -38,14 +38,12 @@
%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')
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
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')
+ = 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-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-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-grouped issuable-edit' do
Edit
@@ -70,7 +68,7 @@
.content-block.content-block-small
= render 'new_branch'
- = render 'votes/votes_block', votable: @issue
+ = render 'award_emoji/awards_block', awardable: @issue, inline: true
%section.issuable-discussion
= render 'projects/issues/discussion'
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 8bf544b8371..73c6f2a046c 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,28 +1,50 @@
-%li{id: dom_id(label)}
+- label_css_id = dom_id(label)
+%li{id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
- .pull-info-right
- %span.append-right-20
- = link_to_label(label, type: :merge_request) do
- = pluralize label.open_merge_requests_count, 'merge request'
+ .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
+ %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-align-right
+ %ul
+ %li
+ = link_to_label(label, type: :merge_request) do
+ = pluralize label.open_merge_requests_count, 'merge request'
+ %li
+ = link_to_label(label) do
+ = pluralize label.open_issues_count(current_user), 'open issue'
+ - if current_user
+ %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } }
+ %span= label_subscription_toggle_button_text(label)
+ - if can? current_user, :admin_label, @project
+ %li
+ = link_to "Edit", edit_namespace_project_label_path(@project.namespace, @project, label)
+ %li
+ = link_to "Delete", namespace_project_label_path(@project.namespace, @project, label), title: "Delete", method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
- %span.append-right-20
- = link_to_label(label) do
- = pluralize label.open_issues_count(current_user), 'open issue'
+ .pull-right.hidden-xs.hidden-sm.hidden-md
+ = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
+ = pluralize label.open_merge_requests_count, 'merge request'
+ = link_to_label(label, css_class: 'btn btn-transparent btn-action') do
+ = pluralize label.open_issues_count(current_user), 'open issue'
- if current_user
- .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
- .subscription-status{data: {status: label_subscription_status(label)}}
-
- %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
- %span= label_subscription_toggle_button_text(label)
+ .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } }
+ %span.sr-only= label_subscription_toggle_button_text(label)
+ = icon('eye', class: 'label-subscribe-button-icon')
+ = icon('spinner spin', class: 'label-subscribe-button-loading')
- if can? current_user, :admin_label, @project
- = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: {toggle: "tooltip"} do
- %i.fa.fa-pencil-square-o
- = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
- %i.fa.fa-trash-o
+ = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
+ %span.sr-only Edit
+ = icon('pencil-square-o')
+ = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
+ %span.sr-only Delete
+ = icon('trash-o')
-- if current_user
- :javascript
- new Subscription('##{dom_id(label)} .label-subscription');
+ - if current_user
+ :javascript
+ new Subscription('##{dom_id(label)} .label-subscription');
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 2557d1a4d5b..6e1baa46b05 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,22 +1,38 @@
+- @no_container = true
- page_title "Labels"
+- hide_class = ''
+= render "projects/issues/head"
-.top-area
- .nav-text
- Labels can be applied to issues and merge requests.
- .nav-controls
- - if can? current_user, :admin_label, @project
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
- = icon('plus')
- New label
+%div{ class: (container_class) }
+ .top-area
+ .nav-text
+ Labels can be applied to issues and merge requests.
+ .nav-controls
+ - if can?(current_user, :admin_label, @project)
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+ New label
-.labels
- - if @labels.present?
- %ul.content-list.manage-labels-list
- = render @labels
- = paginate @labels, theme: 'gitlab'
- - else
- .nothing-here-block
- - if can? current_user, :admin_label, @project
- Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
+ .labels
+ - if can?(current_user, :admin_label, @project)
+ -# Only show it in the first page
+ - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1')
+ .prioritized-labels{ class: ('hide' if hide) }
+ %h5 Prioritized Labels
+ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
+ - if @prioritized_labels.present?
+ = render @prioritized_labels
+ - else
+ %p.empty-message No prioritized labels yet
+ .other-labels
+ - if can?(current_user, :admin_label, @project)
+ %h5{ class: ('hide' if hide) } Other Labels
+ - if @labels.present?
+ %ul.content-list.manage-labels-list.js-other-labels
+ = render @labels
+ = paginate @labels, theme: 'gitlab'
- else
- No labels created
+ .nothing-here-block
+ - if can?(current_user, :admin_label, @project)
+ Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
+ - else
+ No labels created
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
deleted file mode 100644
index 19e4dab874b..00000000000
--- a/app/views/projects/merge_requests/_head.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-.top-tabs
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), class: "tab #{'active' if current_page?(namespace_project_merge_requests_path(@project.namespace, @project)) }" do
- %span
- Merge Requests
-
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c02f94490a0..5029b365f93 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -11,9 +11,9 @@
= icon('ban')
CLOSED
- - if merge_request.ci_commit
+ - if merge_request.pipeline
%li
- = render_pipeline_status(merge_request.ci_commit)
+ = render_pipeline_status(merge_request.pipeline)
- if merge_request.open? && merge_request.broken?
%li
@@ -35,7 +35,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = merge_request.mr_and_commit_notes.user.nonawards.count
+ - note_count = merge_request.mr_and_commit_notes.user.count
%li
= link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do
= icon('comments')
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 5473fa19166..446887774a4 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -6,4 +6,3 @@
- if @merge_requests.present?
= paginate @merge_requests, theme: "gitlab"
-
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 18b3f9e1549..a5e67b95727 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -23,7 +23,7 @@
= link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
- - if @ci_commit
+ - if @pipeline
%li.builds-tab.active
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
Builds
@@ -43,7 +43,7 @@
%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, show_whitespace_toggle: false
- - if @ci_commit
+ - if @pipeline
#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 7af227129ec..c4df8bd504f 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -14,13 +14,11 @@
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
- = link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
- = icon('cloud-download fw')
+ = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do
Check out branch
%span.dropdown
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
- = icon('download')
Download as
%span.caret
%ul.dropdown-menu
@@ -49,12 +47,12 @@
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
- %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count
+ %span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
- - if @ci_commit
+ - if @pipeline
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
Builds
@@ -67,7 +65,7 @@
.tab-content
#notes.notes.tab-pane.voting_notes
.content-block.content-block-small.oneline-block
- = render 'votes/votes_block', votable: @merge_request
+ = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
.row
%section.col-md-12
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index b517e874b0f..9f948d41dda 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,19 +1,20 @@
+- @no_container = true
- page_title "Merge Requests"
-
+= render "projects/issues/head"
= render 'projects/last_push'
-.top-area
- = render 'shared/issuable/nav', type: :merge_requests
- .nav-controls
- = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
+%div{ class: (container_class) }
+ .top-area
+ = render 'shared/issuable/nav', type: :merge_requests
+ .nav-controls
+ = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- - if merge_project
- = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
- = icon('plus')
- New Merge Request
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - if merge_project
+ = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
+ New Merge Request
-= render 'shared/issuable/filter', type: :merge_requests
+ = render 'shared/issuable/filter', type: :merge_requests
-.merge-requests-holder
- = render 'merge_requests'
+ .merge-requests-holder
+ = render 'merge_requests'
diff --git a/app/views/projects/merge_requests/merge.js.haml b/app/views/projects/merge_requests/merge.js.haml
index 92ce479d463..84b6c9ebc5c 100644
--- a/app/views/projects/merge_requests/merge.js.haml
+++ b/app/views/projects/merge_requests/merge.js.haml
@@ -5,6 +5,9 @@
- when :merge_when_build_succeeds
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
+- when :sha_mismatch
+ :plain
+ $('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/sha_mismatch'))}");
- else
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml
index a116ffe2e15..81de60f116c 100644
--- a/app/views/projects/merge_requests/show/_builds.html.haml
+++ b/app/views/projects/merge_requests/show/_builds.html.haml
@@ -1,2 +1,2 @@
-= render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
+= render "projects/commit/pipeline", pipeline: @pipeline, 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 36c275e8be1..5bf5210aeab 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -25,8 +25,7 @@
= 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')
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn 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-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-grouped issuable-edit" do
Edit
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 4d381754610..08a38d283d2 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,7 +1,7 @@
-- if @ci_commit
+- if @pipeline
.mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status|
- .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @ci_commit.status == status) }
+ .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
= ci_icon_for_status(status)
%span
CI build
@@ -9,7 +9,7 @@
for
- commit = @merge_request.last_commit
= succeed "." do
- = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
+ = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage
= link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index 13359abede7..0e0af57d76e 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -17,6 +17,8 @@
= render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed'
+ - elsif !@merge_request.mergeable_ci_state? && @pipeline && @pipeline.failed?
+ = render 'projects/merge_requests/widget/open/build_failed'
- elsif @merge_request.can_be_merged?
= render 'projects/merge_requests/widget/open/accept'
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index b79508bdc34..d9efe81701f 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -13,7 +13,7 @@
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
- ci_status: "#{@merge_request.ci_commit ? @merge_request.ci_commit.status : ''}",
+ ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}",
ci_message: {
normal: "Build {{status}} for \"{{title}}\"",
preparing: "{{status}} build for \"{{title}}\""
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index cfdf4edac37..941513febbd 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,27 +1,29 @@
-- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
+- status_class = @pipeline ? " ci-#{@pipeline.status}" : nil
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
+ = hidden_field_tag :sha, @merge_request.source_sha
.accept-merge-holder.clearfix.js-toggle-container
.clearfix
.accept-action
- - if @ci_commit && @ci_commit.active?
+ - if @pipeline && @pipeline.active?
%span.btn-group
= button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do
Merge When Build Succeeds
- = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
- %span.caret
- %span.sr-only
- Select Merge Moment
- %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
- %li
- = link_to "#", class: "merge_when_build_succeeds" do
- = icon('check fw')
- Merge When Build Succeeds
- %li
- = link_to "#", class: "accept_merge_request" do
- = icon('warning fw')
- Merge Immediately
+ - unless @project.only_allow_merge_if_build_succeeds?
+ = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
+ %span.caret
+ %span.sr-only
+ Select Merge Moment
+ %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
+ %li
+ = link_to "#", class: "merge_when_build_succeeds" do
+ = icon('check fw')
+ Merge When Build Succeeds
+ %li
+ = link_to "#", class: "accept_merge_request" do
+ = icon('warning fw')
+ Merge Immediately
- else
= f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do
Accept Merge Request
diff --git a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
new file mode 100644
index 00000000000..14f51af5360
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
@@ -0,0 +1,6 @@
+%h4
+ = icon('exclamation-triangle')
+ The build for this merge request failed
+
+%p
+ Please retry the build or push a new commit to fix the failure.
diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
index e6c089fefb2..06ab0a3fa00 100644
--- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -1,9 +1,9 @@
-%h4
+%h4.has-conflicts
= icon("exclamation-triangle")
This merge request contains merge conflicts
%p
- Please resolve these conflicts or
+ Please resolve these conflicts or
- if @merge_request.can_be_merged_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index b83ddcab3a4..ad898ff153b 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -16,7 +16,7 @@
- if remove_source_branch_button || user_can_cancel_automatic_merge
.clearfix.prepend-top-10
- if remove_source_branch_button
- = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+ = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.source_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times')
Remove Source Branch When Merged
diff --git a/app/views/projects/merge_requests/widget/open/_sha_mismatch.html.haml b/app/views/projects/merge_requests/widget/open/_sha_mismatch.html.haml
new file mode 100644
index 00000000000..499624f8dd8
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/open/_sha_mismatch.html.haml
@@ -0,0 +1,6 @@
+%h4
+ = icon("exclamation-triangle")
+ This merge request has received new commits since the page was loaded.
+
+%p
+ Please reload the page to review the new commits before merging.
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 687222fa92f..f5e2b927da8 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -17,9 +17,8 @@
.col-md-6
.form-group
= f.label :due_date, "Due Date", class: "control-label"
- .col-sm-10= f.hidden_field :due_date
.col-sm-10
- .datepicker
+ = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date"
.form-actions
- if @milestone.new_record?
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index e6133b22f96..b0e0bdfff5a 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,20 +1,22 @@
+- @no_container = true
- page_title "Milestones"
+= render "projects/issues/head"
-.top-area
- = render 'shared/milestones_filter'
+%div{ class: (container_class) }
+ .top-area
+ = render 'shared/milestones_filter'
- .nav-controls
- - if can?(current_user, :admin_milestone, @project)
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
- = icon('plus')
- New Milestone
+ .nav-controls
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
+ New Milestone
-.milestones
- %ul.content-list
- = render @milestones
+ .milestones
+ %ul.content-list
+ = render @milestones
- - if @milestones.blank?
- %li
- .nothing-here-block No milestones to show
+ - if @milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
- = paginate @milestones, theme: "gitlab"
+ = paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 19944e3e023..73772cc0e32 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -6,7 +6,7 @@
- if @milestone.closed?
Closed
- elsif @milestone.expired?
- Expired
+ Past due
- else
Open
%span.identifier
@@ -23,11 +23,9 @@
= 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 edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
- = icon('pencil-square-o')
Edit
= 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
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index c609c505def..86295a3d011 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,6 +1,9 @@
-.row-content-block.append-bottom-default
- .tree-ref-holder
- = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+- @no_container = true
- .oneline
- You can move around the graph by using the arrow keys.
+%div{ class: (container_class) }
+ .row-content-block.second-block.content-component-block
+ .tree-ref-holder
+ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
+
+ .oneline
+ You can move around the graph by using the arrow keys.
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 326180ebe4e..bf9baaea889 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,20 +1,21 @@
- page_title "Network", @ref
= render "projects/commits/head"
= render "head"
-.project-network
- .controls
- = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
- = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
- = button_tag class: 'btn btn-success' do
- = icon('search')
- .inline.prepend-left-20
- .checkbox.light
- = label_tag :filter_ref do
- = check_box_tag :filter_ref, 1, @options[:filter_ref]
- %span Begin with the selected commit
+%div{ class: (container_class) }
+ .project-network
+ .controls
+ = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
+ = button_tag class: 'btn btn-success' do
+ = icon('search')
+ .inline.prepend-left-20
+ .checkbox.light
+ = label_tag :filter_ref do
+ = check_box_tag :filter_ref, 1, @options[:filter_ref]
+ %span Begin with the selected commit
- .network-graph
- = spinner nil, true
+ .network-graph
+ = spinner nil, true
:javascript
network_graph = new Network({
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index f1045bbd8c3..bcdbff08011 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -19,20 +19,25 @@
.note-actions
- access = note.project.team.human_max_access(note.author.id)
- if access
- %span.note-role
- = access
+ %span.note-role.hidden-xs= access
+ - if current_user
+ = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
+ = icon('spinner spin')
+ = icon('smile-o')
- 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
+ = 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 hidden-xs js-note-delete danger' do
= icon('trash-o')
.note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
+ = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- 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)
+ .note-awards
+ = render 'award_emoji/awards_block', awardable: note, inline: false
- if note.attachment.url
.note-attachment
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index 2c8ae625e67..f278d4e0538 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -1,14 +1,15 @@
-%ul.nav-links
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipelines) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
- %span.badge.count.ci_counter= number_with_delimiter(@project.ci_commits.running_or_pending.count)
+%ul.nav-links.sub-nav
+ %div{ class: (container_class) }
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipelines) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
+ %span.badge.count.ci_counter= number_with_delimiter(@project.pipelines.running_or_pending.count)
- - if project_nav_tab? :builds
- = nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
- %span
- Builds
- %span.badge.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
+ - if project_nav_tab? :builds
+ = nav_link(controller: %w(builds)) do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+ %span
+ Builds
+ %span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 453767920b5..b70693eeb62 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,58 +1,58 @@
+- @no_container = true
- page_title "Pipelines"
= render "projects/pipelines/head"
-.top-area
- %ul.nav-links
- %li{class: ('active' if @scope.nil?)}
- = link_to project_pipelines_path(@project) do
- All
- %span.badge.js-totalbuilds-count
- = number_with_delimiter(@pipelines_count)
-
- %li{class: ('active' if @scope == 'running')}
- = link_to project_pipelines_path(@project, scope: :running) do
- Running
- %span.badge.js-running-count
- = number_with_delimiter(@running_or_pending_count)
-
- %li{class: ('active' if @scope == 'branches')}
- = link_to project_pipelines_path(@project, scope: :branches) do
- Branches
-
- %li{class: ('active' if @scope == 'tags')}
- = link_to project_pipelines_path(@project, scope: :tags) do
- Tags
-
- .nav-controls
- - if can? current_user, :create_pipeline, @project
- = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
- = icon('plus')
- New pipeline
-
- - unless @repository.gitlab_ci_yml
- = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
-
- = link_to ci_lint_path, class: 'btn btn-default' do
- = icon('wrench')
- %span CI Lint
-
-%ul.content-list.pipelines
- - stages = @pipelines.stages
- - if @pipelines.blank?
- %li
- .nothing-here-block No pipelines to show
- - else
- .table-holder
- %table.table.builds
- %tbody
- %th ID
- %th Commit
- - stages.each do |stage|
- %th.stage
- %span.has-tooltip{ title: "#{stage.titleize}" }
- = stage.titleize.pluralize
- %th Duration
- %th
- = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
-
- = paginate @pipelines, theme: 'gitlab'
+%div{ class: (container_class) }
+ .top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_pipelines_path(@project) do
+ All
+ %span.badge.js-totalbuilds-count
+ = number_with_delimiter(@pipelines_count)
+
+ %li{class: ('active' if @scope == 'running')}
+ = link_to project_pipelines_path(@project, scope: :running) do
+ Running
+ %span.badge.js-running-count
+ = number_with_delimiter(@running_or_pending_count)
+
+ %li{class: ('active' if @scope == 'branches')}
+ = link_to project_pipelines_path(@project, scope: :branches) do
+ Branches
+
+ %li{class: ('active' if @scope == 'tags')}
+ = link_to project_pipelines_path(@project, scope: :tags) do
+ Tags
+
+ .nav-controls
+ - if can? current_user, :create_pipeline, @project
+ = link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
+ New pipeline
+
+ - unless @repository.gitlab_ci_yml
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+
+ = link_to ci_lint_path, class: 'btn btn-default' do
+ %span CI Lint
+
+ %ul.content-list.pipelines
+ - stages = @pipelines.stages
+ - if @pipelines.blank?
+ %li
+ .nothing-here-block No pipelines to show
+ - else
+ .table-holder
+ %table.table.builds
+ %tbody
+ %th ID
+ %th Commit
+ - stages.each do |stage|
+ %th.stage
+ %span.has-tooltip{ title: "#{stage.titleize}" }
+ = stage.titleize.pluralize
+ %th Duration
+ %th
+ = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
+
+ = paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 2aad5602414..75943c64276 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -5,4 +5,4 @@
= render "projects/pipelines/info"
%div.block-connector
-= render "projects/commit/ci_commit", ci_commit: @pipeline
+= render "projects/commit/pipeline", pipeline: @pipeline
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index c53033e367c..6671ee2c6d6 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -7,7 +7,6 @@
- if can?(current_user, :admin_group_member, @group)
.controls
= link_to group_group_members_path(@group), class: 'btn' do
- = icon('pencil-square-o')
Manage group members
%ul.content-list
- members.limit(20).each do |member|
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 05bf3a7ef6a..268f140d7db 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -32,9 +32,9 @@
.pull-right
%strong= member.human_access
- if can?(current_user, :update_project_member, member)
- = button_tag class: "btn-xs btn js-toggle-button",
+ = button_tag class: "btn-xs btn-grouped inline btn js-toggle-button",
title: 'Edit access level', type: 'button' do
- %i.fa.fa-pencil-square-o
+ = icon('pencil')
- if can?(current_user, :destroy_project_member, member)
&nbsp;
@@ -44,7 +44,7 @@
Leave
- else
= link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
- %i.fa.fa-minus.fa-inverse
+ = icon('trash')
.edit-member.hide.js-toggle-content
%br
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index a19c7c406a0..4afa902b4eb 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -13,7 +13,7 @@
= render "home_panel"
.project-stats.row-content-block.second-block
- .container-fluid.container-limited
+ %div{ class: (container_class) }
%ul.nav
%li
= link_to project_files_path(@project) do
diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml
index 093d1d1bb0f..8a11dbfa9f4 100644
--- a/app/views/projects/tags/_download.html.haml
+++ b/app/views/projects/tags/_download.html.haml
@@ -1,7 +1,6 @@
-%span.btn-group.btn-grouped
+%span.btn-group
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
- %i.fa.fa-download
- %span source code
+ %span Source code
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
@@ -9,9 +8,7 @@
%ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
- %i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
- %i.fa.fa-download
%span Download tar.gz
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index dbc35c16feb..844e1055810 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -15,11 +15,11 @@
= render 'projects/tags/download', ref: tag.name, project: @project
- 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
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes" do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
index ffeacb5a004..e4a78fadbeb 100644
--- a/app/views/projects/tags/destroy.js.haml
+++ b/app/views/projects/tags/destroy.js.haml
@@ -1,3 +1,2 @@
-$('.js-totaltags-count').html("#{@repository.tags.size}");
- if @repository.tags.empty?
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 8f381663e6e..2779084fe38 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,28 +1,30 @@
+- @no_container = true
- page_title "Tags"
= render "projects/commits/head"
-.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
- = icon('plus')
- New tag
- .oneline
- Tags give the ability to mark specific points in history as being important
+%div{ class: (container_class) }
+ .top-area
+ .nav-text
+ Tags give the ability to mark specific points in history as being important
-.tags
- - unless @tags.empty?
- %ul.content-list
- - @tags.each do |tag|
- = render 'tag', tag: @repository.find_tag(tag)
+ - if can? current_user, :push_code, @project
+ .nav-controls
+ = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+ New tag
- = paginate @tags, theme: 'gitlab'
+ .tags
+ - unless @tags.empty?
+ %ul.content-list
+ - @tags.each do |tag|
+ = render 'tag', tag: @repository.find_tag(tag)
- - else
- .nothing-here-block
- Repository has no tags yet.
- %br
- %small
- Use git tag command to add a new one:
+ = paginate @tags, theme: 'gitlab'
+
+ - else
+ .nothing-here-block
+ Repository has no tags yet.
%br
- %span.monospace git tag -a v1.4 -m 'version 1.4'
+ %small
+ Use git tag command to add a new one:
+ %br
+ %span.monospace git tag -a v1.4 -m 'version 1.4'
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 7e9ba09c720..2abcfcdd7b2 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,16 +1,20 @@
+- @no_container = true
+
- page_title @path.presence || "Files", @ref
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render 'projects/last_push'
+= render "projects/commits/head"
-.tree-controls
- = render 'projects/find_file_link'
- - if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
+%div{ class: (container_class) }
+ .tree-controls
+ = render 'projects/find_file_link'
+ - if can? current_user, :download_code, @project
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
-#tree-holder.tree-holder.clearfix
- .nav-block
- = render 'projects/tree/tree_header', tree: @tree
+ #tree-holder.tree-holder.clearfix
+ .nav-block
+ = render 'projects/tree/tree_header', tree: @tree
- = render 'projects/tree/tree_content', tree: @tree
+ = render 'projects/tree/tree_content', tree: @tree
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 2b91b7e8f65..4faa547769b 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,11 +1,9 @@
- if (@page && @page.persisted?)
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History
- if can?(current_user, :create_wiki, @project)
- = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
+ = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn" do
Edit
- if can?(current_user, :admin_wiki, @project)
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
- = icon('trash')
Delete
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index a722fbc5352..988fe024e28 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -13,7 +13,6 @@
.nav-controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- = icon('plus')
New Page
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index aaa15dd3bbe..cbd69ee1a73 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -2,7 +2,7 @@
= render 'nav'
.top-area
- .nav-text
+ .nav-text.wiki-page
%strong
- if @page.persisted?
= link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 1cb48a1e85d..9166c0edb3b 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -18,7 +18,7 @@
You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
-.wiki-holder.prepend-top-default
+.wiki-holder.prepend-top-default.append-bottom-default
.wiki
= preserve do
= render_wiki_content(@page)
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 8ff9d4c1c7f..a5df502d7b5 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,4 +1,4 @@
-- if @issues.any?
+- if @issues.reorder(nil).any?
- @issues.group_by(&:project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 9ce5562e667..478c04318c6 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,5 +1,13 @@
%span.label-row
+ - if can?(current_user, :admin_label, @project)
+ .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
+ dom_id: dom_id(label) } }
+ %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
+ = icon('star-o')
+ %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
+ = icon('star')
%span.label-name
= link_to_label(label, tooltip: false)
- %span.prepend-left-10
- = markdown(label.description, pipeline: :single_line) \ No newline at end of file
+ - if label.description
+ %span.label-description
+ = markdown(label.description, pipeline: :single_line)
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
index dc89e36419c..87028ececd4 100644
--- a/app/views/shared/_labels_row.html.haml
+++ b/app/views/shared/_labels_row.html.haml
@@ -1,3 +1,10 @@
- labels.each do |label|
- %span.label-row
- = link_to_label(label, tooltip: false)
+ %span.label-row.btn-group{ role: "group", aria: { label: escape_once(label.name) }, style: "color: #{text_color_for_bg(label.color)}" }
+ = link_to namespace_project_label_path(@project.namespace, @project, label),
+ class: "btn btn-transparent has-tooltip",
+ style: "background-color: #{label.color};",
+ title: escape_once(label.description),
+ data: { container: "body" } do
+ = escape_once label.name
+ %button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
+ = icon("times")
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index 1c58345278a..51622931e24 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,8 +1,7 @@
- if @projects.any?
- .prepend-left-10.project-item-select-holder
+ .project-item-select-holder
= project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }
%a.btn.btn-new.new-project-item-select-button
- = icon('plus')
= local_assigns[:label]
%b.caret
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 1e0f075b303..249bce926ce 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -8,6 +8,8 @@
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
+ = link_to page_filter_path(sort: sort_value_priority) do
+ = sort_title_priority
= link_to page_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to page_filter_path(sort: sort_value_oldest_created) do
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 40c6eb9be45..a25365a94f2 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -6,10 +6,10 @@
- if group_member
.controls.hidden-xs
- if can?(current_user, :admin_group, group)
- = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
- %i.fa.fa-cogs
+ = link_to edit_group_path(group), class: "btn" do
+ = icon('cogs')
- = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
+ = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn", title: 'Leave this group' do
= icon('sign-out')
.stats
diff --git a/app/views/shared/icons/_activity.svg b/app/views/shared/icons/_activity.svg
new file mode 100644
index 00000000000..d465504b154
--- /dev/null
+++ b/app/views/shared/icons/_activity.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>path-1</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="_activity" fill="#7E7D7D">
+ <g id="Page-1">
+ <g id="path-1">
+ <path d="M5,0 C4.448,0 4,0.448 4,1 L4,3 L1,3 C0.448,3 0,3.448 0,4 L0,9 C0,9.552 0.448,10 1,10 L5,10 L5,8 L11,8 L11,10 L15,10 C15.552,10 16,9.552 16,9 L16,4 C16,3.448 15.552,3 15,3 L12,3 L12,1 C12,0.448 11.552,0 11,0 L5,0 L5,0 L5,0 L5,0 Z M6,2.5 C6,2.224 6.224,2 6.5,2 L9.5,2 C9.776,2 10,2.224 10,2.5 C10,2.776 9.776,3 9.5,3 L6.5,3 C6.224,3 6,2.776 6,2.5 L6,2.5 L6,2.5 L6,2.5 Z M6,11 L10.001,11 L10.001,9 L6,9 L6,11 L6,11 L6,11 L6,11 Z M11,11 L11,12 L5,12 L5,11 L1,11 C0.448,11 0,11.448 0,12 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,12 C16,11.448 15.552,11 15,11 L11,11 L11,11 L11,11 L11,11 Z"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_commits.svg b/app/views/shared/icons/_commits.svg
new file mode 100644
index 00000000000..ba9bb89935e
--- /dev/null
+++ b/app/views/shared/icons/_commits.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>Pasted Image 240</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <path d="M3,8 C3,5.951 4.236,4.194 6,3.422 L6,0 L1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L6,16 L6,12.578 C4.236,11.806 3,10.049 3,8 M7,12.899 L7,16 L9,16 L9,12.899 C8.677,12.965 8.343,13 8,13 C7.657,13 7.323,12.965 7,12.899 M15,0 L10,0 L10,3.422 C11.764,4.194 13,5.951 13,8 C13,10.049 11.764,11.806 10,12.578 L10,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 M10,8 C10,9.105 9.105,10 8,10 C6.895,10 6,9.105 6,8 C6,6.895 6.895,6 8,6 C9.105,6 10,6.895 10,8 M4,8 C4,10.209 5.791,12 8,12 C10.209,12 12,10.209 12,8 C12,5.791 10.209,4 8,4 C5.791,4 4,5.791 4,8 M9,3.101 L9,0 L7,0 L7,3.101 C7.323,3.035 7.657,3 8,3 C8.343,3 8.677,3.035 9,3.101" id="Pasted-Image-240" fill="#7E7D7D"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_contributionanalytics.svg b/app/views/shared/icons/_contributionanalytics.svg
new file mode 100644
index 00000000000..adf09a14964
--- /dev/null
+++ b/app/views/shared/icons/_contributionanalytics.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group">
+ <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1" fill="#7E7C7C"></path>
+ <polygon id="Stroke-6" fill="#7E7C7C" points="2.0197351 9.86809696 6.4567351 6.52409696 5.79233671 6.46815759 9.53233671 10.4271576 9.87070552 10.78534 10.2338016 10.4522494 15.0258016 6.05624938 14.3497984 5.31935062 9.55779844 9.71535062 10.2592633 9.74044241 6.51926329 5.78144241 6.21208651 5.45627854 5.8548649 5.72550304 1.4178649 9.06950304"></polygon>
+ <path d="M7.0313,6.3928 C7.0313,6.9448 6.5833,7.3928 6.0313,7.3928 C5.4793,7.3928 5.0313,6.9448 5.0313,6.3928 C5.0313,5.8408 5.4793,5.3928 6.0313,5.3928 C6.5833,5.3928 7.0313,5.8408 7.0313,6.3928" id="Fill-8" fill="#FEFEFE"></path>
+ <path d="M6.5313,6.3928 C6.5313,6.66865763 6.30715763,6.8928 6.0313,6.8928 C5.75544237,6.8928 5.5313,6.66865763 5.5313,6.3928 C5.5313,6.11694237 5.75544237,5.8928 6.0313,5.8928 C6.30715763,5.8928 6.5313,6.11694237 6.5313,6.3928 L6.5313,6.3928 Z M7.5313,6.3928 C7.5313,5.56465763 6.85944237,4.8928 6.0313,4.8928 C5.20315763,4.8928 4.5313,5.56465763 4.5313,6.3928 C4.5313,7.22094237 5.20315763,7.8928 6.0313,7.8928 C6.85944237,7.8928 7.5313,7.22094237 7.5313,6.3928 L7.5313,6.3928 Z" id="Stroke-10" fill="#7E7C7C"></path>
+ <path d="M10.8854,9.8715 C10.8854,10.4235 10.4374,10.8715 9.8854,10.8715 C9.3334,10.8715 8.8854,10.4235 8.8854,9.8715 C8.8854,9.3195 9.3334,8.8715 9.8854,8.8715 C10.4374,8.8715 10.8854,9.3195 10.8854,9.8715" id="Fill-12" fill="#FEFEFE"></path>
+ <path d="M10.3854,9.8715 C10.3854,10.1473576 10.1612576,10.3715 9.8854,10.3715 C9.60954237,10.3715 9.3854,10.1473576 9.3854,9.8715 C9.3854,9.59564237 9.60954237,9.3715 9.8854,9.3715 C10.1612576,9.3715 10.3854,9.59564237 10.3854,9.8715 L10.3854,9.8715 Z M11.3854,9.8715 C11.3854,9.04335763 10.7135424,8.3715 9.8854,8.3715 C9.05725763,8.3715 8.3854,9.04335763 8.3854,9.8715 C8.3854,10.6996424 9.05725763,11.3715 9.8854,11.3715 C10.7135424,11.3715 11.3854,10.6996424 11.3854,9.8715 L11.3854,9.8715 Z" id="Stroke-14" fill="#7E7C7C"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_files.svg b/app/views/shared/icons/_files.svg
new file mode 100644
index 00000000000..fc378d81e40
--- /dev/null
+++ b/app/views/shared/icons/_files.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>Pasted Image 237</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Pasted-Image-237">
+ <path d="M15.1111,16 C15.6021,16 16.0001,15.602 16.0001,15.111 L16.0001,4.444 C15.5341,3.983 12.0671,0.378 11.5551,0 L0.8891,0 C0.3981,0 0.0001,0.398 0.0001,0.889 L0.0001,15.111 C0.0001,15.602 0.3981,16 0.8891,16 L15.1111,16 M14.0001,14.111 L1.8891,14.111 L1.8891,2 L10.8131,2 C11.4451,2.42 13.5811,4.555 14.0001,5.187 L14.0001,14.111" id="Fill-1" fill="#7E7D7D"></path>
+ <path d="M0.889,0 C0.398,0 0,0.398 0,0.889 L0,15.111 C0,15.602 0.398,16 0.889,16 L15.111,16 C15.602,16 16,15.602 16,15.111 L16,4.445 C15.534,3.983 12.068,0.377 11.555,0 L0.889,0 L0.889,0 Z M1.889,2 L10.813,2 C11.446,2.42 13.581,4.554 14,5.187 L14,14.111 L1.889,14.111 L1.889,2 L1.889,2 Z" id="Clip-4"></path>
+ <polygon id="Fill-6" fill="#7E7D7D" points="9 7 11 7 11 2 9 2"></polygon>
+ <polygon id="Clip-9" points="9 7 11 7 11 2.001 9 2.001"></polygon>
+ <polygon id="Fill-11" fill="#7E7D7D" points="10 7 15.444 7 15.444 5 10 5"></polygon>
+ <polygon id="Clip-14" points="10 7 15.444 7 15.444 5 10 5"></polygon>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_group.svg b/app/views/shared/icons/_group.svg
new file mode 100644
index 00000000000..75cae0d16c8
--- /dev/null
+++ b/app/views/shared/icons/_group.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#303030">
+ <path d="M15.6667,10.0105 L10.3337,10.0105 C10.1497,10.0105 9.9997,10.1775 9.9997,10.3845 L9.9997,15.6145 C9.9997,15.8215 10.1497,15.9885 10.3337,15.9885 L15.6667,15.9885 C15.8507,15.9885 15.9997,15.8215 15.9997,15.6145 L15.9997,10.3845 C15.9997,10.1775 15.8507,10.0105 15.6667,10.0105 L15.6667,10.0105 L15.6667,10.0105 Z M11.9997,14.0105 L13.9997,14.0105 L13.9997,12.0105 L11.9997,12.0105 L11.9997,14.0105 L11.9997,14.0105 Z" id="Fill-11"></path>
+ <path d="M5.6667,10.0105 L0.3337,10.0105 C0.1497,10.0105 -0.0003,10.1775 -0.0003,10.3845 L-0.0003,15.6145 C-0.0003,15.8215 0.1497,15.9885 0.3337,15.9885 L5.6667,15.9885 C5.8507,15.9885 5.9997,15.8215 5.9997,15.6145 L5.9997,10.3845 C5.9997,10.1775 5.8507,10.0105 5.6667,10.0105 L5.6667,10.0105 L5.6667,10.0105 Z M1.9997,14.0105 L3.9997,14.0105 L3.9997,12.0105 L1.9997,12.0105 L1.9997,14.0105 L1.9997,14.0105 Z" id="Fill-8"></path>
+ <polygon id="Stroke-1" points="12.5 7.5834 3.5 7.5834 3.5 9.5834 12.5 9.5834"></polygon>
+ <polygon id="Stroke-3" points="9 9.0834 9 5.0834 7 5.0834 7 9.0834"></polygon>
+ <polygon id="Stroke-4" points="4 11.0834 4 7.5834 2 7.5834 2 11.0834"></polygon>
+ <polygon id="Stroke-6" points="14 11.0834 14 7.5834 12 7.5834 12 11.0834"></polygon>
+ <path d="M11.6667,6.21724894e-15 L4.3337,6.21724894e-15 C4.1497,6.21724894e-15 3.9997,0.167 3.9997,0.374 L3.9997,6.604 C3.9997,6.811 4.1497,6.978 4.3337,6.978 L11.6667,6.978 C11.8507,6.978 11.9997,6.811 11.9997,6.604 L11.9997,0.374 C11.9997,0.167 11.8507,6.21724894e-15 11.6667,6.21724894e-15 L11.6667,6.21724894e-15 L11.6667,6.21724894e-15 Z M5.9997,5 L9.9997,5 L9.9997,2 L5.9997,2 L5.9997,5 L5.9997,5 Z" id="Fill-14"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg
new file mode 100644
index 00000000000..2682c27ade9
--- /dev/null
+++ b/app/views/shared/icons/_issues.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#7E7C7C">
+ <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1"></path>
+ <path d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z" id="Combined-Shape"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_members.svg b/app/views/shared/icons/_members.svg
new file mode 100644
index 00000000000..f8043b31fe8
--- /dev/null
+++ b/app/views/shared/icons/_members.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="22px" height="16px" viewBox="0 0 22 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#7E7C7C">
+ <path d="M6.4357,11.8588 C7.1487,11.2798 7.8797,10.7808 8.5357,10.3708 C8.5837,10.3008 8.6187,10.2338 8.6187,10.1768 L8.6187,8.8088 C8.9197,8.5218 9.0927,8.1248 9.0927,7.7028 L9.0927,5.3748 C9.0927,3.9478 7.9187,2.7858 6.4757,2.7858 L5.9687,2.7858 C4.5247,2.7858 3.3507,3.9478 3.3507,5.3748 L3.3507,7.7028 C3.3507,8.1248 3.5247,8.5218 3.8247,8.8088 L3.8247,10.5838 C3.2537,10.8738 1.8797,11.6198 0.5967,12.6618 C0.2177,12.9698 -0.0003,13.4258 -0.0003,13.9138 L-0.0003,15.5088 C-0.0003,15.5438 0.0857,15.7668 0.3467,15.7778 C1.3257,15.8198 3.8417,15.8328 5.9617,15.9038 C5.8337,15.8148 5.7447,15.6748 5.7447,15.5088 L5.7447,13.5498 C5.7447,12.9848 5.9967,12.2158 6.4357,11.8588" id="Fill-1"></path>
+ <path d="M21.3092,12.1 C19.6932,10.787 17.9592,9.86 17.3042,9.53 L17.3042,7.235 C17.6722,6.9 17.8862,6.428 17.8862,5.925 L17.8862,3.066 C17.8862,1.376 16.4952,0 14.7852,0 L14.1632,0 C12.4532,0 11.0622,1.376 11.0622,3.066 L11.0622,5.925 C11.0622,6.428 11.2752,6.9 11.6442,7.235 L11.6442,9.53 C10.9892,9.86 9.2542,10.787 7.6392,12.1 C7.2002,12.457 6.9482,12.985 6.9482,13.55 L6.9482,15.509 C6.9482,15.78 7.1702,16 7.4442,16 L14.1172,16 L14.1172,11.704 C12.6812,11.595 11.5652,10.853 11.5652,9.945 C11.5652,9.804 11.5982,9.669 11.6482,9.538 C11.9502,10.326 13.0982,10.913 14.4762,10.913 C15.8532,10.913 17.0012,10.326 17.3032,9.538 C17.3532,9.669 17.3862,9.804 17.3862,9.945 C17.3862,10.793 16.4152,11.5 15.1172,11.679 L15.1172,16 L21.5032,16 C21.7772,16 22.0002,15.78 22.0002,15.509 L22.0002,13.55 C22.0002,12.985 21.7482,12.457 21.3092,12.1" id="Fill-4"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_milestones.svg b/app/views/shared/icons/_milestones.svg
new file mode 100644
index 00000000000..3d62ecc0631
--- /dev/null
+++ b/app/views/shared/icons/_milestones.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="17px" viewBox="0 0 16 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#7E7C7C">
+ <path d="M15.1111,1 L0.8891,1 C0.3981,1 0.0001,1.446 0.0001,1.996 L0.0001,15.945 C0.0001,16.495 0.3981,16.941 0.8891,16.941 L15.1111,16.941 C15.6021,16.941 16.0001,16.495 16.0001,15.945 L16.0001,1.996 C16.0001,1.446 15.6021,1 15.1111,1 L15.1111,1 L15.1111,1 Z M14.0001,6.0002 L14.0001,14.949 L2.0001,14.949 L2.0001,6.0002 L14.0001,6.0002 Z M14.0001,4.0002 L14.0001,2.993 L2.0001,2.993 L2.0001,4.0002 L14.0001,4.0002 Z" id="Combined-Shape"></path>
+ <polygon id="Fill-11" points="3 2.0002 5 2.0002 5 0.0002 3 0.0002"></polygon>
+ <polygon id="Fill-16" points="11 2.0002 13 2.0002 13 0.0002 11 0.0002"></polygon>
+ <path d="M5.37709616,11.5511984 L6.92309616,12.7821984 C7.35112915,13.123019 7.97359761,13.0565604 8.32002627,12.6330535 L10.7740263,9.63305349 C11.1237073,9.20557058 11.0606364,8.57555475 10.6331535,8.22587373 C10.2056706,7.87619272 9.57565475,7.93926361 9.22597373,8.36674651 L6.77197373,11.3667465 L8.16890384,11.2176016 L6.62290384,9.98660159 C6.19085236,9.6425813 5.56172188,9.71394467 5.21770159,10.1459962 C4.8736813,10.5780476 4.94504467,11.2071781 5.37709616,11.5511984 L5.37709616,11.5511984 Z" id="Stroke-21"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_mr.svg b/app/views/shared/icons/_mr.svg
new file mode 100644
index 00000000000..dd3dbcc4473
--- /dev/null
+++ b/app/views/shared/icons/_mr.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#7E7C7C">
+ <path d="M15.1111,0 L0.8891,0 C0.3981,0 0.0001,0.446 0.0001,0.996 L0.0001,14.945 C0.0001,15.495 0.3981,15.941 0.8891,15.941 L15.1111,15.941 C15.6021,15.941 16.0001,15.495 16.0001,14.945 L16.0001,0.996 C16.0001,0.446 15.6021,0 15.1111,0 L15.1111,0 L15.1111,0 Z M2.0001,13.949 L14.0001,13.949 L14.0001,1.993 L2.0001,1.993 L2.0001,13.949 Z M2,5.0002 L14,5.0002 L14,3.0002 L2,3.0002 L2,5.0002 Z" id="Combined-Shape"></path>
+ <path d="M8.547,12.0002 L12,12.0002 L12,10.0002 L8.547,10.0002 L8.547,12.0002 Z M5.2029,12 L3.9999,10.867 L5.2029,9.501 L3.9999,8.181 L5.2029,7 L7.4529,9.499 L5.2029,12 Z" id="Combined-Shape"></path>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_pipelines.svg b/app/views/shared/icons/_pipelines.svg
new file mode 100644
index 00000000000..794e8a27025
--- /dev/null
+++ b/app/views/shared/icons/_pipelines.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>Pasted Image 246</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <path d="M12.5,14 C11.672,14 11,13.328 11,12.5 C11,11.672 11.672,11 12.5,11 C13.328,11 14,11.672 14,12.5 C14,13.328 13.328,14 12.5,14 M12.5,9 L3.5,9 C1.567,9 0,10.567 0,12.5 C0,14.433 1.567,16 3.5,16 L12.5,16 C14.433,16 16,14.433 16,12.5 C16,10.567 14.433,9 12.5,9 M3.5,2 C4.328,2 5,2.672 5,3.5 C5,4.328 4.328,5 3.5,5 C2.672,5 2,4.328 2,3.5 C2,2.672 2.672,2 3.5,2 M3.5,7 L12.5,7 C14.433,7 16,5.433 16,3.5 C16,1.567 14.433,0 12.5,0 L3.5,0 C1.567,0 0,1.567 0,3.5 C0,5.433 1.567,7 3.5,7" id="Pasted-Image-246" fill="#303030"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_project.svg b/app/views/shared/icons/_project.svg
new file mode 100644
index 00000000000..1e8b43f8c6b
--- /dev/null
+++ b/app/views/shared/icons/_project.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>Page 1</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_wiki.svg b/app/views/shared/icons/_wiki.svg
new file mode 100644
index 00000000000..182d91e23aa
--- /dev/null
+++ b/app/views/shared/icons/_wiki.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
+ <title>Pasted Image 241</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <path d="M2.004,12.9999459 L3.939,12.9999459 L3.939,4.99994585 L2.004,4.99994585 L2.004,12.9999459 Z M7.017,9.99994585 L13.018,9.99994585 L13.018,8.99994585 L7.017,8.99994585 L7.017,9.99994585 Z M7.017,7.99994585 L13.018,7.99994585 L13.018,6.99994585 L7.017,6.99994585 L7.017,7.99994585 Z M7.017,5.99994585 L13.018,5.99994585 L13.018,4.99994585 L7.017,4.99994585 L7.017,5.99994585 Z M14.754,-5.41499267e-05 L4.938,-5.41499267e-05 C4.386,-5.41499267e-05 3.938,0.44794585 3.938,0.99994585 L3.938,2.99994585 L1,2.99994585 C0.448,2.99994585 0,3.44794585 0,3.99994585 L0,12.9999459 C0.037,13.4999459 -0.25,16.0509459 3.938,15.9999459 L12.408,15.9999459 C12.408,15.9999459 15.754,15.9169459 15.754,13.9999459 L15.754,0.99994585 C15.754,0.44794585 15.306,-5.41499267e-05 14.754,-5.41499267e-05 L14.754,-5.41499267e-05 Z" id="Pasted-Image-241" fill="#7E7D7D"></path>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index cedff4af2e0..380ab465bf4 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -31,7 +31,7 @@
- if controller.controller_name == 'issues'
.issues_bulk_update.hide
- = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
+ = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do
.filter-item.inline
= dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
%ul
@@ -44,6 +44,10 @@
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
+
+ .filter-item.inline.labels-filter
+ = render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, show_footer: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
+
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index b430251dbf6..17e2a7e9290 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -88,9 +88,9 @@
.col-lg-6
.form-group
= f.label :due_date, "Due date", class: "control-label"
- = f.hidden_field :due_date, id: "issuable-due-date"
.col-sm-10
- .datepicker
+ .issuable-form-select-holder
+ = f.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date"
- if issuable.can_move?(current_user)
%hr
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 61fd1e9c335..d34d28f6736 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,14 +1,25 @@
+- show_create = local_assigns.fetch(:show_create, true)
+- extra_options = local_assigns.fetch(:extra_options, true)
+- filter_submit = local_assigns.fetch(:filter_submit, true)
+- show_footer = local_assigns.fetch(:show_footer, true)
+- data_options = local_assigns.fetch(:data_options, {})
+- classes = local_assigns.fetch(:classes, [])
+- dropdown_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"}
+- dropdown_data.merge!(data_options)
+- classes << 'js-extra-options' if extra_options
+- classes << 'js-filter-submit' if filter_submit
+
- if params[:label_name].present?
- 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-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"}}
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text
= h(multi_label_name(params[:label_name], "Label"))
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- - if can? current_user, :admin_label, @project and @project
+ = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create }
+ - if show_create and @project and can?(current_user, :admin_label, @project)
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 7f4867417f7..0acb8253139 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -1,20 +1,22 @@
- title = local_assigns.fetch(:title, 'Assign labels')
+- show_create = local_assigns.fetch(:show_create, true)
+- show_footer = local_assigns.fetch(:show_footer, true)
- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
.dropdown-page-one
= dropdown_title(title)
- = dropdown_filter(filter_placeholder)
+ = dropdown_filter(filter_placeholder, search_id: "label-name")
= dropdown_content
- - if @project
+ - if @project && show_footer
= dropdown_footer do
%ul.dropdown-footer-list
- - if can? current_user, :admin_label, @project
+ - 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
+ - if show_create && @project && can?(current_user, :admin_label, @project)
Manage labels
- else
View labels
- = dropdown_loading \ No newline at end of file
+ = dropdown_loading
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c1eec450193..fb906de829a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -2,23 +2,8 @@
.issuable-sidebar
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
- %span.issuable-count.hide-collapsed.pull-left
- = issuable.iid
- of
- = issuables_count(issuable)
%a.gutter-toggle.pull-right.js-sidebar-toggle{href: '#'}
= 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 issuable-pager'
- - else
- %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 issuable-pager'
- - else
- %a.btn.btn-default.issuable-pager.disabled{href: '#'}
- Next
= 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
@@ -56,7 +41,8 @@
= icon('clock-o')
%span
- if issuable.milestone
- = issuable.milestone.title
+ %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1, placement: 'left'}}
+ = issuable.milestone.title
- else
None
.title.hide-collapsed
@@ -67,7 +53,8 @@
.value.bold.hide-collapsed
- if issuable.milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- = issuable.milestone.title
+ %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1}}
+ = issuable.milestone.title
- else
.light None
@@ -114,20 +101,20 @@
.sidebar-collapsed-icon
= icon('tags')
%span
- = issuable.labels.count
+ = issuable.labels_array.size
.title.hide-collapsed
Labels
= icon('spinner spin', class: 'block-loading')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
- .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels.any?) }
- - if issuable.labels.any?
- - issuable.labels.each do |label|
+ .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
+ - if issuable.labels_array.any?
+ - issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
.selectbox.hide-collapsed
- - issuable.labels.each do |label|
+ - issuable.labels_array.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
@@ -148,7 +135,7 @@
.title.hide-collapsed
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
+ %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 6b25745c554..acc3ccf4dcf 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -35,11 +35,9 @@
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
- = icon('pencil-square-o')
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit
\
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
- = icon('trash-o')
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
Delete
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
new file mode 100644
index 00000000000..d1e861ca80c
--- /dev/null
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -0,0 +1,91 @@
+- page_title "Webhooks"
+- context_title = @project ? 'project' : 'group'
+
+.row.prepend-top-default
+ .col-lg-3
+ %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
+ = form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f|
+ = form_errors(hook)
+
+ .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'
+ %ul.list-unstyled
+ %li
+ = 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
+ %li
+ = 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
+ %li
+ = 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
+ %li
+ = 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
+ %li
+ = 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
+ %li
+ = 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
+ %li
+ = f.check_box :wiki_page_events, class: 'pull-left'
+ .prepend-left-20
+ = f.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
+ .form-group
+ = f.label :enable_ssl_verification, "SSL verification", class: 'label-light checkbox'
+ .checkbox
+ = f.label :enable_ssl_verification do
+ = f.check_box :enable_ssl_verification
+ %strong Enable SSL verification
+ = f.submit "Add Webhook", class: "btn btn-create"
+ %hr
+ %h5.prepend-top-default
+ Webhooks (#{hooks.count})
+ - if hooks.any?
+ %ul.well-list
+ - hooks.each do |hook|
+ = render "project_hook", hook: hook
+ - else
+ %p.settings-message.text-center.append-bottom-0
+ No webhooks found, add one in the form above.
diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml
index 5c9294c0ab5..30e956e5f40 100644
--- a/app/views/sherlock/queries/_backtrace.html.haml
+++ b/app/views/sherlock/queries/_backtrace.html.haml
@@ -6,7 +6,11 @@
%ul.well-list
- @query.application_backtrace.each do |location|
%li
- = location.path
+ %strong
+ - if defined?(BetterErrors)
+ = link_to(location.path, BetterErrors.editor[location.path, location.line])
+ - else
+ = location.path
%small.light
= t('sherlock.line')
= location.line
diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml
index 549b47430e6..7073c0f4d90 100644
--- a/app/views/sherlock/queries/_general.html.haml
+++ b/app/views/sherlock/queries/_general.html.haml
@@ -11,13 +11,17 @@
= @query.duration.round(4)
= t('sherlock.milliseconds')
%li
+ - frame = @query.last_application_frame
%span.light
#{t('sherlock.origin')}:
%strong
- = @query.last_application_frame.path
+ - if defined?(BetterErrors)
+ = link_to(frame.path, BetterErrors.editor[frame.path, frame.line])
+ - else
+ = frame.path
%small.light
= t('sherlock.line')
- = @query.last_application_frame.line
+ = frame.line
.panel.panel-default
.panel-heading
diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml
new file mode 100644
index 00000000000..75fb0e303ad
--- /dev/null
+++ b/app/views/u2f/_authenticate.html.haml
@@ -0,0 +1,28 @@
+#js-authenticate-u2f
+
+%script#js-authenticate-u2f-not-supported{ type: "text/template" }
+ %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer).
+
+%script#js-authenticate-u2f-setup{ type: "text/template" }
+ %div
+ %p Insert your security key (if you haven't already), and press the button below.
+ %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Login Via U2F Device
+
+%script#js-authenticate-u2f-in-progress{ type: "text/template" }
+ %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.
+
+%script#js-authenticate-u2f-error{ type: "text/template" }
+ %div
+ %p <%= error_message %>
+ %a.btn.btn-warning#js-u2f-try-again Try again?
+
+%script#js-authenticate-u2f-authenticated{ type: "text/template" }
+ %div
+ %p We heard back from your U2F device. Click this button to authenticate with the GitLab server.
+ = form_tag(new_user_session_path, method: :post) do |f|
+ = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response"
+ = submit_tag "Authenticate via U2F Device", class: "btn btn-success"
+
+:javascript
+ var u2fAuthenticate = new U2FAuthenticate($("#js-authenticate-u2f"), gon.u2f);
+ u2fAuthenticate.start();
diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml
new file mode 100644
index 00000000000..46af591fc43
--- /dev/null
+++ b/app/views/u2f/_register.html.haml
@@ -0,0 +1,31 @@
+#js-register-u2f
+
+%script#js-register-u2f-not-supported{ type: "text/template" }
+ %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer).
+
+%script#js-register-u2f-setup{ type: "text/template" }
+ .row.append-bottom-10
+ .col-md-3
+ %a#js-setup-u2f-device.btn.btn-info{ href: 'javascript:void(0)' } Setup New U2F Device
+ .col-md-9
+ %p Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left.
+
+%script#js-register-u2f-in-progress{ type: "text/template" }
+ %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.
+
+%script#js-register-u2f-error{ type: "text/template" }
+ %div
+ %p
+ %span <%= error_message %>
+ %a.btn.btn-warning#js-u2f-try-again Try again?
+
+%script#js-register-u2f-registered{ type: "text/template" }
+ %div.row.append-bottom-10
+ %p Your device was successfully set up! Click this button to register with the GitLab server.
+ = form_tag(create_u2f_profile_two_factor_auth_path, method: :post) do
+ = hidden_field_tag :device_response, nil, class: 'form-control', required: true, id: "js-device-response"
+ = submit_tag "Register U2F Device", class: "btn btn-success"
+
+:javascript
+ var u2fRegister = new U2FRegister($("#js-register-u2f"), gon.u2f);
+ u2fRegister.start();
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 8268380dafc..92305594a81 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -79,10 +79,10 @@
%li.js-contributed-tab
= link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do
Contributed projects
- %li.projects-tab
+ %li.js-projects-tab
= link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects
- %li.snippets-tab
+ %li.js-snippets-tab
= link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do
Snippets
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
deleted file mode 100644
index 4beb8746444..00000000000
--- a/app/views/votes/_votes_block.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-.awards.votes-block
- - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
- %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}}
- = emoji_icon(emoji, sprite: false)
- %span.award-control-text.js-counter
- = notes.count
-
- - if current_user
- %div.award-menu-holder.js-award-holder
- %a.btn.award-control.js-add-award{"href" => "#"}
- = icon('smile-o', {class: "award-control-icon"})
- = icon('spinner spin', {class: "award-control-icon award-control-icon-loading"})
- %span.award-control-text
- Add
-
-- if current_user
- :javascript
- 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.awardsHandler = new AwardsHandler(
- getEmojisUrl,
- postEmojiUrl,
- noteableType,
- noteableId,
- unicodes
- );
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index f9e32337983..d947f105516 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -15,8 +15,7 @@ class RepositoryForkWorker
result = gitlab_shell.fork_repository(source_path, target_path)
unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
- project.update(import_error: "The project could not be forked.")
- project.import_fail
+ project.mark_import_as_failed('The project could not be forked.')
return
end
@@ -24,8 +23,7 @@ class RepositoryForkWorker
unless project.valid_repo?
logger.error("Project #{project_id} had an invalid repository after fork")
- project.update(import_error: "The forked repository is invalid.")
- project.import_fail
+ project.mark_import_as_failed('The forked repository is invalid.')
return
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index fbc7ed63c6a..7d819fe78f8 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -13,8 +13,7 @@ class RepositoryImportWorker
result = Projects::ImportService.new(project, current_user).execute
if result[:status] == :error
- project.update(import_error: Gitlab::UrlSanitizer.sanitize(result[:message]))
- project.import_fail
+ project.mark_import_as_failed(result[:message])
return
end
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
new file mode 100644
index 00000000000..436a2c5e17a
--- /dev/null
+++ b/config/dependency_decisions.yml
@@ -0,0 +1,183 @@
+---
+# IGNORED GROUPS AND GEMS
+- - :ignore_group
+ - development
+ - :who: Connor Shea
+ :why: Development gems are not distributed with the final product and are therefore exempt.
+ :versions: []
+ :when: 2016-04-17 21:27:01.054140000 Z
+- - :ignore_group
+ - test
+ - :who: Connor Shea
+ :why: Test gems are not distributed with the final product and are therefore exempt.
+ :versions: []
+ :when: 2016-04-17 21:27:06.250326000 Z
+- - :ignore
+ - bundler
+ - :who: Connor Shea
+ :why: Bundler is MIT licensed but will sometimes fail in CI.
+ :versions: []
+ :when: 2016-05-02 06:42:08.045090000 Z
+
+# LICENSE WHITELIST
+- - :whitelist
+ - MIT
+ - :who: Connor Shea
+ :why: http://choosealicense.com/licenses/mit/
+ :versions: []
+ :when: 2016-04-17 21:12:24.558441000 Z
+- - :whitelist
+ - Apache 2.0
+ - :who: Connor Shea
+ :why: http://choosealicense.com/licenses/apache-2.0/
+ :versions: []
+ :when: 2016-05-02 05:27:43.762702000 Z
+- - :whitelist
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/ruby/ruby/blob/ruby_2_1/COPYING
+ :versions: []
+ :when: 2016-05-02 05:31:54.498490000 Z
+- - :whitelist
+ - LGPL
+ - :who: Connor Shea
+ :why: http://www.gnu.org/licenses/license-list.html#LGPLv2.1
+ :versions: []
+ :when: 2016-05-02 05:32:48.645841000 Z
+- - :whitelist
+ - ISC
+ - :who: Connor Shea
+ :why: http://www.gnu.org/licenses/license-list.html#ISC
+ :versions: []
+ :when: 2016-05-02 05:42:01.894452000 Z
+- - :whitelist
+ - New BSD
+ - :who: Connor Shea
+ :why: https://opensource.org/licenses/BSD-3-Clause
+ :versions: []
+ :when: 2016-05-02 05:44:38.246021000 Z
+- - :whitelist
+ - LGPL-2.1+
+ - :who: Connor Shea
+ :why: Equivalent to LGPL.
+ :versions: []
+ :when: 2016-05-02 05:52:56.303239000 Z
+- - :whitelist
+ - BSD
+ - :who: Connor Shea
+ :why: https://opensource.org/licenses/BSD-2-Clause
+ :versions: []
+ :when: 2016-05-02 05:55:09.796363000 Z
+
+# LICENSE BLACKLIST
+- - :blacklist
+ - GPLv2
+ - :who: Connor Shea
+ :why: GPL-licensed libraries cannot be linked to from non-GPL projects.
+ :versions: []
+ :when: 2016-05-02 05:29:27.637336000 Z
+- - :blacklist
+ - GPLv3
+ - :who: Connor Shea
+ :why: GPL-licensed libraries cannot be linked to from non-GPL projects.
+ :versions: []
+ :when: 2016-05-02 05:29:43.904715000 Z
+
+# GEM LICENSES
+- - :license
+ - raphael-rails
+ - MIT
+ - :who: Connor Shea
+ :why: https://github.com/mockdeep/raphael-rails/blob/master/license.txt
+ :versions: []
+ :when: 2016-04-17 21:30:07.575392000 Z
+- - :license
+ - rouge
+ - MIT
+ - :who: Connor Shea
+ :why: https://github.com/jneen/rouge/blob/master/LICENSE
+ :versions: []
+ :when: 2016-04-17 21:31:29.490394000 Z
+- - :license
+ - pyu-ruby-sasl
+ - MIT
+ - :who: Connor Shea
+ :why: https://github.com/pyu10055/ruby-sasl/blob/master/MIT-LICENSE
+ :versions: []
+ :when: 2016-04-17 21:41:55.266420000 Z
+- - :license
+ - six
+ - MIT
+ - :who: Connor Shea
+ :why: https://github.com/randx/six/blob/master/LICENSE
+ :versions: []
+ :when: 2016-04-17 21:42:31.420186000 Z
+- - :license
+ - rdoc
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/rdoc/rdoc/blob/master/LICENSE.rdoc
+ :versions: []
+ :when: 2016-04-17 21:43:30.480413000 Z
+- - :license
+ - expression_parser
+ - MIT
+ - :who: Connor Shea
+ :why: https://github.com/nricciar/expression_parser/blob/master/MIT-LICENSE
+ :versions: []
+ :when: 2016-04-17 21:45:41.829912000 Z
+- - :license
+ - creole
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/minad/creole#license
+ :versions: []
+ :when: 2016-04-17 21:49:10.329759000 Z
+- - :license
+ - eventmachine
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/eventmachine/eventmachine/blob/master/LICENSE
+ :versions: []
+ :when: 2016-04-17 21:49:10.329759001 Z
+- - :license
+ - unicorn
+ - ruby
+ - :who: Connor Shea
+ :why: http://unicorn.bogomips.org/LICENSE.html
+ :versions: []
+ :when: 2016-05-02 05:45:28.817510000 Z
+- - :license
+ - unicorn-worker-killer
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/kzk/unicorn-worker-killer/blob/master/LICENSE
+ :versions: []
+ :when: 2016-05-02 05:45:38.323867000 Z
+- - :license
+ - json
+ - ruby
+ - :who: Connor Shea
+ :why: https://github.com/flori/json/tree/master#license
+ :versions: []
+ :when: 2016-05-02 05:50:07.826564000 Z
+- - :license
+ - unf
+ - BSD
+ - :who: Connor Shea
+ :why: https://github.com/knu/ruby-unf/blob/master/LICENSE
+ :versions: []
+ :when: 2016-05-02 05:51:46.886872000 Z
+- - :license
+ - rubypants
+ - BSD
+ - :who: Connor Shea
+ :why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
+ :versions: []
+ :when: 2016-05-02 05:56:50.696858000 Z
+- - :whitelist
+ - LGPLv2+
+ - :who: Stan Hu
+ :why: Equivalent to LGPLv2
+ :versions: []
+ :when: 2016-06-07 17:14:10.907682000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 0510e7df597..1048ef6e243 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -181,7 +181,7 @@ production: &base
# host: registry.example.com
# port: 5005
# api_url: http://localhost:5000/ # internal address to the registry, will be used by GitLab to directly communicate with API
- # key_path: config/registry.key
+ # key: config/registry.key
# path: shared/registry
# issuer: gitlab-issuer
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 7bd13105045..8dc8e270afc 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -12,7 +12,7 @@ Doorkeeper.configure do
end
resource_owner_from_credentials do |routes|
- Gitlab::Auth.new.find(params[:username], params[:password])
+ Gitlab::Auth.find_in_gitlab_or_ldap(params[:username], params[:password])
end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 9e8b0131f8f..3d1a41a4652 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -8,3 +8,7 @@
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
+#
+ActiveSupport::Inflector.inflections do |inflect|
+ inflect.uncountable %w(award_emoji)
+end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 0c788714714..2673093b96a 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -121,6 +121,13 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Gitlab::GitAccessWiki)
config.instrument_instance_methods(API::Helpers)
+
+ config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
+ # Iterate over each non-super private instance method to keep up to date if
+ # internals change
+ RepositoryCheck::SingleRepositoryWorker.private_instance_methods(false).each do |method|
+ config.instrument_instance_method(RepositoryCheck::SingleRepositoryWorker, method)
+ end
end
GC::Profiler.enable
diff --git a/config/license_finder.yml b/config/license_finder.yml
new file mode 100644
index 00000000000..e01ebec3298
--- /dev/null
+++ b/config/license_finder.yml
@@ -0,0 +1,2 @@
+---
+decisions_file: './config/dependency_decisions.yml'
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 761a32adb9e..7cab24b295e 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -2,7 +2,7 @@
<%
require "yaml"
require "json"
-require_relative "lib/gitlab/redis"
+require_relative "lib/gitlab/redis" unless defined?(Gitlab::Redis)
rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
diff --git a/config/routes.rb b/config/routes.rb
index 428302d0fd7..95fbe7dd9df 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -80,8 +80,8 @@ Rails.application.routes.draw do
# Health check
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
- # Enable Grack support
- mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
+ # Enable Grack support (for LFS only)
+ mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
# Help
get 'help' => 'help#index'
@@ -343,8 +343,9 @@ Rails.application.routes.draw do
resources :keys
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
- resource :two_factor_auth, only: [:new, :create, :destroy] do
+ resource :two_factor_auth, only: [:show, :create, :destroy] do
member do
+ post :create_u2f
post :codes
patch :skip
end
@@ -440,6 +441,7 @@ Rails.application.routes.draw do
resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, except:
[:new, :create, :index], path: "/") do
+
member do
put :transfer
delete :remove_fork
@@ -453,6 +455,29 @@ Rails.application.routes.draw do
end
scope module: :projects do
+ # Git HTTP clients ('git clone' etc.)
+ scope constraints: { id: /.+\.git/, format: nil } do
+ get '/info/refs', to: 'git_http#info_refs'
+ post '/git-upload-pack', to: 'git_http#git_upload_pack'
+ post '/git-receive-pack', to: 'git_http#git_receive_pack'
+ end
+
+ # Allow /info/refs, /info/refs?service=git-upload-pack, and
+ # /info/refs?service=git-receive-pack, but nothing else.
+ #
+ git_http_handshake = lambda do |request|
+ request.query_string.blank? ||
+ request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)
+ end
+
+ ref_redirect = redirect do |params, request|
+ path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs"
+ path << "?#{request.query_string}" unless request.query_string.blank?
+ path
+ end
+
+ get '/info/refs', constraints: git_http_handshake, to: ref_redirect
+
# Blob routes:
get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
@@ -591,7 +616,6 @@ Rails.application.routes.draw do
# Order matters to give priority to these matches
get '/wikis/git_access', to: 'wikis#git_access'
get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages'
- post '/wikis/markdown_preview', to:'wikis#markdown_preview'
post '/wikis', to: 'wikis#create'
get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID
@@ -600,6 +624,7 @@ Rails.application.routes.draw do
get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID
delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID
put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID
+ post '/wikis/*id/markdown_preview', to:'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
end
resource :repository, only: [:show, :create] do
@@ -652,6 +677,7 @@ Rails.application.routes.draw do
post :cancel_merge_when_build_succeeds
get :ci_status
post :toggle_subscription
+ post :toggle_award_emoji
post :remove_wip
end
@@ -717,16 +743,19 @@ Rails.application.routes.draw do
resources :labels, constraints: { id: /\d+/ } do
collection do
post :generate
+ post :set_priorities
end
member do
post :toggle_subscription
+ delete :remove_priority
end
end
resources :issues, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
+ post :toggle_award_emoji
get :referenced_merge_requests
get :related_branches
get :can_create_branch
@@ -755,12 +784,9 @@ Rails.application.routes.draw do
resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do
+ post :toggle_award_emoji
delete :delete_attachment
end
-
- collection do
- post :award_toggle
- end
end
resources :uploads, only: [:create] do
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index b99d24a03c9..51ff451eb4c 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, 'master')
+ @project.ensure_pipeline(sha, 'master')
end
rescue
[]
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 78746c83225..b37dc794015 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -16,21 +16,21 @@ user = User.new(user_args)
user.skip_confirmation!
if user.save
- puts "Administrator account created:".green
+ puts "Administrator account created:".color(:green)
puts
- puts "login: root".green
+ puts "login: root".color(:green)
if user_args.key?(:password)
- puts "password: #{user_args[:password]}".green
+ puts "password: #{user_args[:password]}".color(:green)
else
- puts "password: You'll be prompted to create one on your first visit.".green
+ puts "password: You'll be prompted to create one on your first visit.".color(:green)
end
puts
else
- puts "Could not create the default administrator account:".red
+ puts "Could not create the default administrator account:".color(:red)
puts
user.errors.full_messages.map do |message|
- puts "--> #{message}".red
+ puts "--> #{message}".color(:red)
end
puts
diff --git a/db/migrate/20121220064453_init_schema.rb b/db/migrate/20121220064453_init_schema.rb
index d7644b6847a..f93dc92b70f 100644
--- a/db/migrate/20121220064453_init_schema.rb
+++ b/db/migrate/20121220064453_init_schema.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class InitSchema < ActiveRecord::Migration
def up
diff --git a/db/migrate/20130102143055_rename_owner_to_creator_for_project.rb b/db/migrate/20130102143055_rename_owner_to_creator_for_project.rb
index d0fca269871..84fd2060770 100644
--- a/db/migrate/20130102143055_rename_owner_to_creator_for_project.rb
+++ b/db/migrate/20130102143055_rename_owner_to_creator_for_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RenameOwnerToCreatorForProject < ActiveRecord::Migration
def change
rename_column :projects, :owner_id, :creator_id
diff --git a/db/migrate/20130110172407_add_public_to_project.rb b/db/migrate/20130110172407_add_public_to_project.rb
index 45edba48152..4362aadcc1d 100644
--- a/db/migrate/20130110172407_add_public_to_project.rb
+++ b/db/migrate/20130110172407_add_public_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPublicToProject < ActiveRecord::Migration
def change
add_column :projects, :public, :boolean, default: false, null: false
diff --git a/db/migrate/20130123114545_add_issues_tracker_to_project.rb b/db/migrate/20130123114545_add_issues_tracker_to_project.rb
index 288d0f07c9a..ba8c50b53e2 100644
--- a/db/migrate/20130123114545_add_issues_tracker_to_project.rb
+++ b/db/migrate/20130123114545_add_issues_tracker_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIssuesTrackerToProject < ActiveRecord::Migration
def change
add_column :projects, :issues_tracker, :string, default: :gitlab, null: false
diff --git a/db/migrate/20130125090214_add_user_permissions.rb b/db/migrate/20130125090214_add_user_permissions.rb
index 38b5f439a2d..1350eadb60e 100644
--- a/db/migrate/20130125090214_add_user_permissions.rb
+++ b/db/migrate/20130125090214_add_user_permissions.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUserPermissions < ActiveRecord::Migration
def up
add_column :users, :can_create_group, :boolean, default: true, null: false
diff --git a/db/migrate/20130131070232_remove_private_flag_from_project.rb b/db/migrate/20130131070232_remove_private_flag_from_project.rb
index 5754db11558..f0273ba448e 100644
--- a/db/migrate/20130131070232_remove_private_flag_from_project.rb
+++ b/db/migrate/20130131070232_remove_private_flag_from_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemovePrivateFlagFromProject < ActiveRecord::Migration
def up
remove_column :projects, :private_flag
diff --git a/db/migrate/20130206084024_add_description_to_namsespace.rb b/db/migrate/20130206084024_add_description_to_namsespace.rb
index ef02e489d03..62676ce8914 100644
--- a/db/migrate/20130206084024_add_description_to_namsespace.rb
+++ b/db/migrate/20130206084024_add_description_to_namsespace.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDescriptionToNamsespace < ActiveRecord::Migration
def change
add_column :namespaces, :description, :string, default: '', null: false
diff --git a/db/migrate/20130207104426_add_description_to_teams.rb b/db/migrate/20130207104426_add_description_to_teams.rb
index 6d03777901c..bd9a4767b69 100644
--- a/db/migrate/20130207104426_add_description_to_teams.rb
+++ b/db/migrate/20130207104426_add_description_to_teams.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDescriptionToTeams < ActiveRecord::Migration
def change
add_column :user_teams, :description, :string, default: '', null: false
diff --git a/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb b/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
index 71763d18aee..56b01cbf892 100644
--- a/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
+++ b/db/migrate/20130211085435_add_issues_tracker_id_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIssuesTrackerIdToProject < ActiveRecord::Migration
def change
add_column :projects, :issues_tracker_id, :string
diff --git a/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb b/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb
index 23797fe1894..4722cc13d4b 100644
--- a/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb
+++ b/db/migrate/20130214154045_rename_state_to_merge_status_in_milestone.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RenameStateToMergeStatusInMilestone < ActiveRecord::Migration
def change
rename_column :merge_requests, :state, :merge_status
diff --git a/db/migrate/20130218140952_add_state_to_issue.rb b/db/migrate/20130218140952_add_state_to_issue.rb
index 062103d0e33..3a5e978a182 100644
--- a/db/migrate/20130218140952_add_state_to_issue.rb
+++ b/db/migrate/20130218140952_add_state_to_issue.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStateToIssue < ActiveRecord::Migration
def change
add_column :issues, :state, :string
diff --git a/db/migrate/20130218141038_add_state_to_merge_request.rb b/db/migrate/20130218141038_add_state_to_merge_request.rb
index ac4108ee311..e0180c755e2 100644
--- a/db/migrate/20130218141038_add_state_to_merge_request.rb
+++ b/db/migrate/20130218141038_add_state_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStateToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :state, :string
diff --git a/db/migrate/20130218141117_add_state_to_milestone.rb b/db/migrate/20130218141117_add_state_to_milestone.rb
index c84039106bd..5f71608692c 100644
--- a/db/migrate/20130218141117_add_state_to_milestone.rb
+++ b/db/migrate/20130218141117_add_state_to_milestone.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStateToMilestone < ActiveRecord::Migration
def change
add_column :milestones, :state, :string
diff --git a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
index 99289166e81..94c0a6845d5 100644
--- a/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
+++ b/db/migrate/20130218141258_convert_closed_to_state_in_issue.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ConvertClosedToStateInIssue < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
index bd1e016d679..64a9c761352 100644
--- a/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
+++ b/db/migrate/20130218141327_convert_closed_to_state_in_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ConvertClosedToStateInMergeRequest < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
index d1174bc3d98..41508c2dc95 100644
--- a/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
+++ b/db/migrate/20130218141344_convert_closed_to_state_in_milestone.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ConvertClosedToStateInMilestone < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20130218141444_remove_merged_from_merge_request.rb b/db/migrate/20130218141444_remove_merged_from_merge_request.rb
index a7bd82f5000..afa5137061e 100644
--- a/db/migrate/20130218141444_remove_merged_from_merge_request.rb
+++ b/db/migrate/20130218141444_remove_merged_from_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveMergedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :merged
diff --git a/db/migrate/20130218141507_remove_closed_from_issue.rb b/db/migrate/20130218141507_remove_closed_from_issue.rb
index 95cc064252b..f250288bc3b 100644
--- a/db/migrate/20130218141507_remove_closed_from_issue.rb
+++ b/db/migrate/20130218141507_remove_closed_from_issue.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveClosedFromIssue < ActiveRecord::Migration
def up
remove_column :issues, :closed
diff --git a/db/migrate/20130218141536_remove_closed_from_merge_request.rb b/db/migrate/20130218141536_remove_closed_from_merge_request.rb
index 371835938b2..efa12e32636 100644
--- a/db/migrate/20130218141536_remove_closed_from_merge_request.rb
+++ b/db/migrate/20130218141536_remove_closed_from_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveClosedFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :closed
diff --git a/db/migrate/20130218141554_remove_closed_from_milestone.rb b/db/migrate/20130218141554_remove_closed_from_milestone.rb
index e8dae4a19b1..75ac14e43be 100644
--- a/db/migrate/20130218141554_remove_closed_from_milestone.rb
+++ b/db/migrate/20130218141554_remove_closed_from_milestone.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveClosedFromMilestone < ActiveRecord::Migration
def up
remove_column :milestones, :closed
diff --git a/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb b/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb
index d78bd0ae923..97615e47c89 100644
--- a/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb
+++ b/db/migrate/20130220124204_add_new_merge_status_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNewMergeStatusToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :new_merge_status, :string
diff --git a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
index 1c758c56ffe..3b8c3686c55 100644
--- a/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
+++ b/db/migrate/20130220125544_convert_merge_status_in_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ConvertMergeStatusInMergeRequest < ActiveRecord::Migration
def up
execute "UPDATE #{table_name} SET new_merge_status = 'unchecked' WHERE merge_status = 1"
diff --git a/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb b/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb
index 9083183beb0..bd25ffbfc99 100644
--- a/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb
+++ b/db/migrate/20130220125545_remove_merge_status_from_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveMergeStatusFromMergeRequest < ActiveRecord::Migration
def up
remove_column :merge_requests, :merge_status
diff --git a/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb b/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb
index 3f8f38dc979..f0595720a39 100644
--- a/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb
+++ b/db/migrate/20130220133245_rename_new_merge_status_to_merge_status_in_milestone.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RenameNewMergeStatusToMergeStatusInMilestone < ActiveRecord::Migration
def change
rename_column :merge_requests, :new_merge_status, :merge_status
diff --git a/db/migrate/20130304104623_add_state_to_user.rb b/db/migrate/20130304104623_add_state_to_user.rb
index 8154c21065f..4456d022e3f 100644
--- a/db/migrate/20130304104623_add_state_to_user.rb
+++ b/db/migrate/20130304104623_add_state_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStateToUser < ActiveRecord::Migration
def change
add_column :users, :state, :string
diff --git a/db/migrate/20130304104740_convert_blocked_to_state.rb b/db/migrate/20130304104740_convert_blocked_to_state.rb
index e8d5257ac96..9afd1093645 100644
--- a/db/migrate/20130304104740_convert_blocked_to_state.rb
+++ b/db/migrate/20130304104740_convert_blocked_to_state.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ConvertBlockedToState < ActiveRecord::Migration
def up
User.transaction do
diff --git a/db/migrate/20130304105317_remove_blocked_from_user.rb b/db/migrate/20130304105317_remove_blocked_from_user.rb
index e010474538c..8f5b2c59b43 100644
--- a/db/migrate/20130304105317_remove_blocked_from_user.rb
+++ b/db/migrate/20130304105317_remove_blocked_from_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveBlockedFromUser < ActiveRecord::Migration
def up
remove_column :users, :blocked
diff --git a/db/migrate/20130315124931_user_color_scheme.rb b/db/migrate/20130315124931_user_color_scheme.rb
index 56c9a31ee3c..06e28a49d9d 100644
--- a/db/migrate/20130315124931_user_color_scheme.rb
+++ b/db/migrate/20130315124931_user_color_scheme.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class UserColorScheme < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20130318212250_add_snippets_to_features.rb b/db/migrate/20130318212250_add_snippets_to_features.rb
index ad0b4434c43..9860b85f504 100644
--- a/db/migrate/20130318212250_add_snippets_to_features.rb
+++ b/db/migrate/20130318212250_add_snippets_to_features.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSnippetsToFeatures < ActiveRecord::Migration
def change
add_column :projects, :snippets_enabled, :boolean, null: false, default: true
diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb
index f91afc26e77..66eb11a4b2b 100644
--- a/db/migrate/20130319214458_create_forked_project_links.rb
+++ b/db/migrate/20130319214458_create_forked_project_links.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateForkedProjectLinks < ActiveRecord::Migration
def change
create_table :forked_project_links do |t|
diff --git a/db/migrate/20130323174317_add_private_to_snippets.rb b/db/migrate/20130323174317_add_private_to_snippets.rb
index 92f3a5c7011..376f4618d41 100644
--- a/db/migrate/20130323174317_add_private_to_snippets.rb
+++ b/db/migrate/20130323174317_add_private_to_snippets.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPrivateToSnippets < ActiveRecord::Migration
def change
add_column :snippets, :private, :boolean, null: false, default: true
diff --git a/db/migrate/20130324151736_add_type_to_snippets.rb b/db/migrate/20130324151736_add_type_to_snippets.rb
index 276aab2ca15..097cb9bc7cb 100644
--- a/db/migrate/20130324151736_add_type_to_snippets.rb
+++ b/db/migrate/20130324151736_add_type_to_snippets.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTypeToSnippets < ActiveRecord::Migration
def change
add_column :snippets, :type, :string
diff --git a/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb b/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
index 4c992bac4d1..9256e62086e 100644
--- a/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
+++ b/db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ChangeProjectIdToNullInSnipepts < ActiveRecord::Migration
def up
change_column :snippets, :project_id, :integer, :null => true
diff --git a/db/migrate/20130324203535_add_type_value_for_snippets.rb b/db/migrate/20130324203535_add_type_value_for_snippets.rb
index 8c05dd2cc71..6e910fd74c7 100644
--- a/db/migrate/20130324203535_add_type_value_for_snippets.rb
+++ b/db/migrate/20130324203535_add_type_value_for_snippets.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTypeValueForSnippets < ActiveRecord::Migration
def up
Snippet.where("project_id IS NOT NULL").update_all(type: 'ProjectSnippet')
diff --git a/db/migrate/20130325173941_add_notification_level_to_user.rb b/db/migrate/20130325173941_add_notification_level_to_user.rb
index 9f466e38c13..1dc58d4bcc8 100644
--- a/db/migrate/20130325173941_add_notification_level_to_user.rb
+++ b/db/migrate/20130325173941_add_notification_level_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotificationLevelToUser < ActiveRecord::Migration
def change
add_column :users, :notification_level, :integer, null: false, default: 1
diff --git a/db/migrate/20130326142630_add_index_to_users_authentication_token.rb b/db/migrate/20130326142630_add_index_to_users_authentication_token.rb
index d42ef113738..0592181927e 100644
--- a/db/migrate/20130326142630_add_index_to_users_authentication_token.rb
+++ b/db/migrate/20130326142630_add_index_to_users_authentication_token.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexToUsersAuthenticationToken < ActiveRecord::Migration
def change
add_index :users, :authentication_token, unique: true
diff --git a/db/migrate/20130403003950_add_last_activity_column_into_project.rb b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
index 85e31608d79..04a01612c6f 100644
--- a/db/migrate/20130403003950_add_last_activity_column_into_project.rb
+++ b/db/migrate/20130403003950_add_last_activity_column_into_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLastActivityColumnIntoProject < ActiveRecord::Migration
def up
add_column :projects, :last_activity_at, :datetime
diff --git a/db/migrate/20130404164628_add_notification_level_to_user_project.rb b/db/migrate/20130404164628_add_notification_level_to_user_project.rb
index 27de5d6bf55..1e072d9c6e1 100644
--- a/db/migrate/20130404164628_add_notification_level_to_user_project.rb
+++ b/db/migrate/20130404164628_add_notification_level_to_user_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotificationLevelToUserProject < ActiveRecord::Migration
def change
add_column :users_projects, :notification_level, :integer, null: false, default: 3
diff --git a/db/migrate/20130410175022_remove_wiki_table.rb b/db/migrate/20130410175022_remove_wiki_table.rb
index 9077aa2473c..5885b1cc375 100644
--- a/db/migrate/20130410175022_remove_wiki_table.rb
+++ b/db/migrate/20130410175022_remove_wiki_table.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveWikiTable < ActiveRecord::Migration
def up
drop_table :wikis
diff --git a/db/migrate/20130419190306_allow_merges_for_forks.rb b/db/migrate/20130419190306_allow_merges_for_forks.rb
index 56ea97e8561..ec953986c6a 100644
--- a/db/migrate/20130419190306_allow_merges_for_forks.rb
+++ b/db/migrate/20130419190306_allow_merges_for_forks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AllowMergesForForks < ActiveRecord::Migration
def self.up
add_column :merge_requests, :target_project_id, :integer, :null => true
diff --git a/db/migrate/20130506085413_add_type_to_key.rb b/db/migrate/20130506085413_add_type_to_key.rb
index 315e7ca77b3..c9f1ee4e389 100644
--- a/db/migrate/20130506085413_add_type_to_key.rb
+++ b/db/migrate/20130506085413_add_type_to_key.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTypeToKey < ActiveRecord::Migration
def change
add_column :keys, :type, :string
diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb
index 0dc8cdeb07d..7d6662d358a 100644
--- a/db/migrate/20130506090604_create_deploy_keys_projects.rb
+++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateDeployKeysProjects < ActiveRecord::Migration
def change
create_table :deploy_keys_projects do |t|
diff --git a/db/migrate/20130506095501_remove_project_id_from_key.rb b/db/migrate/20130506095501_remove_project_id_from_key.rb
index 6b794cfb5c1..53abc4e7b52 100644
--- a/db/migrate/20130506095501_remove_project_id_from_key.rb
+++ b/db/migrate/20130506095501_remove_project_id_from_key.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveProjectIdFromKey < ActiveRecord::Migration
def up
puts 'Migrate deploy keys: '
diff --git a/db/migrate/20130522141856_add_more_fields_to_service.rb b/db/migrate/20130522141856_add_more_fields_to_service.rb
index 298e902df2f..9f764a1d050 100644
--- a/db/migrate/20130522141856_add_more_fields_to_service.rb
+++ b/db/migrate/20130522141856_add_more_fields_to_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMoreFieldsToService < ActiveRecord::Migration
def change
add_column :services, :subdomain, :string
diff --git a/db/migrate/20130528184641_add_system_to_notes.rb b/db/migrate/20130528184641_add_system_to_notes.rb
index 1b22a4934f9..27fbf8983ac 100644
--- a/db/migrate/20130528184641_add_system_to_notes.rb
+++ b/db/migrate/20130528184641_add_system_to_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSystemToNotes < ActiveRecord::Migration
class Note < ActiveRecord::Base
end
diff --git a/db/migrate/20130611210815_increase_snippet_text_column_size.rb b/db/migrate/20130611210815_increase_snippet_text_column_size.rb
index f7b4447e43e..f710c79a9a5 100644
--- a/db/migrate/20130611210815_increase_snippet_text_column_size.rb
+++ b/db/migrate/20130611210815_increase_snippet_text_column_size.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class IncreaseSnippetTextColumnSize < ActiveRecord::Migration
def up
# MYSQL LARGETEXT for snippet
diff --git a/db/migrate/20130613165816_add_password_expires_at_to_users.rb b/db/migrate/20130613165816_add_password_expires_at_to_users.rb
index 3479c8e64d0..47306a370a8 100644
--- a/db/migrate/20130613165816_add_password_expires_at_to_users.rb
+++ b/db/migrate/20130613165816_add_password_expires_at_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPasswordExpiresAtToUsers < ActiveRecord::Migration
def change
add_column :users, :password_expires_at, :datetime
diff --git a/db/migrate/20130613173246_add_created_by_id_to_user.rb b/db/migrate/20130613173246_add_created_by_id_to_user.rb
index 615e96eb156..3138c0f40a7 100644
--- a/db/migrate/20130613173246_add_created_by_id_to_user.rb
+++ b/db/migrate/20130613173246_add_created_by_id_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCreatedByIdToUser < ActiveRecord::Migration
def change
add_column :users, :created_by_id, :integer
diff --git a/db/migrate/20130614132337_add_improted_to_project.rb b/db/migrate/20130614132337_add_improted_to_project.rb
index cc882c3f10a..26dc16e3b43 100644
--- a/db/migrate/20130614132337_add_improted_to_project.rb
+++ b/db/migrate/20130614132337_add_improted_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImprotedToProject < ActiveRecord::Migration
def change
add_column :projects, :imported, :boolean, default: false, null: false
diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb
index 2efc04f1151..45cff93fe4a 100644
--- a/db/migrate/20130617095603_create_users_groups.rb
+++ b/db/migrate/20130617095603_create_users_groups.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateUsersGroups < ActiveRecord::Migration
def change
create_table :users_groups do |t|
diff --git a/db/migrate/20130621195223_add_notification_level_to_user_group.rb b/db/migrate/20130621195223_add_notification_level_to_user_group.rb
index 8c2e3dfcaca..6fd4941f615 100644
--- a/db/migrate/20130621195223_add_notification_level_to_user_group.rb
+++ b/db/migrate/20130621195223_add_notification_level_to_user_group.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotificationLevelToUserGroup < ActiveRecord::Migration
def change
add_column :users_groups, :notification_level, :integer, null: false, default: 3
diff --git a/db/migrate/20130622115340_add_more_db_index.rb b/db/migrate/20130622115340_add_more_db_index.rb
index 9570a7a3f1e..4113217de59 100644
--- a/db/migrate/20130622115340_add_more_db_index.rb
+++ b/db/migrate/20130622115340_add_more_db_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMoreDbIndex < ActiveRecord::Migration
def change
add_index :deploy_keys_projects, :project_id
diff --git a/db/migrate/20130624162710_add_fingerprint_to_key.rb b/db/migrate/20130624162710_add_fingerprint_to_key.rb
index 544a8366727..3e574ea81b9 100644
--- a/db/migrate/20130624162710_add_fingerprint_to_key.rb
+++ b/db/migrate/20130624162710_add_fingerprint_to_key.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddFingerprintToKey < ActiveRecord::Migration
def change
add_column :keys, :fingerprint, :string
diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb
index 395083f2a03..bd9d40a50db 100644
--- a/db/migrate/20130711063759_create_project_group_links.rb
+++ b/db/migrate/20130711063759_create_project_group_links.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateProjectGroupLinks < ActiveRecord::Migration
def change
create_table :project_group_links do |t|
diff --git a/db/migrate/20130804151314_add_st_diff_to_note.rb b/db/migrate/20130804151314_add_st_diff_to_note.rb
index 3f9abb975c3..9e2da73b695 100644
--- a/db/migrate/20130804151314_add_st_diff_to_note.rb
+++ b/db/migrate/20130804151314_add_st_diff_to_note.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStDiffToNote < ActiveRecord::Migration
def change
add_column :notes, :st_diff, :text, :null => true
diff --git a/db/migrate/20130809124851_add_permission_check_to_user.rb b/db/migrate/20130809124851_add_permission_check_to_user.rb
index c26157904c7..9f9dea36101 100644
--- a/db/migrate/20130809124851_add_permission_check_to_user.rb
+++ b/db/migrate/20130809124851_add_permission_check_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPermissionCheckToUser < ActiveRecord::Migration
def change
add_column :users, :last_credential_check_at, :datetime
diff --git a/db/migrate/20130812143708_add_import_url_to_project.rb b/db/migrate/20130812143708_add_import_url_to_project.rb
index 023a48741b2..d2bdfe1894e 100644
--- a/db/migrate/20130812143708_add_import_url_to_project.rb
+++ b/db/migrate/20130812143708_add_import_url_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportUrlToProject < ActiveRecord::Migration
def change
add_column :projects, :import_url, :string
diff --git a/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb b/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb
index e55ae38f144..0e0e78b0f0d 100644
--- a/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb
+++ b/db/migrate/20130819182730_add_internal_ids_to_issues_and_mr.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddInternalIdsToIssuesAndMr < ActiveRecord::Migration
def change
add_column :issues, :iid, :integer
diff --git a/db/migrate/20130820102832_add_access_to_project_group_link.rb b/db/migrate/20130820102832_add_access_to_project_group_link.rb
index 00e3947a6bb..98f3fa87523 100644
--- a/db/migrate/20130820102832_add_access_to_project_group_link.rb
+++ b/db/migrate/20130820102832_add_access_to_project_group_link.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAccessToProjectGroupLink < ActiveRecord::Migration
def change
add_column :project_group_links, :group_access, :integer, null: false, default: ProjectGroupLink.default_access
diff --git a/db/migrate/20130821090530_remove_deprecated_tables.rb b/db/migrate/20130821090530_remove_deprecated_tables.rb
index 539c0617eeb..d22e713a7a1 100644
--- a/db/migrate/20130821090530_remove_deprecated_tables.rb
+++ b/db/migrate/20130821090530_remove_deprecated_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveDeprecatedTables < ActiveRecord::Migration
def up
drop_table :user_teams
diff --git a/db/migrate/20130821090531_add_internal_ids_to_milestones.rb b/db/migrate/20130821090531_add_internal_ids_to_milestones.rb
index 33e5bae5805..e25b8f91662 100644
--- a/db/migrate/20130821090531_add_internal_ids_to_milestones.rb
+++ b/db/migrate/20130821090531_add_internal_ids_to_milestones.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddInternalIdsToMilestones < ActiveRecord::Migration
def change
add_column :milestones, :iid, :integer
diff --git a/db/migrate/20130909132950_add_description_to_merge_request.rb b/db/migrate/20130909132950_add_description_to_merge_request.rb
index 9bcd0c7ee06..fbac50c8216 100644
--- a/db/migrate/20130909132950_add_description_to_merge_request.rb
+++ b/db/migrate/20130909132950_add_description_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDescriptionToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :description, :text, null: true
diff --git a/db/migrate/20130926081215_change_owner_id_for_group.rb b/db/migrate/20130926081215_change_owner_id_for_group.rb
index 8f1992c37ab..2bdd22d5a04 100644
--- a/db/migrate/20130926081215_change_owner_id_for_group.rb
+++ b/db/migrate/20130926081215_change_owner_id_for_group.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ChangeOwnerIdForGroup < ActiveRecord::Migration
def up
change_column :namespaces, :owner_id, :integer, null: true
diff --git a/db/migrate/20131005191208_add_avatar_to_users.rb b/db/migrate/20131005191208_add_avatar_to_users.rb
index 7b4de37ad72..df9057b81d6 100644
--- a/db/migrate/20131005191208_add_avatar_to_users.rb
+++ b/db/migrate/20131005191208_add_avatar_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAvatarToUsers < ActiveRecord::Migration
def change
add_column :users, :avatar, :string
diff --git a/db/migrate/20131009115346_add_confirmable_to_users.rb b/db/migrate/20131009115346_add_confirmable_to_users.rb
index 249cbe704ed..d714dd98e85 100644
--- a/db/migrate/20131009115346_add_confirmable_to_users.rb
+++ b/db/migrate/20131009115346_add_confirmable_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddConfirmableToUsers < ActiveRecord::Migration
def self.up
add_column :users, :confirmation_token, :string
diff --git a/db/migrate/20131106151520_remove_default_branch.rb b/db/migrate/20131106151520_remove_default_branch.rb
index 88a890eb3eb..fd3d1ed7ab3 100644
--- a/db/migrate/20131106151520_remove_default_branch.rb
+++ b/db/migrate/20131106151520_remove_default_branch.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveDefaultBranch < ActiveRecord::Migration
def up
remove_column :projects, :default_branch
diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb
index 147178e9dcf..ce37a8e2708 100644
--- a/db/migrate/20131112114325_create_broadcast_messages.rb
+++ b/db/migrate/20131112114325_create_broadcast_messages.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateBroadcastMessages < ActiveRecord::Migration
def change
create_table :broadcast_messages do |t|
diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
index 89421cbedad..5efc17b228e 100644
--- a/db/migrate/20131112220935_add_visibility_level_to_projects.rb
+++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddVisibilityLevelToProjects < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20131129154016_add_archived_to_projects.rb b/db/migrate/20131129154016_add_archived_to_projects.rb
index 917e690ba47..e8e6908d137 100644
--- a/db/migrate/20131129154016_add_archived_to_projects.rb
+++ b/db/migrate/20131129154016_add_archived_to_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddArchivedToProjects < ActiveRecord::Migration
def change
add_column :projects, :archived, :boolean, default: false, null: false
diff --git a/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb
index 473f355eceb..348a284a53e 100644
--- a/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb
+++ b/db/migrate/20131130165425_add_color_and_font_to_broadcast_messages.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddColorAndFontToBroadcastMessages < ActiveRecord::Migration
def change
add_column :broadcast_messages, :color, :string
diff --git a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb
index d29e996852e..99d76611524 100644
--- a/db/migrate/20131202192556_add_event_fields_for_web_hook.rb
+++ b/db/migrate/20131202192556_add_event_fields_for_web_hook.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEventFieldsForWebHook < ActiveRecord::Migration
def change
add_column :web_hooks, :push_events, :boolean, default: true, null: false
diff --git a/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb
index 7cec79e7ee8..4333dc59323 100644
--- a/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb
+++ b/db/migrate/20131214224427_add_hide_no_ssh_key_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHideNoSshKeyToUsers < ActiveRecord::Migration
def change
add_column :users, :hide_no_ssh_key, :boolean, :default => false
diff --git a/db/migrate/20131217102743_add_recipients_to_service.rb b/db/migrate/20131217102743_add_recipients_to_service.rb
index 9695c251352..3c76be0f68d 100644
--- a/db/migrate/20131217102743_add_recipients_to_service.rb
+++ b/db/migrate/20131217102743_add_recipients_to_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRecipientsToService < ActiveRecord::Migration
def change
add_column :services, :recipients, :text
diff --git a/db/migrate/20140116231608_add_website_url_to_users.rb b/db/migrate/20140116231608_add_website_url_to_users.rb
index 0996fdcad73..1c39423562e 100644
--- a/db/migrate/20140116231608_add_website_url_to_users.rb
+++ b/db/migrate/20140116231608_add_website_url_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddWebsiteUrlToUsers < ActiveRecord::Migration
def change
add_column :users, :website_url, :string, {:null => false, :default => ''}
diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb
index f34e30925df..395c3edfc79 100644
--- a/db/migrate/20140122112253_create_merge_request_diffs.rb
+++ b/db/migrate/20140122112253_create_merge_request_diffs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateMergeRequestDiffs < ActiveRecord::Migration
def up
create_table :merge_request_diffs do |t|
diff --git a/db/migrate/20140122114406_migrate_mr_diffs.rb b/db/migrate/20140122114406_migrate_mr_diffs.rb
index 1595e2b6472..429aeb2293f 100644
--- a/db/migrate/20140122114406_migrate_mr_diffs.rb
+++ b/db/migrate/20140122114406_migrate_mr_diffs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateMrDiffs < ActiveRecord::Migration
def self.up
execute "INSERT INTO merge_request_diffs ( merge_request_id, st_commits, st_diffs ) SELECT id, st_commits, st_diffs FROM merge_requests"
diff --git a/db/migrate/20140122122549_remove_m_rdiff_fields.rb b/db/migrate/20140122122549_remove_m_rdiff_fields.rb
index 8f863d85a68..bbf35811b61 100644
--- a/db/migrate/20140122122549_remove_m_rdiff_fields.rb
+++ b/db/migrate/20140122122549_remove_m_rdiff_fields.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveMRdiffFields < ActiveRecord::Migration
def up
remove_column :merge_requests, :st_commits
diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb
index 9523ac722f2..888341b7535 100644
--- a/db/migrate/20140125162722_add_avatar_to_projects.rb
+++ b/db/migrate/20140125162722_add_avatar_to_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAvatarToProjects < ActiveRecord::Migration
def change
add_column :projects, :avatar, :string
diff --git a/db/migrate/20140127170938_add_group_avatars.rb b/db/migrate/20140127170938_add_group_avatars.rb
index 2911096dd5d..95d1c1c6b27 100644
--- a/db/migrate/20140127170938_add_group_avatars.rb
+++ b/db/migrate/20140127170938_add_group_avatars.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddGroupAvatars < ActiveRecord::Migration
def change
add_column :namespaces, :avatar, :string
diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb
index cb78c4af11b..571beb19cdd 100644
--- a/db/migrate/20140209025651_create_emails.rb
+++ b/db/migrate/20140209025651_create_emails.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateEmails < ActiveRecord::Migration
def change
create_table :emails do |t|
diff --git a/db/migrate/20140214102325_add_api_key_to_services.rb b/db/migrate/20140214102325_add_api_key_to_services.rb
index 30eeca2c1f6..b58c36c0a30 100644
--- a/db/migrate/20140214102325_add_api_key_to_services.rb
+++ b/db/migrate/20140214102325_add_api_key_to_services.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddApiKeyToServices < ActiveRecord::Migration
def change
add_column :services, :api_key, :string
diff --git a/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
index 65d28e8cb01..aab8a41c2c3 100644
--- a/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
+++ b/db/migrate/20140304005354_add_index_merge_request_diffs_on_merge_request_id.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexMergeRequestDiffsOnMergeRequestId < ActiveRecord::Migration
def change
add_index :merge_request_diffs, :merge_request_id, unique: true
diff --git a/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
index 7017148702a..ec163bb843c 100644
--- a/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
+++ b/db/migrate/20140305193308_add_tag_push_hooks_to_project_hook.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTagPushHooksToProjectHook < ActiveRecord::Migration
def change
add_column :web_hooks, :tag_push_events, :boolean, default: false
diff --git a/db/migrate/20140312145357_add_import_status_to_project.rb b/db/migrate/20140312145357_add_import_status_to_project.rb
index ef972e8342a..9947cd8c6f9 100644
--- a/db/migrate/20140312145357_add_import_status_to_project.rb
+++ b/db/migrate/20140312145357_add_import_status_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportStatusToProject < ActiveRecord::Migration
def change
add_column :projects, :import_status, :string
diff --git a/db/migrate/20140313092127_migrate_already_imported_projects.rb b/db/migrate/20140313092127_migrate_already_imported_projects.rb
index 0a9f73a5758..f2e91fe1b40 100644
--- a/db/migrate/20140313092127_migrate_already_imported_projects.rb
+++ b/db/migrate/20140313092127_migrate_already_imported_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateAlreadyImportedProjects < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20140407135544_fix_namespaces.rb b/db/migrate/20140407135544_fix_namespaces.rb
index 59665d538f0..91374966698 100644
--- a/db/migrate/20140407135544_fix_namespaces.rb
+++ b/db/migrate/20140407135544_fix_namespaces.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FixNamespaces < ActiveRecord::Migration
def up
Namespace.where('name <> path and type is null').each do |namespace|
diff --git a/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
index 1f6d85d5f66..fb9c7a6636e 100644
--- a/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
+++ b/db/migrate/20140414131055_change_state_to_allow_empty_merge_request_diffs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ChangeStateToAllowEmptyMergeRequestDiffs < ActiveRecord::Migration
def up
change_column :merge_request_diffs, :state, :string, null: true,
diff --git a/db/migrate/20140415124820_limits_to_mysql.rb b/db/migrate/20140415124820_limits_to_mysql.rb
index 3f6e62617c5..c712423bcd1 100644
--- a/db/migrate/20140415124820_limits_to_mysql.rb
+++ b/db/migrate/20140415124820_limits_to_mysql.rb
@@ -1 +1,2 @@
+# rubocop:disable all
require_relative 'limits_to_mysql'
diff --git a/db/migrate/20140416074002_add_index_on_iid.rb b/db/migrate/20140416074002_add_index_on_iid.rb
index 85269e2a03b..6cdaa5a3c08 100644
--- a/db/migrate/20140416074002_add_index_on_iid.rb
+++ b/db/migrate/20140416074002_add_index_on_iid.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexOnIid < ActiveRecord::Migration
def change
RemoveDuplicateIid.clean(Issue)
diff --git a/db/migrate/20140416185734_index_on_current_sign_in_at.rb b/db/migrate/20140416185734_index_on_current_sign_in_at.rb
index 0bf80ce154a..8c620b545bd 100644
--- a/db/migrate/20140416185734_index_on_current_sign_in_at.rb
+++ b/db/migrate/20140416185734_index_on_current_sign_in_at.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class IndexOnCurrentSignInAt < ActiveRecord::Migration
def change
add_index :users, :current_sign_in_at
diff --git a/db/migrate/20140428105831_add_notes_index_updated_at.rb b/db/migrate/20140428105831_add_notes_index_updated_at.rb
index 6c25570f128..0589101af93 100644
--- a/db/migrate/20140428105831_add_notes_index_updated_at.rb
+++ b/db/migrate/20140428105831_add_notes_index_updated_at.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotesIndexUpdatedAt < ActiveRecord::Migration
def change
add_index :notes, :updated_at
diff --git a/db/migrate/20140502115131_add_repo_size_to_db.rb b/db/migrate/20140502115131_add_repo_size_to_db.rb
index 7361d1a9440..090b30a4f26 100644
--- a/db/migrate/20140502115131_add_repo_size_to_db.rb
+++ b/db/migrate/20140502115131_add_repo_size_to_db.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRepoSizeToDb < ActiveRecord::Migration
def change
add_column :projects, :repository_size, :float, default: 0
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index efdf53112fd..84463727b3b 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateRepoSize < ActiveRecord::Migration
def up
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')
diff --git a/db/migrate/20140611135229_add_position_to_merge_request.rb b/db/migrate/20140611135229_add_position_to_merge_request.rb
index d5fdecd0c39..3a7d2f7c359 100644
--- a/db/migrate/20140611135229_add_position_to_merge_request.rb
+++ b/db/migrate/20140611135229_add_position_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPositionToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :position, :integer, default: 0
diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb
index 412f0f6f34b..32dd99e83be 100644
--- a/db/migrate/20140625115202_create_users_star_projects.rb
+++ b/db/migrate/20140625115202_create_users_star_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateUsersStarProjects < ActiveRecord::Migration
def change
create_table :users_star_projects do |t|
diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb
index 3a4b6a152dc..df0f8cb9f03 100644
--- a/db/migrate/20140729134820_create_labels.rb
+++ b/db/migrate/20140729134820_create_labels.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateLabels < ActiveRecord::Migration
def change
create_table :labels do |t|
diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb
index 2bfc4ae2094..fa5992605f8 100644
--- a/db/migrate/20140729140420_create_label_links.rb
+++ b/db/migrate/20140729140420_create_label_links.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateLabelLinks < ActiveRecord::Migration
def change
create_table :label_links do |t|
diff --git a/db/migrate/20140729145339_migrate_project_tags.rb b/db/migrate/20140729145339_migrate_project_tags.rb
index 5760e4bfeaa..ac46847f3e6 100644
--- a/db/migrate/20140729145339_migrate_project_tags.rb
+++ b/db/migrate/20140729145339_migrate_project_tags.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateProjectTags < ActiveRecord::Migration
def up
ActsAsTaggableOn::Tagging.where(taggable_type: 'Project', context: 'labels').update_all(context: 'tags')
diff --git a/db/migrate/20140729152420_migrate_taggable_labels.rb b/db/migrate/20140729152420_migrate_taggable_labels.rb
index dc28d727d9a..04cdc6beadd 100644
--- a/db/migrate/20140729152420_migrate_taggable_labels.rb
+++ b/db/migrate/20140729152420_migrate_taggable_labels.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateTaggableLabels < ActiveRecord::Migration
def up
taggings = ActsAsTaggableOn::Tagging.where(taggable_type: ['Issue', 'MergeRequest'], context: 'labels')
diff --git a/db/migrate/20140730111702_add_index_to_labels.rb b/db/migrate/20140730111702_add_index_to_labels.rb
index 494241c873c..cc7ac1fc449 100644
--- a/db/migrate/20140730111702_add_index_to_labels.rb
+++ b/db/migrate/20140730111702_add_index_to_labels.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexToLabels < ActiveRecord::Migration
def change
add_index "labels", :project_id
diff --git a/db/migrate/20140903115954_migrate_to_new_shell.rb b/db/migrate/20140903115954_migrate_to_new_shell.rb
index 54cbe48960a..04acf24284b 100644
--- a/db/migrate/20140903115954_migrate_to_new_shell.rb
+++ b/db/migrate/20140903115954_migrate_to_new_shell.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateToNewShell < ActiveRecord::Migration
def change
return if Rails.env.test?
diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb
index d45a10465be..c2d67fad0ab 100644
--- a/db/migrate/20140907220153_serialize_service_properties.rb
+++ b/db/migrate/20140907220153_serialize_service_properties.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class SerializeServiceProperties < ActiveRecord::Migration
def change
unless column_exists?(:services, :properties)
diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb
index d311f3033ee..bc3c1bb61e4 100644
--- a/db/migrate/20140914113604_add_members_table.rb
+++ b/db/migrate/20140914113604_add_members_table.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMembersTable < ActiveRecord::Migration
def change
create_table :members do |t|
diff --git a/db/migrate/20140914145549_migrate_to_new_members_model.rb b/db/migrate/20140914145549_migrate_to_new_members_model.rb
index 2a5a49c724a..b4c98f016d0 100644
--- a/db/migrate/20140914145549_migrate_to_new_members_model.rb
+++ b/db/migrate/20140914145549_migrate_to_new_members_model.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateToNewMembersModel < ActiveRecord::Migration
def up
execute "INSERT INTO members ( user_id, source_id, source_type, access_level, notification_level, type ) SELECT user_id, group_id, 'Namespace', group_access, notification_level, 'GroupMember' FROM users_groups"
diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb
index 408b9551dbb..aff8e94e5be 100644
--- a/db/migrate/20140914173417_remove_old_member_tables.rb
+++ b/db/migrate/20140914173417_remove_old_member_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveOldMemberTables < ActiveRecord::Migration
def up
drop_table :users_groups
diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
index 5836cd6b8db..8cb120f7007 100644
--- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb
+++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MoveSlackServiceToWebhook < ActiveRecord::Migration
def change
SlackService.all.each do |slack_service|
diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
index 93826185e8b..688d8578478 100644
--- a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
+++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddVisibilityLevelToSnippet < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb
index 07383c6bbc7..3884228456f 100644
--- a/db/migrate/20141118150935_add_audit_event.rb
+++ b/db/migrate/20141118150935_add_audit_event.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAuditEvent < ActiveRecord::Migration
def change
create_table :audit_events do |t|
diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb
index ef6d4dedf32..68f164cd35d 100644
--- a/db/migrate/20141121133009_add_timestamps_to_members.rb
+++ b/db/migrate/20141121133009_add_timestamps_to_members.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the
# created_at and updated_at times for new records in the 'members' table. This
# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
index a85b0426cec..5a399f0d325 100644
--- a/db/migrate/20141121161704_add_identity_table.rb
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIdentityTable < ActiveRecord::Migration
def up
create_table :identities do |t|
diff --git a/db/migrate/20141205134006_add_locked_at_to_merge_request.rb b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
index 49651c44a82..5aa91c7587a 100644
--- a/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
+++ b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLockedAtToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :locked_at, :datetime
diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb
index af5aa7d8b73..b323ffe96f5 100644
--- a/db/migrate/20141216155758_create_doorkeeper_tables.rb
+++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateDoorkeeperTables < ActiveRecord::Migration
def change
create_table :oauth_applications do |t|
diff --git a/db/migrate/20141217125223_add_owner_to_application.rb b/db/migrate/20141217125223_add_owner_to_application.rb
index 7d5e6d07d0f..e5a669ab4d8 100644
--- a/db/migrate/20141217125223_add_owner_to_application.rb
+++ b/db/migrate/20141217125223_add_owner_to_application.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddOwnerToApplication < ActiveRecord::Migration
def change
add_column :oauth_applications, :owner_id, :integer, null: true
diff --git a/db/migrate/20141223135007_add_import_data_to_project_table.rb b/db/migrate/20141223135007_add_import_data_to_project_table.rb
index 5db78f94cc9..9c8a483e4d5 100644
--- a/db/migrate/20141223135007_add_import_data_to_project_table.rb
+++ b/db/migrate/20141223135007_add_import_data_to_project_table.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportDataToProjectTable < ActiveRecord::Migration
def change
add_column :projects, :import_type, :string
diff --git a/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb
index 70e7272f7f3..a18b2f4974d 100644
--- a/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb
+++ b/db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDevelopersCanPushToProtectedBranches < ActiveRecord::Migration
def change
add_column :protected_branches, :developers_can_push, :boolean, default: false, null: false
diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb
index 651e35fdf7a..dfa2f765357 100644
--- a/db/migrate/20150108073740_create_application_settings.rb
+++ b/db/migrate/20150108073740_create_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateApplicationSettings < ActiveRecord::Migration
def change
create_table :application_settings do |t|
diff --git a/db/migrate/20150116234544_add_home_page_url_for_application_settings.rb b/db/migrate/20150116234544_add_home_page_url_for_application_settings.rb
index aa179ce3a4d..10e6549c729 100644
--- a/db/migrate/20150116234544_add_home_page_url_for_application_settings.rb
+++ b/db/migrate/20150116234544_add_home_page_url_for_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHomePageUrlForApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :home_page_url, :string
diff --git a/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb b/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb
index c28ba3197ac..e083973615a 100644
--- a/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb
+++ b/db/migrate/20150116234545_add_gitlab_access_token_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddGitlabAccessTokenToUser < ActiveRecord::Migration
def change
add_column :users, :gitlab_access_token, :string
diff --git a/db/migrate/20150125163100_add_default_branch_protection_setting.rb b/db/migrate/20150125163100_add_default_branch_protection_setting.rb
index 5020daf55f3..7ca3116d354 100644
--- a/db/migrate/20150125163100_add_default_branch_protection_setting.rb
+++ b/db/migrate/20150125163100_add_default_branch_protection_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDefaultBranchProtectionSetting < ActiveRecord::Migration
def change
add_column :application_settings, :default_branch_protection, :integer, :default => 2
diff --git a/db/migrate/20150205211843_add_timestamps_to_identities.rb b/db/migrate/20150205211843_add_timestamps_to_identities.rb
index 77cddbfec3b..a78e28eb4eb 100644
--- a/db/migrate/20150205211843_add_timestamps_to_identities.rb
+++ b/db/migrate/20150205211843_add_timestamps_to_identities.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTimestampsToIdentities < ActiveRecord::Migration
def change
add_timestamps(:identities)
diff --git a/db/migrate/20150206181414_add_index_to_created_at.rb b/db/migrate/20150206181414_add_index_to_created_at.rb
index fc624fca60d..a161fad79dc 100644
--- a/db/migrate/20150206181414_add_index_to_created_at.rb
+++ b/db/migrate/20150206181414_add_index_to_created_at.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexToCreatedAt < ActiveRecord::Migration
def change
add_index "users", [:created_at, :id]
diff --git a/db/migrate/20150206222854_add_notification_email_to_user.rb b/db/migrate/20150206222854_add_notification_email_to_user.rb
index ab80f7e582f..ebae092cac8 100644
--- a/db/migrate/20150206222854_add_notification_email_to_user.rb
+++ b/db/migrate/20150206222854_add_notification_email_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotificationEmailToUser < ActiveRecord::Migration
def up
add_column :users, :notification_email, :string
diff --git a/db/migrate/20150209222013_add_missing_index.rb b/db/migrate/20150209222013_add_missing_index.rb
index a816c2e9e8c..18e3ac2cbbb 100644
--- a/db/migrate/20150209222013_add_missing_index.rb
+++ b/db/migrate/20150209222013_add_missing_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMissingIndex < ActiveRecord::Migration
def change
add_index "services", [:created_at, :id]
diff --git a/db/migrate/20150211172122_add_template_to_service.rb b/db/migrate/20150211172122_add_template_to_service.rb
index b1bfbc45ee9..a3e96b25c56 100644
--- a/db/migrate/20150211172122_add_template_to_service.rb
+++ b/db/migrate/20150211172122_add_template_to_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTemplateToService < ActiveRecord::Migration
def change
add_column :services, :template, :boolean, default: false
diff --git a/db/migrate/20150211174341_allow_null_in_services_project_id.rb b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
index 68f02812791..fea95c79adf 100644
--- a/db/migrate/20150211174341_allow_null_in_services_project_id.rb
+++ b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AllowNullInServicesProjectId < ActiveRecord::Migration
def change
change_column :services, :project_id, :integer, null: true
diff --git a/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
index a0439172391..334020376e4 100644
--- a/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
+++ b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTwitterSharingEnabledToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :twitter_sharing_enabled, :boolean, default: true
diff --git a/db/migrate/20150213114800_add_hide_no_password_to_user.rb b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
index 685f0844276..a2af3510b9c 100644
--- a/db/migrate/20150213114800_add_hide_no_password_to_user.rb
+++ b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHideNoPasswordToUser < ActiveRecord::Migration
def change
add_column :users, :hide_no_password, :boolean, default: false
diff --git a/db/migrate/20150213121042_add_password_automatically_set_to_user.rb b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
index c3c7c1ffc77..4e84a13f0d2 100644
--- a/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
+++ b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPasswordAutomaticallySetToUser < ActiveRecord::Migration
def change
add_column :users, :password_automatically_set, :boolean, default: false
diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
index 23ac1b399ec..78e9fd0c3a9 100644
--- a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
+++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration
def change
add_column :users, :bitbucket_access_token, :string
diff --git a/db/migrate/20150219004514_add_events_to_services.rb b/db/migrate/20150219004514_add_events_to_services.rb
index cf73a0174f4..560382c3fa1 100644
--- a/db/migrate/20150219004514_add_events_to_services.rb
+++ b/db/migrate/20150219004514_add_events_to_services.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEventsToServices < ActiveRecord::Migration
def change
add_column :services, :push_events, :boolean, :default => true
diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb
index 3f6d4d83474..300381ad65b 100644
--- a/db/migrate/20150223022001_set_missing_last_activity_at.rb
+++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class SetMissingLastActivityAt < ActiveRecord::Migration
def up
execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL"
diff --git a/db/migrate/20150225065047_add_note_events_to_services.rb b/db/migrate/20150225065047_add_note_events_to_services.rb
index d54ba9e482f..7843cabc43b 100644
--- a/db/migrate/20150225065047_add_note_events_to_services.rb
+++ b/db/migrate/20150225065047_add_note_events_to_services.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNoteEventsToServices < ActiveRecord::Migration
def change
add_column :services, :note_events, :boolean, default: true, null: false
diff --git a/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
index 494c3033bff..7d8d65ef2ee 100644
--- a/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
+++ b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRestrictedVisibilityLevelsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :restricted_visibility_levels, :text
diff --git a/db/migrate/20150306023106_fix_namespace_duplication.rb b/db/migrate/20150306023106_fix_namespace_duplication.rb
index 334e5574559..ea53a9d71f2 100644
--- a/db/migrate/20150306023106_fix_namespace_duplication.rb
+++ b/db/migrate/20150306023106_fix_namespace_duplication.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FixNamespaceDuplication < ActiveRecord::Migration
def up
#fixes path duplication
diff --git a/db/migrate/20150306023112_add_unique_index_to_namespace.rb b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
index 6472138e3ef..f293a9b643f 100644
--- a/db/migrate/20150306023112_add_unique_index_to_namespace.rb
+++ b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUniqueIndexToNamespace < ActiveRecord::Migration
def change
remove_index :namespaces, column: :name if index_exists?(:namespaces, :name)
diff --git a/db/migrate/20150310194358_add_version_check_to_application_settings.rb b/db/migrate/20150310194358_add_version_check_to_application_settings.rb
index e9d42c1e749..5d3dae6e7d8 100644
--- a/db/migrate/20150310194358_add_version_check_to_application_settings.rb
+++ b/db/migrate/20150310194358_add_version_check_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddVersionCheckToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :version_check_enabled, :boolean, default: true
diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb
index a1d4d9dedc5..8adb193b27f 100644
--- a/db/migrate/20150313012111_create_subscriptions_table.rb
+++ b/db/migrate/20150313012111_create_subscriptions_table.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateSubscriptionsTable < ActiveRecord::Migration
def change
create_table :subscriptions do |t|
diff --git a/db/migrate/20150320234437_add_location_to_user.rb b/db/migrate/20150320234437_add_location_to_user.rb
index 32731d37d75..df046570361 100644
--- a/db/migrate/20150320234437_add_location_to_user.rb
+++ b/db/migrate/20150320234437_add_location_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLocationToUser < ActiveRecord::Migration
def change
add_column :users, :location, :string
diff --git a/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb
index 42dc8173e46..9f8b6f4bd59 100644
--- a/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb
+++ b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class SetIncorrectAssigneeIdToNull < ActiveRecord::Migration
def up
execute "UPDATE issues SET assignee_id = NULL WHERE assignee_id = -1"
diff --git a/db/migrate/20150327122227_add_public_to_key.rb b/db/migrate/20150327122227_add_public_to_key.rb
index 6ffbf4cda19..33c20d65e03 100644
--- a/db/migrate/20150327122227_add_public_to_key.rb
+++ b/db/migrate/20150327122227_add_public_to_key.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPublicToKey < ActiveRecord::Migration
def change
add_column :keys, :public, :boolean, default: false, null: false
diff --git a/db/migrate/20150327150017_add_import_data_to_project.rb b/db/migrate/20150327150017_add_import_data_to_project.rb
index 12c00339eec..67b1554dfd1 100644
--- a/db/migrate/20150327150017_add_import_data_to_project.rb
+++ b/db/migrate/20150327150017_add_import_data_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportDataToProject < ActiveRecord::Migration
def change
add_column :projects, :import_data, :text
diff --git a/db/migrate/20150327223628_add_devise_two_factor_to_users.rb b/db/migrate/20150327223628_add_devise_two_factor_to_users.rb
index 11b026ee8f3..eccb0123e77 100644
--- a/db/migrate/20150327223628_add_devise_two_factor_to_users.rb
+++ b/db/migrate/20150327223628_add_devise_two_factor_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDeviseTwoFactorToUsers < ActiveRecord::Migration
def change
add_column :users, :encrypted_otp_secret, :string
diff --git a/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb
index 1d161674a9a..4c56a2fb78b 100644
--- a/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb
+++ b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMaxAttachmentSizeToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :max_attachment_size, :integer, default: 10, null: false
diff --git a/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb b/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb
index 913958db7c5..fdb6d72917e 100644
--- a/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb
+++ b/db/migrate/20150331183602_add_devise_two_factor_backupable_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration
def change
add_column :users, :otp_backup_codes, :text
diff --git a/db/migrate/20150406133311_add_invite_data_to_member.rb b/db/migrate/20150406133311_add_invite_data_to_member.rb
index 5d3e856ddce..63d0f184f32 100644
--- a/db/migrate/20150406133311_add_invite_data_to_member.rb
+++ b/db/migrate/20150406133311_add_invite_data_to_member.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddInviteDataToMember < ActiveRecord::Migration
def up
add_column :members, :created_by_id, :integer
diff --git a/db/migrate/20150411000035_fix_identities.rb b/db/migrate/20150411000035_fix_identities.rb
index d9051f9fffd..a10fcc001f4 100644
--- a/db/migrate/20150411000035_fix_identities.rb
+++ b/db/migrate/20150411000035_fix_identities.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FixIdentities < ActiveRecord::Migration
def up
# Up until now, legacy 'ldap' references in the database were charitably
diff --git a/db/migrate/20150411180045_rename_buildbox_service.rb b/db/migrate/20150411180045_rename_buildbox_service.rb
index 5a0b5d07e50..9f3b25c3971 100644
--- a/db/migrate/20150411180045_rename_buildbox_service.rb
+++ b/db/migrate/20150411180045_rename_buildbox_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RenameBuildboxService < ActiveRecord::Migration
def up
execute "UPDATE services SET type = 'BuildkiteService' WHERE type = 'BuildboxService';"
diff --git a/db/migrate/20150413192223_add_public_email_to_users.rb b/db/migrate/20150413192223_add_public_email_to_users.rb
index 700e9f343a6..0fed5eaf461 100644
--- a/db/migrate/20150413192223_add_public_email_to_users.rb
+++ b/db/migrate/20150413192223_add_public_email_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPublicEmailToUsers < ActiveRecord::Migration
def change
add_column :users, :public_email, :string, default: "", null: false
diff --git a/db/migrate/20150417121913_create_project_import_data.rb b/db/migrate/20150417121913_create_project_import_data.rb
index c78f5fde85e..fc357cbacc8 100644
--- a/db/migrate/20150417121913_create_project_import_data.rb
+++ b/db/migrate/20150417121913_create_project_import_data.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateProjectImportData < ActiveRecord::Migration
def change
create_table :project_import_data do |t|
diff --git a/db/migrate/20150417122318_remove_import_data_from_project.rb b/db/migrate/20150417122318_remove_import_data_from_project.rb
index 46cf63593c9..5a008218fa5 100644
--- a/db/migrate/20150417122318_remove_import_data_from_project.rb
+++ b/db/migrate/20150417122318_remove_import_data_from_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveImportDataFromProject < ActiveRecord::Migration
def up
remove_column :projects, :import_data
diff --git a/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb b/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb
index 3057ea3c68c..3445e9ce59e 100644
--- a/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb
+++ b/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemovePeriodsAtEndsOfUsernames < ActiveRecord::Migration
include Gitlab::ShellAdapter
diff --git a/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
index 50a9b2439e0..129ce4d04af 100644
--- a/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
+++ b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDefaultProjectVisibililtyToApplicationSettings < ActiveRecord::Migration
def up
add_column :application_settings, :default_project_visibility, :integer
diff --git a/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
index 281c88d2a7d..8f352414ffd 100644
--- a/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164646_gitlab_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration is a duplicate of 20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
# It shold be applied before the index additions to ensure that `name` is case sensitive.
diff --git a/db/migrate/20150425164647_remove_duplicate_tags.rb b/db/migrate/20150425164647_remove_duplicate_tags.rb
index 13e5038db9c..e77623bf507 100644
--- a/db/migrate/20150425164647_remove_duplicate_tags.rb
+++ b/db/migrate/20150425164647_remove_duplicate_tags.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveDuplicateTags < ActiveRecord::Migration
def up
select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(id) > 1").each do |tag|
diff --git a/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
index c1b78681519..cbff98cdbc4 100644
--- a/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164648_add_missing_unique_indices.acts_as_taggable_on_engine.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration comes from acts_as_taggable_on_engine (originally 2)
class AddMissingUniqueIndices < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20150425164649_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20150425164649_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
index 8edb5080781..1568d2dd4ce 100644
--- a/db/migrate/20150425164649_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164649_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration comes from acts_as_taggable_on_engine (originally 3)
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20150425164650_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20150425164650_add_missing_taggable_index.acts_as_taggable_on_engine.rb
index 71f2d7f4330..88829b87711 100644
--- a/db/migrate/20150425164650_add_missing_taggable_index.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164650_add_missing_taggable_index.acts_as_taggable_on_engine.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration comes from acts_as_taggable_on_engine (originally 4)
class AddMissingTaggableIndex < ActiveRecord::Migration
def self.up
diff --git a/db/migrate/20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
index bfb06bc7cda..642c4745321 100644
--- a/db/migrate/20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
+++ b/db/migrate/20150425164651_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration comes from acts_as_taggable_on_engine (originally 5)
# This migration is added to circumvent issue #623 and have special characters
# work properly
diff --git a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
index 8f1b0cc8935..dd13def4176 100644
--- a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
+++ b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDefaultSnippetVisibilityToAppSettings < ActiveRecord::Migration
def up
add_column :application_settings, :default_snippet_visibility, :integer
diff --git a/db/migrate/20150429002313_remove_abandoned_group_members_records.rb b/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
index 244637e1c4a..d2c7f3c442e 100644
--- a/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
+++ b/db/migrate/20150429002313_remove_abandoned_group_members_records.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveAbandonedGroupMembersRecords < ActiveRecord::Migration
def up
execute("DELETE FROM members WHERE type = 'GroupMember' AND source_id NOT IN(\
diff --git a/db/migrate/20150502064022_add_restricted_signup_domains_to_application_settings.rb b/db/migrate/20150502064022_add_restricted_signup_domains_to_application_settings.rb
index 184e2653610..b63ea9aec7a 100644
--- a/db/migrate/20150502064022_add_restricted_signup_domains_to_application_settings.rb
+++ b/db/migrate/20150502064022_add_restricted_signup_domains_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRestrictedSignupDomainsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :restricted_signup_domains, :text
diff --git a/db/migrate/20150509180749_convert_legacy_reference_notes.rb b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
index b02605489be..cd8bf90108d 100644
--- a/db/migrate/20150509180749_convert_legacy_reference_notes.rb
+++ b/db/migrate/20150509180749_convert_legacy_reference_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# Convert legacy Markdown-emphasized notes to the current, non-emphasized format
#
# _mentioned in 54f7727c850972f0401c1312a7c4a6a380de5666_
diff --git a/db/migrate/20150516060434_add_note_events_to_web_hooks.rb b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb
index 0097587b4f6..bf72e5e2e3a 100644
--- a/db/migrate/20150516060434_add_note_events_to_web_hooks.rb
+++ b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNoteEventsToWebHooks < ActiveRecord::Migration
def up
add_column :web_hooks, :note_events, :boolean, default: false, null: false
diff --git a/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
index 6a78294f0b2..9b02eda56ab 100644
--- a/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
+++ b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUserOauthApplicationsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :user_oauth_applications, :bool, default: true
diff --git a/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
index 83e08101407..833c36de52d 100644
--- a/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
+++ b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAfterSignOutPathForApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :after_sign_out_path, :string
diff --git a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
index 61ff0af41f4..1f5cf1fe5f1 100644
--- a/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
+++ b/db/migrate/20150609141121_add_session_expire_delay_for_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSessionExpireDelayForApplicationSettings < ActiveRecord::Migration
def change
unless column_exists?(:application_settings, :session_expire_delay)
diff --git a/db/migrate/20150610065936_add_dashboard_to_users.rb b/db/migrate/20150610065936_add_dashboard_to_users.rb
index 2628e450722..df38472f893 100644
--- a/db/migrate/20150610065936_add_dashboard_to_users.rb
+++ b/db/migrate/20150610065936_add_dashboard_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDashboardToUsers < ActiveRecord::Migration
def up
add_column :users, :dashboard, :integer, default: 0
diff --git a/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb b/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb
index 8eed8678b2f..da0fd457a34 100644
--- a/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb
+++ b/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDefaultOtpRequiredForLoginValue < ActiveRecord::Migration
def up
execute %q{UPDATE users SET otp_required_for_login = FALSE WHERE otp_required_for_login IS NULL}
diff --git a/db/migrate/20150713160110_add_project_view_to_users.rb b/db/migrate/20150713160110_add_project_view_to_users.rb
index fe3d206df89..0de5a93035c 100644
--- a/db/migrate/20150713160110_add_project_view_to_users.rb
+++ b/db/migrate/20150713160110_add_project_view_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectViewToUsers < ActiveRecord::Migration
def change
add_column :users, :project_view, :integer, default: 0
diff --git a/db/migrate/20150717130904_add_commits_count_to_project.rb b/db/migrate/20150717130904_add_commits_count_to_project.rb
index 9b46daa5933..5799e068c69 100644
--- a/db/migrate/20150717130904_add_commits_count_to_project.rb
+++ b/db/migrate/20150717130904_add_commits_count_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCommitsCountToProject < ActiveRecord::Migration
def change
add_column :projects, :commit_count, :integer, default: 0
diff --git a/db/migrate/20150730122406_add_updated_by_to_issuables_and_notes.rb b/db/migrate/20150730122406_add_updated_by_to_issuables_and_notes.rb
index 78d45c7f96b..be30e881c74 100644
--- a/db/migrate/20150730122406_add_updated_by_to_issuables_and_notes.rb
+++ b/db/migrate/20150730122406_add_updated_by_to_issuables_and_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUpdatedByToIssuablesAndNotes < ActiveRecord::Migration
def change
add_column :notes, :updated_by_id, :integer
diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb
index e97dc4cf04c..3c749b5d9a9 100644
--- a/db/migrate/20150806104937_create_abuse_reports.rb
+++ b/db/migrate/20150806104937_create_abuse_reports.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateAbuseReports < ActiveRecord::Migration
def change
create_table :abuse_reports do |t|
diff --git a/db/migrate/20150812080800_add_settings_import_sources.rb b/db/migrate/20150812080800_add_settings_import_sources.rb
index 276d2fdb2b1..07f417fa3e3 100644
--- a/db/migrate/20150812080800_add_settings_import_sources.rb
+++ b/db/migrate/20150812080800_add_settings_import_sources.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
require 'yaml'
class AddSettingsImportSources < ActiveRecord::Migration
diff --git a/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb b/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb
index de2078a9268..7eaa7eda311 100644
--- a/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb
+++ b/db/migrate/20150814065925_remove_oauth_tokens_from_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveOauthTokensFromUsers < ActiveRecord::Migration
def change
remove_column :users, :github_access_token, :string
diff --git a/db/migrate/20150817163600_deduplicate_user_identities.rb b/db/migrate/20150817163600_deduplicate_user_identities.rb
index fceffc48018..b0cfad7d20f 100644
--- a/db/migrate/20150817163600_deduplicate_user_identities.rb
+++ b/db/migrate/20150817163600_deduplicate_user_identities.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class DeduplicateUserIdentities < ActiveRecord::Migration
def change
execute 'DROP TABLE IF EXISTS tt_migration_DeduplicateUserIdentities;'
diff --git a/db/migrate/20150818213832_add_sent_notifications.rb b/db/migrate/20150818213832_add_sent_notifications.rb
index 43e8d6a1a82..fa0c3ce0acf 100644
--- a/db/migrate/20150818213832_add_sent_notifications.rb
+++ b/db/migrate/20150818213832_add_sent_notifications.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSentNotifications < ActiveRecord::Migration
def change
create_table :sent_notifications do |t|
diff --git a/db/migrate/20150824002011_add_enable_ssl_verification.rb b/db/migrate/20150824002011_add_enable_ssl_verification.rb
index 093c068fbde..6e992f08834 100644
--- a/db/migrate/20150824002011_add_enable_ssl_verification.rb
+++ b/db/migrate/20150824002011_add_enable_ssl_verification.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEnableSslVerification < ActiveRecord::Migration
def change
add_column :web_hooks, :enable_ssl_verification, :boolean, default: false
diff --git a/db/migrate/20150826001931_add_ci_tables.rb b/db/migrate/20150826001931_add_ci_tables.rb
index c4f51363e57..d1f8506d1fe 100644
--- a/db/migrate/20150826001931_add_ci_tables.rb
+++ b/db/migrate/20150826001931_add_ci_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiTables < ActiveRecord::Migration
def change
create_table "ci_application_settings", force: true do |t|
diff --git a/db/migrate/20150902001023_add_template_to_label.rb b/db/migrate/20150902001023_add_template_to_label.rb
index bd381a97b69..0f6ae8d6cc3 100644
--- a/db/migrate/20150902001023_add_template_to_label.rb
+++ b/db/migrate/20150902001023_add_template_to_label.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTemplateToLabel < ActiveRecord::Migration
def change
add_column :labels, :template, :boolean, default: false
diff --git a/db/migrate/20150914215247_add_ci_tags.rb b/db/migrate/20150914215247_add_ci_tags.rb
index df3390e8a82..b647bc9c8a2 100644
--- a/db/migrate/20150914215247_add_ci_tags.rb
+++ b/db/migrate/20150914215247_add_ci_tags.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiTags < ActiveRecord::Migration
def change
create_table "ci_taggings", force: true do |t|
diff --git a/db/migrate/20150915001905_enable_ssl_verification_by_default.rb b/db/migrate/20150915001905_enable_ssl_verification_by_default.rb
index 6e924262a13..3f070139418 100644
--- a/db/migrate/20150915001905_enable_ssl_verification_by_default.rb
+++ b/db/migrate/20150915001905_enable_ssl_verification_by_default.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class EnableSslVerificationByDefault < ActiveRecord::Migration
def change
change_column :web_hooks, :enable_ssl_verification, :boolean, default: true
diff --git a/db/migrate/20150916000405_enable_ssl_verification_for_web_hooks.rb b/db/migrate/20150916000405_enable_ssl_verification_for_web_hooks.rb
index 90ce6c2db3d..ea2ab6e4093 100644
--- a/db/migrate/20150916000405_enable_ssl_verification_for_web_hooks.rb
+++ b/db/migrate/20150916000405_enable_ssl_verification_for_web_hooks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class EnableSslVerificationForWebHooks < ActiveRecord::Migration
def up
execute("UPDATE web_hooks SET enable_ssl_verification = true")
diff --git a/db/migrate/20150916114643_add_help_page_text_to_application_settings.rb b/db/migrate/20150916114643_add_help_page_text_to_application_settings.rb
index 37a27f11935..a504f25b1be 100644
--- a/db/migrate/20150916114643_add_help_page_text_to_application_settings.rb
+++ b/db/migrate/20150916114643_add_help_page_text_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHelpPageTextToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :help_page_text, :text
diff --git a/db/migrate/20150916145038_add_index_for_committed_at_and_id.rb b/db/migrate/20150916145038_add_index_for_committed_at_and_id.rb
index 78d9e5f61a1..a18ed93cf37 100644
--- a/db/migrate/20150916145038_add_index_for_committed_at_and_id.rb
+++ b/db/migrate/20150916145038_add_index_for_committed_at_and_id.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexForCommittedAtAndId < ActiveRecord::Migration
def change
add_index :ci_commits, [:project_id, :committed_at, :id]
diff --git a/db/migrate/20150918084513_add_ci_enabled_to_application_settings.rb b/db/migrate/20150918084513_add_ci_enabled_to_application_settings.rb
index 6cf668a170e..c9b6e035122 100644
--- a/db/migrate/20150918084513_add_ci_enabled_to_application_settings.rb
+++ b/db/migrate/20150918084513_add_ci_enabled_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiEnabledToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :ci_enabled, :boolean, null: false, default: true
diff --git a/db/migrate/20150918161719_remove_invalid_milestones_from_merge_requests.rb b/db/migrate/20150918161719_remove_invalid_milestones_from_merge_requests.rb
index 0aad6fe5e6e..e1818b566d7 100644
--- a/db/migrate/20150918161719_remove_invalid_milestones_from_merge_requests.rb
+++ b/db/migrate/20150918161719_remove_invalid_milestones_from_merge_requests.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveInvalidMilestonesFromMergeRequests < ActiveRecord::Migration
def up
execute("UPDATE merge_requests SET milestone_id = NULL where milestone_id NOT IN (SELECT id FROM milestones)")
diff --git a/db/migrate/20150920010715_add_consumed_timestep_to_users.rb b/db/migrate/20150920010715_add_consumed_timestep_to_users.rb
index c8438b3f6aa..e6975f5b9fe 100644
--- a/db/migrate/20150920010715_add_consumed_timestep_to_users.rb
+++ b/db/migrate/20150920010715_add_consumed_timestep_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddConsumedTimestepToUsers < ActiveRecord::Migration
def change
add_column :users, :consumed_timestep, :integer
diff --git a/db/migrate/20150920161119_add_line_code_to_sent_notification.rb b/db/migrate/20150920161119_add_line_code_to_sent_notification.rb
index d9af4e71751..1bcb06e4bda 100644
--- a/db/migrate/20150920161119_add_line_code_to_sent_notification.rb
+++ b/db/migrate/20150920161119_add_line_code_to_sent_notification.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLineCodeToSentNotification < ActiveRecord::Migration
def change
add_column :sent_notifications, :line_code, :string
diff --git a/db/migrate/20150924125150_add_project_id_to_ci_commit.rb b/db/migrate/20150924125150_add_project_id_to_ci_commit.rb
index 1a761fe0f86..905332b7dc7 100644
--- a/db/migrate/20150924125150_add_project_id_to_ci_commit.rb
+++ b/db/migrate/20150924125150_add_project_id_to_ci_commit.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectIdToCiCommit < ActiveRecord::Migration
def up
add_column :ci_commits, :gl_project_id, :integer
diff --git a/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb b/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb
index 2be57b6062e..fb0e0ba1fa5 100644
--- a/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb
+++ b/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateProjectIdForCiCommits < ActiveRecord::Migration
def up
subquery = 'SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = ci_commits.project_id'
diff --git a/db/migrate/20150930001110_merge_request_error_field.rb b/db/migrate/20150930001110_merge_request_error_field.rb
index c2ee498ef3f..71a8ae3938a 100644
--- a/db/migrate/20150930001110_merge_request_error_field.rb
+++ b/db/migrate/20150930001110_merge_request_error_field.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MergeRequestErrorField < ActiveRecord::Migration
def up
add_column :merge_requests, :merge_error, :string
diff --git a/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb b/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb
index 8d47dac6441..229c9942b50 100644
--- a/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb
+++ b/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNullToNameForCiProjects < ActiveRecord::Migration
def up
change_column_null :ci_projects, :name, true
diff --git a/db/migrate/20150930110012_add_group_share_lock.rb b/db/migrate/20150930110012_add_group_share_lock.rb
index 78d1a4538f2..96938bf9ab6 100644
--- a/db/migrate/20150930110012_add_group_share_lock.rb
+++ b/db/migrate/20150930110012_add_group_share_lock.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddGroupShareLock < ActiveRecord::Migration
def change
add_column :namespaces, :share_with_group_lock, :boolean, default: false
diff --git a/db/migrate/20151002112914_add_stage_idx_to_builds.rb b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
index 68a745ffef4..4297ba0e7c8 100644
--- a/db/migrate/20151002112914_add_stage_idx_to_builds.rb
+++ b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddStageIdxToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :stage_idx, :integer
diff --git a/db/migrate/20151002121400_add_index_for_builds.rb b/db/migrate/20151002121400_add_index_for_builds.rb
index 4ffc1363910..bd945c54540 100644
--- a/db/migrate/20151002121400_add_index_for_builds.rb
+++ b/db/migrate/20151002121400_add_index_for_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexForBuilds < ActiveRecord::Migration
def up
add_index :ci_builds, [:commit_id, :stage_idx, :created_at]
diff --git a/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb b/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb
index e3d2ac1cea5..3c0fcf6c45d 100644
--- a/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb
+++ b/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRefAndTagToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :tag, :boolean
diff --git a/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb b/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb
index 01d7b3f6773..52217ce5af2 100644
--- a/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb
+++ b/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateRefAndTagToBuild < ActiveRecord::Migration
def change
execute('UPDATE ci_builds SET ref=(SELECT ref FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE ref IS NULL')
diff --git a/db/migrate/20151005075649_add_user_id_to_build.rb b/db/migrate/20151005075649_add_user_id_to_build.rb
index 0f4b92b8b79..be9d403e002 100644
--- a/db/migrate/20151005075649_add_user_id_to_build.rb
+++ b/db/migrate/20151005075649_add_user_id_to_build.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUserIdToBuild < ActiveRecord::Migration
def change
add_column :ci_builds, :user_id, :integer
diff --git a/db/migrate/20151005150751_add_layout_option_for_users.rb b/db/migrate/20151005150751_add_layout_option_for_users.rb
index ead9b1f8977..7e68606969f 100644
--- a/db/migrate/20151005150751_add_layout_option_for_users.rb
+++ b/db/migrate/20151005150751_add_layout_option_for_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLayoutOptionForUsers < ActiveRecord::Migration
def change
add_column :users, :layout, :integer, default: 0
diff --git a/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb b/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb
index be6aa810bb5..07dba598749 100644
--- a/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb
+++ b/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveCiEnabledFromApplicationSettings < ActiveRecord::Migration
def change
remove_column :application_settings, :ci_enabled, :boolean, null: false, default: true
diff --git a/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb b/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb
index 7f6cd6d5a78..38208e59804 100644
--- a/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb
+++ b/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class NamespacesProjectsPathLowerIndexes < ActiveRecord::Migration
disable_ddl_transaction!
diff --git a/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
index 2f2dc776785..6080d2a0fcf 100644
--- a/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
+++ b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
disable_ddl_transaction!
diff --git a/db/migrate/20151008123042_add_type_and_description_to_builds.rb b/db/migrate/20151008123042_add_type_and_description_to_builds.rb
index c72b1c611c6..a19eb6c6c49 100644
--- a/db/migrate/20151008123042_add_type_and_description_to_builds.rb
+++ b/db/migrate/20151008123042_add_type_and_description_to_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTypeAndDescriptionToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :type, :string
diff --git a/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb b/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb
index f5c44babd84..306fa7092ea 100644
--- a/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb
+++ b/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateNameToDescriptionForBuilds < ActiveRecord::Migration
def change
execute("UPDATE ci_builds SET type='Ci::Build' WHERE type IS NULL")
diff --git a/db/migrate/20151008143519_add_admin_notification_email_setting.rb b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
index 0bb581efe2c..f48ec9aa4a6 100644
--- a/db/migrate/20151008143519_add_admin_notification_email_setting.rb
+++ b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAdminNotificationEmailSetting < ActiveRecord::Migration
def change
add_column :application_settings, :admin_notification_email, :string
diff --git a/db/migrate/20151012173029_set_jira_service_api_url.rb b/db/migrate/20151012173029_set_jira_service_api_url.rb
index 2af99e0db0b..2b6f61428c0 100644
--- a/db/migrate/20151012173029_set_jira_service_api_url.rb
+++ b/db/migrate/20151012173029_set_jira_service_api_url.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class SetJiraServiceApiUrl < ActiveRecord::Migration
# This migration can be performed online without errors, but some Jira API calls may be missed
# when doing so because api_url is not yet available.
diff --git a/db/migrate/20151013092124_add_artifacts_file_to_builds.rb b/db/migrate/20151013092124_add_artifacts_file_to_builds.rb
index 5a299f7b26d..a54ac9d57a4 100644
--- a/db/migrate/20151013092124_add_artifacts_file_to_builds.rb
+++ b/db/migrate/20151013092124_add_artifacts_file_to_builds.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddArtifactsFileToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :artifacts_file, :text
diff --git a/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
index 52a47aa9c54..eb3351eb767 100644
--- a/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
+++ b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration
def change
add_index :ci_commits, :gl_project_id
diff --git a/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
index 7f1af1c7583..899e004d610 100644
--- a/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
+++ b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
def change
add_index :ci_projects, :gitlab_id
diff --git a/db/migrate/20151016195706_add_notes_line_code_index.rb b/db/migrate/20151016195706_add_notes_line_code_index.rb
index aeeb1a759fa..3298630c1e8 100644
--- a/db/migrate/20151016195706_add_notes_line_code_index.rb
+++ b/db/migrate/20151016195706_add_notes_line_code_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotesLineCodeIndex < ActiveRecord::Migration
def change
add_index :notes, :line_code
diff --git a/db/migrate/20151019111551_fix_build_tags.rb b/db/migrate/20151019111551_fix_build_tags.rb
index 299a24b0a7c..8c05acfc190 100644
--- a/db/migrate/20151019111551_fix_build_tags.rb
+++ b/db/migrate/20151019111551_fix_build_tags.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FixBuildTags < ActiveRecord::Migration
def up
execute("UPDATE taggings SET taggable_type='CommitStatus' WHERE taggable_type='Ci::Build'")
diff --git a/db/migrate/20151019111703_fail_build_without_names.rb b/db/migrate/20151019111703_fail_build_without_names.rb
index dcdb5d1b25d..362e31eb435 100644
--- a/db/migrate/20151019111703_fail_build_without_names.rb
+++ b/db/migrate/20151019111703_fail_build_without_names.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FailBuildWithoutNames < ActiveRecord::Migration
def up
execute("UPDATE ci_builds SET status='failed' WHERE name IS NULL AND status='pending'")
diff --git a/db/migrate/20151020145526_add_services_template_index.rb b/db/migrate/20151020145526_add_services_template_index.rb
index 1b04f313565..14ff07bd726 100644
--- a/db/migrate/20151020145526_add_services_template_index.rb
+++ b/db/migrate/20151020145526_add_services_template_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddServicesTemplateIndex < ActiveRecord::Migration
def change
add_index :services, :template
diff --git a/db/migrate/20151020173516_ci_limits_to_mysql.rb b/db/migrate/20151020173516_ci_limits_to_mysql.rb
index 9bb960082f5..5314611cbcd 100644
--- a/db/migrate/20151020173516_ci_limits_to_mysql.rb
+++ b/db/migrate/20151020173516_ci_limits_to_mysql.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CiLimitsToMysql < ActiveRecord::Migration
def change
return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
diff --git a/db/migrate/20151020173906_add_ci_builds_index_for_status.rb b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
index c3f0e0606da..81a31e46ff8 100644
--- a/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
+++ b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiBuildsIndexForStatus < ActiveRecord::Migration
def change
add_index :ci_builds, [:commit_id, :status, :type]
diff --git a/db/migrate/20151023112551_fail_build_with_empty_name.rb b/db/migrate/20151023112551_fail_build_with_empty_name.rb
index 41c0f0649cd..0666dfeaef4 100644
--- a/db/migrate/20151023112551_fail_build_with_empty_name.rb
+++ b/db/migrate/20151023112551_fail_build_with_empty_name.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FailBuildWithEmptyName < ActiveRecord::Migration
def up
execute("UPDATE ci_builds SET status='failed' WHERE (name IS NULL OR name='') AND status='pending'")
diff --git a/db/migrate/20151023144219_remove_satellites.rb b/db/migrate/20151023144219_remove_satellites.rb
index e73f300028a..98fe0bd7d1d 100644
--- a/db/migrate/20151023144219_remove_satellites.rb
+++ b/db/migrate/20151023144219_remove_satellites.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
require 'fileutils'
class RemoveSatellites < ActiveRecord::Migration
diff --git a/db/migrate/20151026182941_add_project_path_index.rb b/db/migrate/20151026182941_add_project_path_index.rb
index a62fe199d70..117f65c1a1b 100644
--- a/db/migrate/20151026182941_add_project_path_index.rb
+++ b/db/migrate/20151026182941_add_project_path_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectPathIndex < ActiveRecord::Migration
def up
add_index :projects, :path
diff --git a/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb
index ceb52f0c222..4a989669464 100644
--- a/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb
+++ b/db/migrate/20151028152939_add_merge_when_build_succeeds_to_merge_request.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMergeWhenBuildSucceedsToMergeRequest < ActiveRecord::Migration
def change
add_column :merge_requests, :merge_params, :text
diff --git a/db/migrate/20151103001141_add_public_to_group.rb b/db/migrate/20151103001141_add_public_to_group.rb
index 635346300c2..ba1f7c27832 100644
--- a/db/migrate/20151103001141_add_public_to_group.rb
+++ b/db/migrate/20151103001141_add_public_to_group.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPublicToGroup < ActiveRecord::Migration
def change
add_column :namespaces, :public, :boolean, default: false
diff --git a/db/migrate/20151103133339_add_shared_runners_setting.rb b/db/migrate/20151103133339_add_shared_runners_setting.rb
index 4231dfd5c2e..b5b34d4ca61 100644
--- a/db/migrate/20151103133339_add_shared_runners_setting.rb
+++ b/db/migrate/20151103133339_add_shared_runners_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSharedRunnersSetting < ActiveRecord::Migration
def up
add_column :application_settings, :shared_runners_enabled, :boolean, default: true, null: false
diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb
index 2d04c170a88..745b52e2b24 100644
--- a/db/migrate/20151103134857_create_lfs_objects.rb
+++ b/db/migrate/20151103134857_create_lfs_objects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateLfsObjects < ActiveRecord::Migration
def change
create_table :lfs_objects do |t|
diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb
index f3f58b931ec..3178e85b899 100644
--- a/db/migrate/20151103134958_create_lfs_objects_projects.rb
+++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateLfsObjectsProjects < ActiveRecord::Migration
def change
create_table :lfs_objects_projects do |t|
diff --git a/db/migrate/20151104105513_add_file_to_lfs_objects.rb b/db/migrate/20151104105513_add_file_to_lfs_objects.rb
index 7c57f3f0df6..4e46ae8101c 100644
--- a/db/migrate/20151104105513_add_file_to_lfs_objects.rb
+++ b/db/migrate/20151104105513_add_file_to_lfs_objects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddFileToLfsObjects < ActiveRecord::Migration
def change
add_column :lfs_objects, :file, :string
diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb
index fe4608c6662..145b8db1486 100644
--- a/db/migrate/20151105094515_create_releases.rb
+++ b/db/migrate/20151105094515_create_releases.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateReleases < ActiveRecord::Migration
def change
create_table :releases do |t|
diff --git a/db/migrate/20151106000015_add_is_award_to_notes.rb b/db/migrate/20151106000015_add_is_award_to_notes.rb
index 02b271637e9..b463d939b78 100644
--- a/db/migrate/20151106000015_add_is_award_to_notes.rb
+++ b/db/migrate/20151106000015_add_is_award_to_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIsAwardToNotes < ActiveRecord::Migration
def change
add_column :notes, :is_award, :boolean, default: false, null: false
diff --git a/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb b/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb
index 01d8c0f043e..25106ace7e9 100644
--- a/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb
+++ b/db/migrate/20151109100728_add_max_artifacts_size_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMaxArtifactsSizeToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :max_artifacts_size, :integer, default: 100, null: false
diff --git a/db/migrate/20151109134526_add_issues_state_index.rb b/db/migrate/20151109134526_add_issues_state_index.rb
index 1c4d2e30171..7a9970e8591 100644
--- a/db/migrate/20151109134526_add_issues_state_index.rb
+++ b/db/migrate/20151109134526_add_issues_state_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIssuesStateIndex < ActiveRecord::Migration
def change
add_index :issues, :state
diff --git a/db/migrate/20151109134916_add_projects_visibility_level_index.rb b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
index 600b4bafd98..471db437b11 100644
--- a/db/migrate/20151109134916_add_projects_visibility_level_index.rb
+++ b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectsVisibilityLevelIndex < ActiveRecord::Migration
def change
add_index :projects, :visibility_level
diff --git a/db/migrate/20151110125604_add_import_error_to_project.rb b/db/migrate/20151110125604_add_import_error_to_project.rb
index 7fc990f8d0a..793358c305e 100644
--- a/db/migrate/20151110125604_add_import_error_to_project.rb
+++ b/db/migrate/20151110125604_add_import_error_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportErrorToProject < ActiveRecord::Migration
def change
add_column :projects, :import_error, :text
diff --git a/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb
index d10f1f6e605..00a4c74ffbc 100644
--- a/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb
+++ b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexForLfsOidAndSize < ActiveRecord::Migration
def change
add_index :lfs_objects, :oid
diff --git a/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb
index 41b93da0a86..1f192544ea1 100644
--- a/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb
+++ b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUniqueForLfsOidIndex < ActiveRecord::Migration
def change
remove_index :lfs_objects, :oid
diff --git a/db/migrate/20151118162244_add_projects_public_index.rb b/db/migrate/20151118162244_add_projects_public_index.rb
index fded70e3c0c..589f124c21e 100644
--- a/db/migrate/20151118162244_add_projects_public_index.rb
+++ b/db/migrate/20151118162244_add_projects_public_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectsPublicIndex < ActiveRecord::Migration
def change
add_index :namespaces, :public
diff --git a/db/migrate/20151201203948_raise_hook_url_limit.rb b/db/migrate/20151201203948_raise_hook_url_limit.rb
index 98a7fca6f6f..c490b7ace0f 100644
--- a/db/migrate/20151201203948_raise_hook_url_limit.rb
+++ b/db/migrate/20151201203948_raise_hook_url_limit.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RaiseHookUrlLimit < ActiveRecord::Migration
def change
change_column :web_hooks, :url, :string, limit: 2000
diff --git a/db/migrate/20151203162133_add_hide_project_limit_to_users.rb b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
index 6ffadfa1894..5dc6d8bf445 100644
--- a/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
+++ b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHideProjectLimitToUsers < ActiveRecord::Migration
def change
add_column :users, :hide_project_limit, :boolean, default: false
diff --git a/db/migrate/20151203162134_add_build_events_to_services.rb b/db/migrate/20151203162134_add_build_events_to_services.rb
index c5542cb864d..455882e5ec0 100644
--- a/db/migrate/20151203162134_add_build_events_to_services.rb
+++ b/db/migrate/20151203162134_add_build_events_to_services.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddBuildEventsToServices < ActiveRecord::Migration
def change
add_column :services, :build_events, :boolean, default: false, null: false
diff --git a/db/migrate/20151209144329_migrate_ci_web_hooks.rb b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
index d7e196e6763..cb1e556623a 100644
--- a/db/migrate/20151209144329_migrate_ci_web_hooks.rb
+++ b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateCiWebHooks < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20151209145909_migrate_ci_emails.rb b/db/migrate/20151209145909_migrate_ci_emails.rb
index 7f330a2cf0a..6b7a106814d 100644
--- a/db/migrate/20151209145909_migrate_ci_emails.rb
+++ b/db/migrate/20151209145909_migrate_ci_emails.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateCiEmails < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20151210030143_add_unlock_token_to_user.rb b/db/migrate/20151210030143_add_unlock_token_to_user.rb
index 0ea66ba65df..d23c648f782 100644
--- a/db/migrate/20151210030143_add_unlock_token_to_user.rb
+++ b/db/migrate/20151210030143_add_unlock_token_to_user.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUnlockTokenToUser < ActiveRecord::Migration
def change
add_column :users, :unlock_token, :string
diff --git a/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb b/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb
index 00f88180e46..92c7b5befd2 100644
--- a/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb
+++ b/db/migrate/20151210072243_add_runners_registration_token_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRunnersRegistrationTokenToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :runners_registration_token, :string
diff --git a/db/migrate/20151210125232_migrate_ci_slack_service.rb b/db/migrate/20151210125232_migrate_ci_slack_service.rb
index f14efa3e95d..633d5148d97 100644
--- a/db/migrate/20151210125232_migrate_ci_slack_service.rb
+++ b/db/migrate/20151210125232_migrate_ci_slack_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateCiSlackService < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
index b9e04323576..dae084ce180 100644
--- a/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
+++ b/db/migrate/20151210125927_migrate_ci_hip_chat_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateCiHipChatService < ActiveRecord::Migration
include Gitlab::Database
diff --git a/db/migrate/20151210125928_add_ci_to_project.rb b/db/migrate/20151210125928_add_ci_to_project.rb
index 8c167f64a2b..a9ff49a3f7e 100644
--- a/db/migrate/20151210125928_add_ci_to_project.rb
+++ b/db/migrate/20151210125928_add_ci_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiToProject < ActiveRecord::Migration
def change
add_column :projects, :ci_id, :integer
diff --git a/db/migrate/20151210125929_add_project_id_to_ci.rb b/db/migrate/20151210125929_add_project_id_to_ci.rb
index 84273591fa2..b5de64b82ca 100644
--- a/db/migrate/20151210125929_add_project_id_to_ci.rb
+++ b/db/migrate/20151210125929_add_project_id_to_ci.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddProjectIdToCi < ActiveRecord::Migration
def change
add_column :ci_builds, :gl_project_id, :integer
diff --git a/db/migrate/20151210125930_migrate_ci_to_project.rb b/db/migrate/20151210125930_migrate_ci_to_project.rb
index c32c7feb193..bb6d74ae212 100644
--- a/db/migrate/20151210125930_migrate_ci_to_project.rb
+++ b/db/migrate/20151210125930_migrate_ci_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class MigrateCiToProject < ActiveRecord::Migration
def up
migrate_project_id_for_table('ci_runner_projects')
diff --git a/db/migrate/20151210125931_add_index_to_ci_tables.rb b/db/migrate/20151210125931_add_index_to_ci_tables.rb
index 5e129c9303d..d87d335cf6b 100644
--- a/db/migrate/20151210125931_add_index_to_ci_tables.rb
+++ b/db/migrate/20151210125931_add_index_to_ci_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexToCiTables < ActiveRecord::Migration
def change
add_index :ci_builds, :gl_project_id
diff --git a/db/migrate/20151210125932_drop_null_for_ci_tables.rb b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
index c520c2ed56f..e1a0a964589 100644
--- a/db/migrate/20151210125932_drop_null_for_ci_tables.rb
+++ b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class DropNullForCiTables < ActiveRecord::Migration
def change
remove_index :ci_variables, :project_id
diff --git a/db/migrate/20151218154042_add_tfa_to_application_settings.rb b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
index dd95db775c5..afdaf76b917 100644
--- a/db/migrate/20151218154042_add_tfa_to_application_settings.rb
+++ b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTfaToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
diff --git a/db/migrate/20151221234414_add_tfa_additional_fields.rb b/db/migrate/20151221234414_add_tfa_additional_fields.rb
index c16df47932f..c3e4aaa606a 100644
--- a/db/migrate/20151221234414_add_tfa_additional_fields.rb
+++ b/db/migrate/20151221234414_add_tfa_additional_fields.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTfaAdditionalFields < ActiveRecord::Migration
def change
change_table :users do |t|
diff --git a/db/migrate/20151224123230_rename_emojis.rb b/db/migrate/20151224123230_rename_emojis.rb
index 62d921dfdcc..2c24f3beeea 100644
--- a/db/migrate/20151224123230_rename_emojis.rb
+++ b/db/migrate/20151224123230_rename_emojis.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# Migration type: online without errors (works on previous version and new one)
class RenameEmojis < ActiveRecord::Migration
def up
diff --git a/db/migrate/20151228111122_remove_public_from_namespace.rb b/db/migrate/20151228111122_remove_public_from_namespace.rb
index f4c848bbf47..bcb322d9cba 100644
--- a/db/migrate/20151228111122_remove_public_from_namespace.rb
+++ b/db/migrate/20151228111122_remove_public_from_namespace.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# Migration type: online
class RemovePublicFromNamespace < ActiveRecord::Migration
def change
diff --git a/db/migrate/20151228150906_influxdb_settings.rb b/db/migrate/20151228150906_influxdb_settings.rb
index 3012bd52cfd..2e080a02e6a 100644
--- a/db/migrate/20151228150906_influxdb_settings.rb
+++ b/db/migrate/20151228150906_influxdb_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class InfluxdbSettings < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_enabled, :boolean, default: false
diff --git a/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
index 259fd0248d2..e0dd19b2b06 100644
--- a/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
+++ b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRecaptchaToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
diff --git a/db/migrate/20151229102248_influxdb_udp_port_setting.rb b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
index ae0499f936d..3e1bfd43899 100644
--- a/db/migrate/20151229102248_influxdb_udp_port_setting.rb
+++ b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class InfluxdbUdpPortSetting < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_port, :integer, default: 8089
diff --git a/db/migrate/20151229112614_influxdb_remote_database_setting.rb b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
index f0e1ee1e7a7..d2ac906ead3 100644
--- a/db/migrate/20151229112614_influxdb_remote_database_setting.rb
+++ b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class InfluxdbRemoteDatabaseSetting < ActiveRecord::Migration
def change
remove_column :application_settings, :metrics_database
diff --git a/db/migrate/20151230132518_add_artifacts_metadata_to_ci_build.rb b/db/migrate/20151230132518_add_artifacts_metadata_to_ci_build.rb
index 6c282fc5039..4fcca06d905 100644
--- a/db/migrate/20151230132518_add_artifacts_metadata_to_ci_build.rb
+++ b/db/migrate/20151230132518_add_artifacts_metadata_to_ci_build.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddArtifactsMetadataToCiBuild < ActiveRecord::Migration
def change
add_column :ci_builds, :artifacts_metadata, :text
diff --git a/db/migrate/20151231152326_add_akismet_to_application_settings.rb b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
index 3f52c758f9a..7b0fab6f557 100644
--- a/db/migrate/20151231152326_add_akismet_to_application_settings.rb
+++ b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAkismetToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
diff --git a/db/migrate/20151231202530_remove_alert_type_from_broadcast_messages.rb b/db/migrate/20151231202530_remove_alert_type_from_broadcast_messages.rb
index 78fdfeaf5cf..0bdd639eb21 100644
--- a/db/migrate/20151231202530_remove_alert_type_from_broadcast_messages.rb
+++ b/db/migrate/20151231202530_remove_alert_type_from_broadcast_messages.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveAlertTypeFromBroadcastMessages < ActiveRecord::Migration
def change
remove_column :broadcast_messages, :alert_type, :integer
diff --git a/db/migrate/20160106162223_add_index_milestones_title.rb b/db/migrate/20160106162223_add_index_milestones_title.rb
index 767885e2aac..9b9b6445a08 100644
--- a/db/migrate/20160106162223_add_index_milestones_title.rb
+++ b/db/migrate/20160106162223_add_index_milestones_title.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexMilestonesTitle < ActiveRecord::Migration
def change
add_index :milestones, :title
diff --git a/db/migrate/20160106164438_remove_influxdb_credentials.rb b/db/migrate/20160106164438_remove_influxdb_credentials.rb
index 47e74400b97..987d75d6fda 100644
--- a/db/migrate/20160106164438_remove_influxdb_credentials.rb
+++ b/db/migrate/20160106164438_remove_influxdb_credentials.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveInfluxdbCredentials < ActiveRecord::Migration
def change
remove_column :application_settings, :metrics_username, :string
diff --git a/db/migrate/20160109054846_create_spam_logs.rb b/db/migrate/20160109054846_create_spam_logs.rb
index f12fe9f8f78..f7103276639 100644
--- a/db/migrate/20160109054846_create_spam_logs.rb
+++ b/db/migrate/20160109054846_create_spam_logs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateSpamLogs < ActiveRecord::Migration
def change
create_table :spam_logs do |t|
diff --git a/db/migrate/20160113111034_add_metrics_sample_interval.rb b/db/migrate/20160113111034_add_metrics_sample_interval.rb
index b741f5d2c75..c1041da818c 100644
--- a/db/migrate/20160113111034_add_metrics_sample_interval.rb
+++ b/db/migrate/20160113111034_add_metrics_sample_interval.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMetricsSampleInterval < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_sample_interval, :integer,
diff --git a/db/migrate/20160118155830_add_sentry_to_application_settings.rb b/db/migrate/20160118155830_add_sentry_to_application_settings.rb
index fa7ff9d9228..a6f715263ef 100644
--- a/db/migrate/20160118155830_add_sentry_to_application_settings.rb
+++ b/db/migrate/20160118155830_add_sentry_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSentryToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
diff --git a/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
index 26606b10b54..19ea40b5547 100644
--- a/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
+++ b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIpBlockingSettingsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :ip_blocking_enabled, :boolean, default: false
diff --git a/db/migrate/20160119111158_add_services_category.rb b/db/migrate/20160119111158_add_services_category.rb
index a9110a8418b..f77484b2f96 100644
--- a/db/migrate/20160119111158_add_services_category.rb
+++ b/db/migrate/20160119111158_add_services_category.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddServicesCategory < ActiveRecord::Migration
def up
add_column :services, :category, :string, default: 'common', null: false
diff --git a/db/migrate/20160119112418_add_services_default.rb b/db/migrate/20160119112418_add_services_default.rb
index 69a42d7b873..7fa531899fe 100644
--- a/db/migrate/20160119112418_add_services_default.rb
+++ b/db/migrate/20160119112418_add_services_default.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddServicesDefault < ActiveRecord::Migration
def up
add_column :services, :default, :boolean, default: false
diff --git a/db/migrate/20160119145451_add_ldap_email_to_users.rb b/db/migrate/20160119145451_add_ldap_email_to_users.rb
index 654d31ab15a..5b2b0bd31ca 100644
--- a/db/migrate/20160119145451_add_ldap_email_to_users.rb
+++ b/db/migrate/20160119145451_add_ldap_email_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddLdapEmailToUsers < ActiveRecord::Migration
def up
add_column :users, :ldap_email, :boolean, default: false, null: false
diff --git a/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb
index d6c6aa4a4e8..3837208f81e 100644
--- a/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb
+++ b/db/migrate/20160120172143_add_base_commit_sha_to_merge_request_diffs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddBaseCommitShaToMergeRequestDiffs < ActiveRecord::Migration
def change
add_column :merge_request_diffs, :base_commit_sha, :string
diff --git a/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb b/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb
index d50791410f9..9a2570ae544 100644
--- a/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb
+++ b/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEmailAuthorInBodyToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :email_author_in_body, :boolean, default: false
diff --git a/db/migrate/20160122185421_add_pending_delete_to_project.rb b/db/migrate/20160122185421_add_pending_delete_to_project.rb
index 046a5d8fc32..61db852843f 100644
--- a/db/migrate/20160122185421_add_pending_delete_to_project.rb
+++ b/db/migrate/20160122185421_add_pending_delete_to_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddPendingDeleteToProject < ActiveRecord::Migration
def change
add_column :projects, :pending_delete, :boolean, default: false
diff --git a/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
index 41821cdcc42..60ecda998dd 100644
--- a/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
+++ b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveIpBlockingSettingsFromApplicationSettings < ActiveRecord::Migration
def change
remove_column :application_settings, :ip_blocking_enabled, :boolean, default: false
diff --git a/db/migrate/20160128233227_change_lfs_objects_size_column.rb b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
index e7fd1f71777..645c0cdb192 100644
--- a/db/migrate/20160128233227_change_lfs_objects_size_column.rb
+++ b/db/migrate/20160128233227_change_lfs_objects_size_column.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ChangeLfsObjectsSizeColumn < ActiveRecord::Migration
def change
change_column :lfs_objects, :size, :integer, limit: 8
diff --git a/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb
index d3ea956952e..b10c0602e24 100644
--- a/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb
+++ b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
include Gitlab::ShellAdapter
diff --git a/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb b/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb
index f0d94226514..332b5a756e8 100644
--- a/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb
+++ b/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMergeCommitShaToMergeRequests < ActiveRecord::Migration
def change
add_column :merge_requests, :merge_commit_sha, :string
diff --git a/db/migrate/20160202091601_add_erasable_to_ci_build.rb b/db/migrate/20160202091601_add_erasable_to_ci_build.rb
index f9912f2274e..767ae160d08 100644
--- a/db/migrate/20160202091601_add_erasable_to_ci_build.rb
+++ b/db/migrate/20160202091601_add_erasable_to_ci_build.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddErasableToCiBuild < ActiveRecord::Migration
def change
add_reference :ci_builds, :erased_by, references: :users, index: true
diff --git a/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb b/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb
index 793984343b4..2c5cb307fad 100644
--- a/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb
+++ b/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddAllowGuestToAccessBuildsProject < ActiveRecord::Migration
def change
add_column :projects, :public_builds, :boolean, default: true, null: false
diff --git a/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb
index f996ae74dca..11b6ff31000 100644
--- a/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb
+++ b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRealSizeToMergeRequestDiffs < ActiveRecord::Migration
def change
add_column :merge_request_diffs, :real_size, :string
diff --git a/db/migrate/20160209130428_add_index_to_snippet.rb b/db/migrate/20160209130428_add_index_to_snippet.rb
index 95d5719be59..4d17c3a2917 100644
--- a/db/migrate/20160209130428_add_index_to_snippet.rb
+++ b/db/migrate/20160209130428_add_index_to_snippet.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexToSnippet < ActiveRecord::Migration
def change
add_index :snippets, :updated_at
diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb
index c3f6f3abc26..20573b01351 100644
--- a/db/migrate/20160212123307_create_tasks.rb
+++ b/db/migrate/20160212123307_create_tasks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateTasks < ActiveRecord::Migration
def change
create_table :tasks do |t|
diff --git a/db/migrate/20160217100506_add_description_to_label.rb b/db/migrate/20160217100506_add_description_to_label.rb
index eed6d1f236a..af5af167470 100644
--- a/db/migrate/20160217100506_add_description_to_label.rb
+++ b/db/migrate/20160217100506_add_description_to_label.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDescriptionToLabel < ActiveRecord::Migration
def change
add_column :labels, :description, :string
diff --git a/db/migrate/20160217174422_add_note_to_tasks.rb b/db/migrate/20160217174422_add_note_to_tasks.rb
index da5cb2e05db..a9a2b77e423 100644
--- a/db/migrate/20160217174422_add_note_to_tasks.rb
+++ b/db/migrate/20160217174422_add_note_to_tasks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNoteToTasks < ActiveRecord::Migration
def change
add_reference :tasks, :note, index: true
diff --git a/db/migrate/20160220123949_rename_tasks_to_todos.rb b/db/migrate/20160220123949_rename_tasks_to_todos.rb
index 30c10d27146..f16b37537f3 100644
--- a/db/migrate/20160220123949_rename_tasks_to_todos.rb
+++ b/db/migrate/20160220123949_rename_tasks_to_todos.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RenameTasksToTodos < ActiveRecord::Migration
def change
rename_table :tasks, :todos
diff --git a/db/migrate/20160222153918_create_appearances_ce.rb b/db/migrate/20160222153918_create_appearances_ce.rb
index bec66bcc71e..b2d5949b23f 100644
--- a/db/migrate/20160222153918_create_appearances_ce.rb
+++ b/db/migrate/20160222153918_create_appearances_ce.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateAppearancesCe < ActiveRecord::Migration
def change
unless table_exists?(:appearances)
diff --git a/db/migrate/20160223192159_add_confidential_to_issues.rb b/db/migrate/20160223192159_add_confidential_to_issues.rb
index e9d47fd589a..5b99ce30e9f 100644
--- a/db/migrate/20160223192159_add_confidential_to_issues.rb
+++ b/db/migrate/20160223192159_add_confidential_to_issues.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddConfidentialToIssues < ActiveRecord::Migration
def change
add_column :issues, :confidential, :boolean, default: false
diff --git a/db/migrate/20160225090018_add_delete_at_to_issues.rb b/db/migrate/20160225090018_add_delete_at_to_issues.rb
index 3ddbef92978..139f911e1c9 100644
--- a/db/migrate/20160225090018_add_delete_at_to_issues.rb
+++ b/db/migrate/20160225090018_add_delete_at_to_issues.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDeleteAtToIssues < ActiveRecord::Migration
def change
add_column :issues, :deleted_at, :datetime
diff --git a/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
index 9d09105f17d..4ca3f0dcdc5 100644
--- a/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
+++ b/db/migrate/20160225101956_add_delete_at_to_merge_requests.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDeleteAtToMergeRequests < ActiveRecord::Migration
def change
add_column :merge_requests, :deleted_at, :datetime
diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
index d7b00e3d6ed..375e389e07a 100644
--- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
+++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTrigramIndexesForSearching < ActiveRecord::Migration
disable_ddl_transaction!
diff --git a/db/migrate/20160227120001_add_event_field_for_web_hook.rb b/db/migrate/20160227120001_add_event_field_for_web_hook.rb
index 65f2a47bb3c..89910893ee1 100644
--- a/db/migrate/20160227120001_add_event_field_for_web_hook.rb
+++ b/db/migrate/20160227120001_add_event_field_for_web_hook.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEventFieldForWebHook < ActiveRecord::Migration
def change
add_column :web_hooks, :wiki_page_events, :boolean, default: false, null: false
diff --git a/db/migrate/20160227120047_add_event_to_services.rb b/db/migrate/20160227120047_add_event_to_services.rb
index f5040d770de..fe7c54ca4eb 100644
--- a/db/migrate/20160227120047_add_event_to_services.rb
+++ b/db/migrate/20160227120047_add_event_to_services.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddEventToServices < ActiveRecord::Migration
def change
add_column :services, :wiki_page_events, :boolean, default: true
diff --git a/db/migrate/20160229193553_add_main_language_to_repository.rb b/db/migrate/20160229193553_add_main_language_to_repository.rb
index b5446c6a447..ad5167b4c93 100644
--- a/db/migrate/20160229193553_add_main_language_to_repository.rb
+++ b/db/migrate/20160229193553_add_main_language_to_repository.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMainLanguageToRepository < ActiveRecord::Migration
def change
add_column :projects, :main_language, :string
diff --git a/db/migrate/20160301124843_add_visibility_level_to_groups.rb b/db/migrate/20160301124843_add_visibility_level_to_groups.rb
index d1b921bb208..a874e6758dd 100644
--- a/db/migrate/20160301124843_add_visibility_level_to_groups.rb
+++ b/db/migrate/20160301124843_add_visibility_level_to_groups.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddVisibilityLevelToGroups < ActiveRecord::Migration
def up
add_column :namespaces, :visibility_level, :integer, null: false, default: Gitlab::VisibilityLevel::PUBLIC
diff --git a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb
index ffcd64266e3..1f400566f9f 100644
--- a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb
+++ b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImportCredentialsToProjectImportData < ActiveRecord::Migration
def change
add_column :project_import_data, :encrypted_credentials, :text
diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
index 6aed0fe03d2..ac7eac0ea7c 100644
--- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
+++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# Loops through old importer projects that kept a token/password in the import URL
# and encrypts the credentials into a separate field in project#import_data
# #down method not supported
diff --git a/db/migrate/20160305220806_remove_expires_at_from_snippets.rb b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
index fc12b5b09e6..cac78703bc2 100644
--- a/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
+++ b/db/migrate/20160305220806_remove_expires_at_from_snippets.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveExpiresAtFromSnippets < ActiveRecord::Migration
def change
remove_column :snippets, :expires_at, :datetime
diff --git a/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb
index 49e787d9a9a..10f2b8cc56a 100644
--- a/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb
+++ b/db/migrate/20160307221555_disallow_blank_line_code_on_note.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class DisallowBlankLineCodeOnNote < ActiveRecord::Migration
def up
execute("UPDATE notes SET line_code = NULL WHERE line_code = ''")
diff --git a/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
index 72b862d67d2..92c0a1e088e 100644
--- a/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
+++ b/db/migrate/20160308212903_add_default_group_visibility_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# Create visibility level field on DB
# Sets default_visibility_level to value on settings if not restricted
# If value is restricted takes higher visibility level allowed
diff --git a/db/migrate/20160309140734_fix_todos.rb b/db/migrate/20160309140734_fix_todos.rb
index ebe0fc82305..94fe1e4fdc3 100644
--- a/db/migrate/20160309140734_fix_todos.rb
+++ b/db/migrate/20160309140734_fix_todos.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class FixTodos < ActiveRecord::Migration
def up
execute <<-SQL
diff --git a/db/migrate/20160310124959_add_due_date_to_issues.rb b/db/migrate/20160310124959_add_due_date_to_issues.rb
index ec08bd9fdfa..a4eb6aaee63 100644
--- a/db/migrate/20160310124959_add_due_date_to_issues.rb
+++ b/db/migrate/20160310124959_add_due_date_to_issues.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDueDateToIssues < ActiveRecord::Migration
def change
add_column :issues, :due_date, :date
diff --git a/db/migrate/20160310185910_add_external_flag_to_users.rb b/db/migrate/20160310185910_add_external_flag_to_users.rb
index 54937f1eb71..209496dc786 100644
--- a/db/migrate/20160310185910_add_external_flag_to_users.rb
+++ b/db/migrate/20160310185910_add_external_flag_to_users.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddExternalFlagToUsers < ActiveRecord::Migration
def change
add_column :users, :external, :boolean, default: false
diff --git a/db/migrate/20160314094147_add_priority_to_label.rb b/db/migrate/20160314094147_add_priority_to_label.rb
new file mode 100644
index 00000000000..7fb23cba4c9
--- /dev/null
+++ b/db/migrate/20160314094147_add_priority_to_label.rb
@@ -0,0 +1,7 @@
+# rubocop:disable all
+class AddPriorityToLabel < ActiveRecord::Migration
+ def change
+ add_column :labels, :priority, :integer
+ add_index :labels, :priority
+ end
+end
diff --git a/db/migrate/20160314143402_projects_add_pushes_since_gc.rb b/db/migrate/20160314143402_projects_add_pushes_since_gc.rb
index 5d30a38bc99..9f8ffe073a3 100644
--- a/db/migrate/20160314143402_projects_add_pushes_since_gc.rb
+++ b/db/migrate/20160314143402_projects_add_pushes_since_gc.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ProjectsAddPushesSinceGc < ActiveRecord::Migration
def change
add_column :projects, :pushes_since_gc, :integer, default: 0
diff --git a/db/migrate/20160315135439_project_add_repository_check.rb b/db/migrate/20160315135439_project_add_repository_check.rb
index 8687d5d6296..8fe649246c7 100644
--- a/db/migrate/20160315135439_project_add_repository_check.rb
+++ b/db/migrate/20160315135439_project_add_repository_check.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ProjectAddRepositoryCheck < ActiveRecord::Migration
def change
add_column :projects, :last_repository_check_failed, :boolean
diff --git a/db/migrate/20160316123110_ci_runners_token_index.rb b/db/migrate/20160316123110_ci_runners_token_index.rb
index 67bf5b4f978..ff3d36d68ee 100644
--- a/db/migrate/20160316123110_ci_runners_token_index.rb
+++ b/db/migrate/20160316123110_ci_runners_token_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CiRunnersTokenIndex < ActiveRecord::Migration
disable_ddl_transaction!
diff --git a/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb
index 6871b3920df..65e0e61c78f 100644
--- a/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb
+++ b/db/migrate/20160316192622_change_target_id_to_null_on_todos.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class ChangeTargetIdToNullOnTodos < ActiveRecord::Migration
def change
change_column_null :todos, :target_id, true
diff --git a/db/migrate/20160316204731_add_commit_id_to_todos.rb b/db/migrate/20160316204731_add_commit_id_to_todos.rb
index ae19fdd1abd..d79858fc920 100644
--- a/db/migrate/20160316204731_add_commit_id_to_todos.rb
+++ b/db/migrate/20160316204731_add_commit_id_to_todos.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCommitIdToTodos < ActiveRecord::Migration
def change
add_column :todos, :commit_id, :string
diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb
index 461e7fb3a9b..9dde668ddff 100644
--- a/db/migrate/20160317092222_add_moved_to_to_issue.rb
+++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMovedToToIssue < ActiveRecord::Migration
def change
add_reference :issues, :moved_to, references: :issues
diff --git a/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb b/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb
index 370b339d45c..07ae7c95477 100644
--- a/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb
+++ b/db/migrate/20160320204112_index_namespaces_on_visibility_level.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class IndexNamespacesOnVisibilityLevel < ActiveRecord::Migration
def change
unless index_exists?(:namespaces, :visibility_level)
diff --git a/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
index 1fff9759d1e..a9a851cfe63 100644
--- a/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
+++ b/db/migrate/20160324020319_remove_todos_for_deleted_issues.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveTodosForDeletedIssues < ActiveRecord::Migration
def up
execute <<-SQL
diff --git a/db/migrate/20160328112808_create_notification_settings.rb b/db/migrate/20160328112808_create_notification_settings.rb
index 4755da8b806..7d77e8004ba 100644
--- a/db/migrate/20160328112808_create_notification_settings.rb
+++ b/db/migrate/20160328112808_create_notification_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class CreateNotificationSettings < ActiveRecord::Migration
def change
create_table :notification_settings do |t|
diff --git a/db/migrate/20160328115649_migrate_new_notification_setting.rb b/db/migrate/20160328115649_migrate_new_notification_setting.rb
index 3c81b2c37bf..eb6b7d07219 100644
--- a/db/migrate/20160328115649_migrate_new_notification_setting.rb
+++ b/db/migrate/20160328115649_migrate_new_notification_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This migration will create one row of NotificationSetting for each Member row
# It can take long time on big instances.
#
diff --git a/db/migrate/20160328121138_add_notification_setting_index.rb b/db/migrate/20160328121138_add_notification_setting_index.rb
index 8aebce0244d..667270d6b04 100644
--- a/db/migrate/20160328121138_add_notification_setting_index.rb
+++ b/db/migrate/20160328121138_add_notification_setting_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddNotificationSettingIndex < ActiveRecord::Migration
def change
add_index :notification_settings, :user_id
diff --git a/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
index 275554e736e..a3df8fb4e2e 100644
--- a/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
+++ b/db/migrate/20160329144452_add_index_on_pending_delete_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddIndexOnPendingDeleteProjects < ActiveRecord::Migration
def change
add_index :projects, :pending_delete
diff --git a/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
index 54cea964ff2..b15af79b9b5 100644
--- a/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
+++ b/db/migrate/20160331133914_remove_todos_for_deleted_merge_requests.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveTodosForDeletedMergeRequests < ActiveRecord::Migration
def up
execute <<-SQL
diff --git a/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
index 0d736e323b6..dec80497fb3 100644
--- a/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
+++ b/db/migrate/20160331223143_remove_twitter_sharing_enabled_from_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveTwitterSharingEnabledFromApplicationSettings < ActiveRecord::Migration
def change
remove_column :application_settings, :twitter_sharing_enabled, :boolean
diff --git a/db/migrate/20160407120251_add_images_enabled_for_project.rb b/db/migrate/20160407120251_add_images_enabled_for_project.rb
index 47f0ca8e8de..fcffc98b47a 100644
--- a/db/migrate/20160407120251_add_images_enabled_for_project.rb
+++ b/db/migrate/20160407120251_add_images_enabled_for_project.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddImagesEnabledForProject < ActiveRecord::Migration
def change
add_column :projects, :container_registry_enabled, :boolean
diff --git a/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb b/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb
index ebfa4bcbc7b..920d4d41110 100644
--- a/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb
+++ b/db/migrate/20160412140240_add_repository_checks_enabled_setting.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRepositoryChecksEnabledSetting < ActiveRecord::Migration
def change
add_column :application_settings, :repository_checks_enabled, :boolean, default: true
diff --git a/db/migrate/20160412173416_add_fields_to_ci_commit.rb b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
index 125956a3ddd..00162af5cda 100644
--- a/db/migrate/20160412173416_add_fields_to_ci_commit.rb
+++ b/db/migrate/20160412173416_add_fields_to_ci_commit.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddFieldsToCiCommit < ActiveRecord::Migration
def change
add_column :ci_commits, :status, :string
diff --git a/db/migrate/20160412173417_update_ci_commit.rb b/db/migrate/20160412173417_update_ci_commit.rb
index fd92444dbac..858faeb060e 100644
--- a/db/migrate/20160412173417_update_ci_commit.rb
+++ b/db/migrate/20160412173417_update_ci_commit.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
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.
diff --git a/db/migrate/20160412173418_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb
index 603d4a41610..414f1f8279f 100644
--- a/db/migrate/20160412173418_add_ci_commit_indexes.rb
+++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddCiCommitIndexes < ActiveRecord::Migration
disable_ddl_transaction!
diff --git a/db/migrate/20160413115152_add_token_to_web_hooks.rb b/db/migrate/20160413115152_add_token_to_web_hooks.rb
index f04225068cd..628b1d51b30 100644
--- a/db/migrate/20160413115152_add_token_to_web_hooks.rb
+++ b/db/migrate/20160413115152_add_token_to_web_hooks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTokenToWebHooks < ActiveRecord::Migration
def change
add_column :web_hooks, :token, :string
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
index d493044c67b..b53b9bc6c3d 100644
--- a/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb
+++ b/db/migrate/20160415133440_add_shared_runners_text_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :shared_runners_text, :text
diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb
new file mode 100644
index 00000000000..a3bee9b1bc6
--- /dev/null
+++ b/db/migrate/20160416180807_add_award_emoji.rb
@@ -0,0 +1,15 @@
+# rubocop:disable all
+class AddAwardEmoji < ActiveRecord::Migration
+ def change
+ create_table :award_emoji do |t|
+ t.string :name
+ t.references :user
+ t.references :awardable, polymorphic: true
+
+ t.timestamps
+ end
+
+ add_index :award_emoji, :user_id
+ add_index :award_emoji, [:awardable_type, :awardable_id]
+ end
+end
diff --git a/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb
new file mode 100644
index 00000000000..c226bc11f6c
--- /dev/null
+++ b/db/migrate/20160416182152_convert_award_note_to_emoji_award.rb
@@ -0,0 +1,10 @@
+# rubocop:disable all
+class ConvertAwardNoteToEmojiAward < ActiveRecord::Migration
+ def change
+ def up
+ execute "INSERT INTO award_emoji (awardable_type, awardable_id, user_id, name, created_at, updated_at) (SELECT noteable_type, noteable_id, author_id, note, created_at, updated_at FROM notes WHERE is_award = true)"
+
+ execute "DELETE FROM notes WHERE is_award = true"
+ end
+ end
+end
diff --git a/db/migrate/20160416190505_remove_note_is_award.rb b/db/migrate/20160416190505_remove_note_is_award.rb
new file mode 100644
index 00000000000..dd24917feb9
--- /dev/null
+++ b/db/migrate/20160416190505_remove_note_is_award.rb
@@ -0,0 +1,6 @@
+# rubocop:disable all
+class RemoveNoteIsAward < ActiveRecord::Migration
+ def change
+ remove_column :notes, :is_award, :boolean
+ end
+end
diff --git a/db/migrate/20160419120017_add_metrics_packet_size.rb b/db/migrate/20160419120017_add_metrics_packet_size.rb
index 78c163d62ac..c759427c590 100644
--- a/db/migrate/20160419120017_add_metrics_packet_size.rb
+++ b/db/migrate/20160419120017_add_metrics_packet_size.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddMetricsPacketSize < ActiveRecord::Migration
def change
add_column :application_settings, :metrics_packet_size, :integer, default: 1
diff --git a/db/migrate/20160419122101_add_only_allow_merge_if_build_succeeds_to_projects.rb b/db/migrate/20160419122101_add_only_allow_merge_if_build_succeeds_to_projects.rb
new file mode 100644
index 00000000000..69d64ccd006
--- /dev/null
+++ b/db/migrate/20160419122101_add_only_allow_merge_if_build_succeeds_to_projects.rb
@@ -0,0 +1,15 @@
+class AddOnlyAllowMergeIfBuildSucceedsToProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:projects,
+ :only_allow_merge_if_build_succeeds,
+ :boolean,
+ default: false)
+ end
+
+ def down
+ remove_column(:projects, :only_allow_merge_if_build_succeeds)
+ end
+end
diff --git a/db/migrate/20160421130527_disable_repository_checks.rb b/db/migrate/20160421130527_disable_repository_checks.rb
index 808a4b93c7c..7e65ddc45e7 100644
--- a/db/migrate/20160421130527_disable_repository_checks.rb
+++ b/db/migrate/20160421130527_disable_repository_checks.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class DisableRepositoryChecks < ActiveRecord::Migration
def up
change_column_default :application_settings, :repository_checks_enabled, false
diff --git a/db/migrate/20160425045124_create_u2f_registrations.rb b/db/migrate/20160425045124_create_u2f_registrations.rb
new file mode 100644
index 00000000000..72cbe98ebba
--- /dev/null
+++ b/db/migrate/20160425045124_create_u2f_registrations.rb
@@ -0,0 +1,14 @@
+# rubocop:disable all
+class CreateU2fRegistrations < ActiveRecord::Migration
+ def change
+ create_table :u2f_registrations do |t|
+ t.text :certificate
+ t.string :key_handle, index: true
+ t.string :public_key
+ t.integer :counter
+ t.references :user, index: true, foreign_key: true
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
index facd33875ba..bf50616656c 100644
--- a/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
+++ b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddDisabledOauthSignInSourcesToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :disabled_oauth_sign_in_sources, :text
diff --git a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb
index 84e5e4eabe2..c60892a6279 100644
--- a/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb
+++ b/db/migrate/20160504112519_add_run_untagged_to_ci_runner.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddRunUntaggedToCiRunner < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
diff --git a/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb
index aa560bc0f0c..6792ffc957a 100644
--- a/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb
+++ b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveWallEnabledFromProjects < ActiveRecord::Migration
def change
remove_column :projects, :wall_enabled, :boolean, default: true, null: false
diff --git a/db/migrate/20160508215820_add_type_to_notes.rb b/db/migrate/20160508215820_add_type_to_notes.rb
index 58944d4e651..c1d07c9363f 100644
--- a/db/migrate/20160508215820_add_type_to_notes.rb
+++ b/db/migrate/20160508215820_add_type_to_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddTypeToNotes < ActiveRecord::Migration
def change
add_column :notes, :type, :string
diff --git a/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
index c3f23d89d5a..6dd958ff4a0 100644
--- a/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
+++ b/db/migrate/20160508221410_set_type_on_legacy_diff_notes.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class SetTypeOnLegacyDiffNotes < ActiveRecord::Migration
def change
execute "UPDATE notes SET type = 'LegacyDiffNote' WHERE line_code IS NOT NULL"
diff --git a/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
index 9d729fec189..b6a5bea79b6 100644
--- a/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
+++ b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddHealthCheckAccessTokenToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :health_check_access_token, :string
diff --git a/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
index c34e7ba5409..8c96353b850 100644
--- a/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
+++ b/db/migrate/20160516174813_add_send_user_confirmation_email_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddSendUserConfirmationEmailToApplicationSettings < ActiveRecord::Migration
def up
add_column :application_settings, :send_user_confirmation_email, :boolean, default: false
diff --git a/db/migrate/20160525205328_remove_main_language_from_projects.rb b/db/migrate/20160525205328_remove_main_language_from_projects.rb
index 0f9d60c385f..dc4ceacddb1 100644
--- a/db/migrate/20160525205328_remove_main_language_from_projects.rb
+++ b/db/migrate/20160525205328_remove_main_language_from_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
diff --git a/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb b/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb
index 7910120b4e0..3e26be7c09c 100644
--- a/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb
+++ b/db/migrate/20160527020117_remove_notification_settings_for_deleted_projects.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class RemoveNotificationSettingsForDeletedProjects < ActiveRecord::Migration
def up
execute <<-SQL
diff --git a/db/migrate/20160528043124_add_users_state_index.rb b/db/migrate/20160528043124_add_users_state_index.rb
index e77a5460737..6419d2ae71d 100644
--- a/db/migrate/20160528043124_add_users_state_index.rb
+++ b/db/migrate/20160528043124_add_users_state_index.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class AddUsersStateIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb b/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb
index e21376bd571..d811fd5271e 100644
--- a/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb
+++ b/db/migrate/20160530150109_add_container_registry_token_expire_delay_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
# This is ONLINE migration
class AddContainerRegistryTokenExpireDelayToApplicationSettings < ActiveRecord::Migration
diff --git a/db/migrate/20160603075128_add_has_external_issue_tracker_to_projects.rb b/db/migrate/20160603075128_add_has_external_issue_tracker_to_projects.rb
new file mode 100644
index 00000000000..be295f0181d
--- /dev/null
+++ b/db/migrate/20160603075128_add_has_external_issue_tracker_to_projects.rb
@@ -0,0 +1,10 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddHasExternalIssueTrackerToProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_column(:projects, :has_external_issue_tracker, :boolean)
+ end
+end
diff --git a/db/migrate/20160603180330_remove_duplicated_notification_settings.rb b/db/migrate/20160603180330_remove_duplicated_notification_settings.rb
new file mode 100644
index 00000000000..4f4f58b1619
--- /dev/null
+++ b/db/migrate/20160603180330_remove_duplicated_notification_settings.rb
@@ -0,0 +1,33 @@
+# rubocop:disable all
+class RemoveDuplicatedNotificationSettings < ActiveRecord::Migration
+ def up
+ duplicates = exec_query(%Q{
+ SELECT user_id, source_type, source_id
+ FROM notification_settings
+ GROUP BY user_id, source_type, source_id
+ HAVING COUNT(*) > 1
+ })
+
+ duplicates.each do |row|
+ uid = row['user_id']
+ stype = connection.quote(row['source_type'])
+ sid = row['source_id']
+
+ execute(%Q{
+ DELETE FROM notification_settings
+ WHERE user_id = #{uid}
+ AND source_type = #{stype}
+ AND source_id = #{sid}
+ AND id != (
+ SELECT id FROM (
+ SELECT min(id) AS id
+ FROM notification_settings
+ WHERE user_id = #{uid}
+ AND source_type = #{stype}
+ AND source_id = #{sid}
+ ) min_ids
+ )
+ })
+ end
+ end
+end
diff --git a/db/migrate/20160603182247_add_index_to_notification_settings.rb b/db/migrate/20160603182247_add_index_to_notification_settings.rb
new file mode 100644
index 00000000000..f6ae26d555f
--- /dev/null
+++ b/db/migrate/20160603182247_add_index_to_notification_settings.rb
@@ -0,0 +1,10 @@
+# rubocop:disable all
+class AddIndexToNotificationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :notification_settings, [:user_id, :source_id, :source_type], { unique: true, name: "index_notifications_on_user_id_and_source_id_and_source_type" }
+ end
+end
diff --git a/db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb b/db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb
new file mode 100644
index 00000000000..3c5d2ad910e
--- /dev/null
+++ b/db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb
@@ -0,0 +1,6 @@
+# rubocop:disable all
+class AddAfterSignUpTextToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :after_sign_up_text, :text
+ end
+end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
index 14d7e84d856..be3501c4c2e 100644
--- a/db/migrate/limits_to_mysql.rb
+++ b/db/migrate/limits_to_mysql.rb
@@ -1,3 +1,4 @@
+# rubocop:disable all
class LimitsToMysql < ActiveRecord::Migration
def up
return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
diff --git a/db/schema.rb b/db/schema.rb
index b2af810f600..aac327797e7 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: 20160530150109) do
+ActiveRecord::Schema.define(version: 20160608155312) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -84,6 +84,7 @@ ActiveRecord::Schema.define(version: 20160530150109) do
t.string "health_check_access_token"
t.boolean "send_user_confirmation_email", default: false
t.integer "container_registry_token_expire_delay", default: 5
+ t.text "after_sign_up_text"
end
create_table "audit_events", force: :cascade do |t|
@@ -100,6 +101,18 @@ ActiveRecord::Schema.define(version: 20160530150109) do
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
+ create_table "award_emoji", force: :cascade do |t|
+ t.string "name"
+ t.integer "user_id"
+ t.integer "awardable_id"
+ t.string "awardable_type"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
+ add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree
+
create_table "broadcast_messages", force: :cascade do |t|
t.text "message", null: false
t.datetime "starts_at"
@@ -485,8 +498,10 @@ ActiveRecord::Schema.define(version: 20160530150109) do
t.datetime "updated_at"
t.boolean "template", default: false
t.string "description"
+ t.integer "priority"
end
+ add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
create_table "lfs_objects", force: :cascade do |t|
@@ -638,7 +653,6 @@ ActiveRecord::Schema.define(version: 20160530150109) 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
t.string "type"
end
@@ -646,7 +660,6 @@ ActiveRecord::Schema.define(version: 20160530150109) do
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
@@ -665,6 +678,7 @@ ActiveRecord::Schema.define(version: 20160530150109) do
end
add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
+ add_index "notification_settings", ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree
add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree
create_table "oauth_access_grants", force: :cascade do |t|
@@ -733,38 +747,40 @@ ActiveRecord::Schema.define(version: 20160530150109) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
- t.boolean "issues_enabled", default: true, null: false
- t.boolean "merge_requests_enabled", default: true, null: false
- t.boolean "wiki_enabled", default: true, null: false
+ t.boolean "issues_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"
- t.string "issues_tracker", default: "gitlab", null: false
+ t.string "issues_tracker", default: "gitlab", null: false
t.string "issues_tracker_id"
- t.boolean "snippets_enabled", default: true, null: false
+ t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at"
t.string "import_url"
- t.integer "visibility_level", default: 0, null: false
- t.boolean "archived", default: false, null: false
+ t.integer "visibility_level", default: 0, null: false
+ t.boolean "archived", default: false, null: false
t.string "avatar"
t.string "import_status"
- t.float "repository_size", default: 0.0
- t.integer "star_count", default: 0, null: false
+ t.float "repository_size", default: 0.0
+ t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
- t.integer "commit_count", default: 0
+ t.integer "commit_count", default: 0
t.text "import_error"
t.integer "ci_id"
- t.boolean "builds_enabled", default: true, null: false
- t.boolean "shared_runners_enabled", default: true, null: false
+ t.boolean "builds_enabled", default: true, null: false
+ t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token"
t.string "build_coverage_regex"
- t.boolean "build_allow_git_fetch", default: true, null: false
- t.integer "build_timeout", default: 3600, null: false
- t.boolean "pending_delete", default: false
- t.boolean "public_builds", default: true, null: false
- t.integer "pushes_since_gc", default: 0
+ t.boolean "build_allow_git_fetch", default: true, null: false
+ t.integer "build_timeout", default: 3600, null: false
+ t.boolean "pending_delete", default: false
+ t.boolean "public_builds", default: true, null: false
+ t.integer "pushes_since_gc", default: 0
t.boolean "last_repository_check_failed"
t.datetime "last_repository_check_at"
t.boolean "container_registry_enabled"
+ t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false
+ t.boolean "has_external_issue_tracker"
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -930,6 +946,19 @@ ActiveRecord::Schema.define(version: 20160530150109) do
add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
+ create_table "u2f_registrations", force: :cascade do |t|
+ t.text "certificate"
+ t.string "key_handle"
+ t.string "public_key"
+ t.integer "counter"
+ t.integer "user_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
+ add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
@@ -1037,4 +1066,5 @@ ActiveRecord::Schema.define(version: 20160530150109) do
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
+ add_foreign_key "u2f_registrations", "users"
end
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index caf9a5bef2c..7870669fa77 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -62,7 +62,7 @@ registry:
host: registry.gitlab.example.com
port: 5005
api_url: http://localhost:5000/
- key_path: config/registry.key
+ key: config/registry.key
path: shared/registry
issuer: gitlab-issuer
```
@@ -75,7 +75,7 @@ where:
| `host` | The host URL under which the Registry will run and the users will be able to use. |
| `port` | The port under which the external Registry domain will listen on. |
| `api_url` | The internal API URL under which the Registry is exposed to. It defaults to `http://localhost:5000`. |
-| `key_path`| The private key location that is a pair of Registry's `rootcertbundle`. Read the [token auth configuration documentation][token-config]. |
+| `key` | The private key location that is a pair of Registry's `rootcertbundle`. Read the [token auth configuration documentation][token-config]. |
| `path` | This should be the same directory like specified in Registry's `rootdirectory`. Read the [storage configuration documentation][storage-config]. This path needs to be readable by the GitLab user, the web-server user and the Registry user. Read more in [#container-registry-storage-path](#container-registry-storage-path). |
| `issuer` | This should be the same value as configured in Registry's `issuer`. Read the [token auth configuration documentation][token-config]. |
diff --git a/doc/api/builds.md b/doc/api/builds.md
index 4c0a47d1ea0..5669bd0cdda 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -278,6 +278,30 @@ Response:
[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
+## Get a trace file
+
+Get a trace of a specific build of a project
+
+```
+GET /projects/:id/builds/:build_id/trace
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| id | integer | yes | The ID of a project |
+| build_id | integer | yes | The ID of a build |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/trace"
+```
+
+Response:
+
+| Status | Description |
+|-----------|-----------------------------------|
+| 200 | Serves the trace file |
+| 404 | Build not found or no trace file |
+
## Cancel a build
Cancel a single build of a project
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 8217e30fe25..16b892dc3b7 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -413,11 +413,13 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c
Merge changes submitted with MR using this API.
-If merge success you get `200 OK`.
+If the merge succeeds you'll get a `200 OK`.
-If it has some conflicts and can not be merged - you get 405 and error message 'Branch cannot be merged'
+If it has some conflicts and can not be merged - you'll get a 405 and the error message 'Branch cannot be merged'
-If merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
+If merge request is already merged or closed - you'll get a 406 and the error message 'Method Not Allowed'
+
+If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a 409 and the error message 'SHA does not match HEAD of source branch'
If you don't have permissions to accept this merge request - you'll get a 401
@@ -431,7 +433,8 @@ Parameters:
- `merge_request_id` (required) - ID of MR
- `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch
-- `merged_when_build_succeeds` (optional) - if `true` the MR is merge when the build succeeds
+- `merged_when_build_succeeds` (optional) - if `true` the MR is merged when the build succeeds
+- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
```json
{
diff --git a/doc/development/README.md b/doc/development/README.md
index aa7d54c01d0..c5d5af43864 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -7,6 +7,7 @@
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
+- [Licensing](licensing.md) for ensuring license compliance
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
new file mode 100644
index 00000000000..8c8c7486fff
--- /dev/null
+++ b/doc/development/licensing.md
@@ -0,0 +1,93 @@
+# GitLab Licensing and Compatibility
+
+GitLab CE is licensed under the terms of the MIT License. GitLab EE is licensed under "The GitLab Enterprise Edition (EE) license" wherein there are more restrictions. See their respective LICENSE files ([CE][CE], [EE][EE]) for more information.
+
+## Automated Testing
+
+In order to comply with the terms the libraries we use are licensed under, we have to make sure to check new gems for compatible licenses whenever they're added. To automate this process, we use the [license_finder][license_finder] gem by Pivotal. It runs every time a new commit is pushed and verifies that all gems in the bundle use a license that doesn't conflict with the licensing of either GitLab Community Edition or GitLab Enterprise Edition.
+
+There are some limitations with the automated testing, however. CSS and JavaScript libraries, as well as any Ruby libraries not included by way of Bundler, must be verified manually and independently. Take care whenever one such library is used, as automated tests won't catch problematic licenses from them.
+
+Some gems may not include their license information in their `gemspec` file. These won't be detected by License Finder, and will have to be verified manually.
+
+### License Finder commands
+
+There are a few basic commands License Finder provides that you'll need in order to manage license detection.
+
+To verify that the checks are passing, and/or to see what dependencies are causing the checks to fail:
+
+```
+bundle exec license_finder
+```
+
+To whitelist a new license:
+
+```
+license_finder whitelist add MIT
+```
+
+To blacklist a new license:
+
+```
+license_finder blacklist add GPLv2
+```
+
+To tell License Finder about a dependency's license if it isn't auto-detected:
+
+```
+license_finder licenses add my_unknown_dependency MIT
+```
+
+For all of the above, please include `--why "Reason"` and `--who "My Name"` so the `decisions.yml` file can keep track of when, why, and who approved of a dependency.
+
+More detailed information on how the gem and its commands work is available in the [License Finder README][license_finder].
+
+## Acceptable Licenses
+
+Libraries with the following licenses are acceptable for use:
+
+- [The MIT License][MIT] (the MIT Expat License specifically): The MIT License requires that the license itself is included with all copies of the source. It is a permissive (non-copyleft) license as defined by the Open Source Initiative.
+- [LGPL][LGPL] (version 2, version 3): GPL constraints regarding modification and redistribution under the same license are not required of projects using an LGPL library, only upon modification of the LGPL-licensed library itself.
+- [Apache 2.0 License][apache-2]: A permissive license that also provides an express grant of patent rights from contributors to users.
+- [Ruby 1.8 License][ruby-1.8]: Dual-licensed under either itself or the GPLv2, defer to the Ruby License itself. Acceptable because of point 3b: "You may distribute the software in object code or binary form, provided that you do at least ONE of the following: b) accompany the distribution with the machine-readable source of the software."
+- [Ruby 1.9 License][ruby-1.9]: Dual-licensed under either itself or the BSD 2-Clause License, defer to BSD 2-Clause.
+- [BSD 2-Clause License][BSD-2-Clause]: A permissive (non-copyleft) license as defined by the Open Source Initiative.
+- [BSD 3-Clause License][BSD-3-Clause] (also known as New BSD or Modified BSD): A permissive (non-copyleft) license as defined by the Open Source Initiative
+- [ISC License][ISC] (also known as the OpenBSD License): A permissive (non-copyleft) license as defined by the Open Source Initiative.
+
+## Unacceptable Licenses
+
+Libraries with the following licenses are unacceptable for use:
+
+- [GNU GPL][GPL] (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects.
+- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
+
+## Notes
+
+Decisions regarding the GNU GPL licenses are based on information provided by [The GNU Project][GNU-GPL-FAQ], as well as [the Open Source Initiative][OSI-GPL], which both state that linking GPL libraries makes the program itself GPL.
+
+If a gem uses a license which is not listed above, open an issue and ask. If a license is not included in the "acceptable" list, operate under the assumption that it is not acceptable.
+
+Keep in mind that each license has its own restrictions (typically defined in their body text). Please make sure to comply with those restrictions at all times whenever an external library is used.
+
+Gems which are included only in the "development" or "test" groups by Bundler are exempt from license requirements, as they're not distributed for use in production.
+
+**NOTE:** This document is **not** legal advice, nor is it comprehensive. It should not be taken as such.
+
+[CE]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/LICENSE
+[EE]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/LICENSE
+[license_finder]: https://github.com/pivotal/LicenseFinder
+[MIT]: http://choosealicense.com/licenses/mit/
+[LGPL]: http://choosealicense.com/licenses/lgpl-3.0/
+[apache-2]: http://choosealicense.com/licenses/apache-2.0/
+[ruby-1.8]: https://github.com/ruby/ruby/blob/ruby_1_8_6/COPYING
+[ruby-1.9]: https://www.ruby-lang.org/en/about/license.txt
+[BSD-2-Clause]: https://opensource.org/licenses/BSD-2-Clause
+[BSD-3-Clause]: https://opensource.org/licenses/BSD-3-Clause
+[ISC]: https://opensource.org/licenses/ISC
+[GPL]: http://choosealicense.com/licenses/gpl-3.0/
+[GPLv2]: http://www.gnu.org/licenses/gpl-2.0.txt
+[GPLv3]: http://www.gnu.org/licenses/gpl-3.0.txt
+[AGPLv3]: http://choosealicense.com/licenses/agpl-3.0/
+[GNU-GPL-FAQ]: http://www.gnu.org/licenses/gpl-faq.html#IfLibraryIsGPL
+[OSI-GPL]: https://opensource.org/faq#linking-proprietary-code
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index b4dcb748351..5893b7c219e 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -33,4 +33,24 @@ be under 'Wiki' tab and so on and so forth.
We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide
part of the UI for smaller resolutions in favor of a better user experience.
However core functionality like browsing files, creating issues, writing comments, should
-be available on all resolutions. \ No newline at end of file
+be available on all resolutions.
+
+## Icons
+
+* `trash` icon for button or link that does destructive action like removing
+information from database or file system
+* `x` icon for closing/hiding UI element. For example close modal window
+* `pencil` icon for edit button or link
+* `eye` icon for subscribe action
+* `rss` for rss/atom feed
+* `plus` for link or dropdown that lead to page where you create new object (For example new issue page)
+
+
+## Buttons
+
+* Button should contain icon or text. Exceptions should be approved by UX designer.
+* Use red button for destructive actions (not revertable). For example removing issue.
+* Use green or blue button for primary action. Primary button should be only one.
+Do not use both green and blue button in one form.
+* For all other cases use default white button
+
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 1318b3d1fa5..d9290b1fa76 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -269,9 +269,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-8-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-9-stable gitlab
-**Note:** You can change `8-8-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-9-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -394,7 +394,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout v0.7.4
+ sudo -u git -H git checkout v0.7.5
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/profile/2fa_u2f_authenticate.png b/doc/profile/2fa_u2f_authenticate.png
new file mode 100644
index 00000000000..b9138ff60db
--- /dev/null
+++ b/doc/profile/2fa_u2f_authenticate.png
Binary files differ
diff --git a/doc/profile/2fa_u2f_register.png b/doc/profile/2fa_u2f_register.png
new file mode 100644
index 00000000000..15b3683ef73
--- /dev/null
+++ b/doc/profile/2fa_u2f_register.png
Binary files differ
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index a0e23c1586c..82505b13401 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -8,12 +8,27 @@ your phone.
By enabling 2FA, the only way someone other than you can log into your account
is to know your username and password *and* have access to your phone.
-#### Note
+> **Note:**
When you enable 2FA, don't forget to back up your recovery codes. For your safety, if you
lose your codes for GitLab.com, we can't disable or recover them.
+In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as
+the second factor of authentication. Once enabled, in addition to supplying your username and
+password to login, you'll be prompted to activate your U2F device (usually by pressing
+a button on it), and it will perform secure authentication on your behalf.
+
+> **Note:** Support for U2F devices was added in version 8.8
+
+The U2F workflow is only supported by Google Chrome at this point, so we _strongly_ recommend
+that you set up both methods of two-factor authentication, so you can still access your account
+from other browsers.
+
+> **Note:** GitLab officially only supports [Yubikey] U2F devices.
+
## Enabling 2FA
+### Enable 2FA via mobile application
+
**In GitLab:**
1. Log in to your GitLab account.
@@ -38,9 +53,26 @@ lose your codes for GitLab.com, we can't disable or recover them.
1. Click **Submit**.
If the pin you entered was correct, you'll see a message indicating that
-Two-factor Authentication has been enabled, and you'll be presented with a list
+Two-Factor Authentication has been enabled, and you'll be presented with a list
of recovery codes.
+### Enable 2FA via U2F device
+
+**In GitLab:**
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Enable Two-Factor Authentication**.
+1. Plug in your U2F device.
+1. Click on **Setup New U2F Device**.
+1. A light will start blinking on your device. Activate it by pressing its button.
+
+You will see a message indicating that your device was successfully set up.
+Click on **Register U2F Device** to complete the process.
+
+![Two-Factor U2F Setup](2fa_u2f_register.png)
+
## Recovery Codes
Should you ever lose access to your phone, you can use one of the ten provided
@@ -51,21 +83,39 @@ account.
If you lose the recovery codes or just want to generate new ones, you can do so
from the **Profile Settings** > **Account** page where you first enabled 2FA.
+> **Note:** Recovery codes are not generated for U2F devices.
+
## Logging in with 2FA Enabled
Logging in with 2FA enabled is only slightly different than a normal login.
Enter your username and password credentials as you normally would, and you'll
-be presented with a second prompt for an authentication code. Enter the pin from
-your phone's application or a recovery code to log in.
+be presented with a second prompt, depending on which type of 2FA you've enabled.
+
+### Log in via mobile application
+
+Enter the pin from your phone's application or a recovery code to log in.
-![Two-factor authentication on sign in](2fa_auth.png)
+![Two-Factor Authentication on sign in via OTP](2fa_auth.png)
+
+### Log in via U2F device
+
+1. Click **Login via U2F Device**
+1. A light will start blinking on your device. Activate it by pressing its button.
+
+You will see a message indicating that your device responded to the authentication request.
+Click on **Authenticate via U2F Device** to complete the process.
+
+![Two-Factor Authentication on sign in via U2F device](2fa_u2f_authenticate.png)
## Disabling 2FA
1. Log in to your GitLab account.
1. Go to your **Profile Settings**.
1. Go to **Account**.
-1. Click **Disable Two-factor Authentication**.
+1. Click **Disable**, under **Two-Factor Authentication**.
+
+This will clear all your two-factor authentication registrations, including mobile
+applications and U2F devices.
## Note to GitLab administrators
@@ -74,3 +124,4 @@ You need to take special care to that 2FA keeps working after
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://fedorahosted.org/freeotp/
+[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
diff --git a/doc/update/8.8-to-8.9.md b/doc/update/8.8-to-8.9.md
new file mode 100644
index 00000000000..f14046bb4be
--- /dev/null
+++ b/doc/update/8.8-to-8.9.md
@@ -0,0 +1,162 @@
+# From 8.8 to 8.9
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-9-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-9-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v3.0.0
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.5
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-8-stable:config/gitlab.yml.example origin/8-9-stable:config/gitlab.yml.example
+```
+
+#### Git configuration
+
+Disable `git gc --auto` because GitLab runs `git gc` for us already.
+
+```sh
+sudo -u git -H git config --global gc.auto 0
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-8-stable:lib/support/nginx/gitlab-ssl origin/8-9-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-8-stable:lib/support/nginx/gitlab origin/8-9-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/lib/support/init.d/gitlab.default.example#L37
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.8)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.7 to 8.8](8.7-to-8.8.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md
index 1b5718c91c1..d2ec56e6504 100644
--- a/doc/workflow/merge_requests.md
+++ b/doc/workflow/merge_requests.md
@@ -2,6 +2,17 @@
Merge requests allow you to exchange changes you made to source code
+## Only allow merge requests to be merged if the build succeeds
+
+You can prevent merge requests from being merged if their build did not succeed
+in the project settings page.
+
+![only_allow_merge_if_build_succeeds](merge_requests/only_allow_merge_if_build_succeeds.png)
+
+Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box.
+
+Please note that you need to have builds configured to enable this feature.
+
## Checkout merge requests locally
Locate the section for your GitLab remote in the `.git/config` file. It looks like this:
diff --git a/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png b/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png
new file mode 100644
index 00000000000..18bebf5fe92
--- /dev/null
+++ b/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png
Binary files differ
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 5125a3e5773..c4f987a7923 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -10,14 +10,9 @@ Feature: Project Active Tab
Then the active main tab should be Home
And no other main tabs should be active
- Scenario: On Project Files
+ Scenario: On Project Code
Given I visit my project's files page
- Then the active main tab should be Files
- And no other main tabs should be active
-
- Scenario: On Project Commits
- Given I visit my project's commits page
- Then the active main tab should be Commits
+ Then the active main tab should be Code
And no other main tabs should be active
Scenario: On Project Issues
@@ -64,40 +59,46 @@ Feature: Project Active Tab
And no other sub navs should be active
And the active main tab should be Settings
- # Sub Tabs: Commits
+ # Sub Tabs: Code
+
+ Scenario: On Project Code/Files
+ Given I visit my project's files page
+ Then the active sub tab should be Files
+ And no other sub tabs should be active
+ And the active main tab should be Code
- Scenario: On Project Commits/Commits
+ Scenario: On Project Code/Commits
Given I visit my project's commits page
Then the active sub tab should be Commits
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Network
+ Scenario: On Project Code/Network
Given I visit my project's network page
Then the active sub tab should be Network
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Compare
+ Scenario: On Project Code/Compare
Given I visit my project's commits page
And I click the "Compare" tab
Then the active sub tab should be Compare
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Branches
+ Scenario: On Project Code/Branches
Given I visit my project's commits page
And I click the "Branches" tab
Then the active sub tab should be Branches
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
- Scenario: On Project Commits/Tags
+ Scenario: On Project Code/Tags
Given I visit my project's commits page
And I click the "Tags" tab
Then the active sub tab should be Tags
And no other sub tabs should be active
- And the active main tab should be Commits
+ And the active main tab should be Code
Scenario: On Project Issues/Browse
Given I visit my project's issues page
@@ -106,12 +107,16 @@ Feature: Project Active Tab
Scenario: On Project Issues/Milestones
Given I visit my project's issues page
- And I click the "Milestones" tab
- Then the active main tab should be Milestones
+ And I click the "Milestones" sub tab
+ Then the active main tab should be Issues
+ Then the active sub tab should be Milestones
And no other main tabs should be active
+ And no other sub tabs should be active
Scenario: On Project Issues/Labels
Given I visit my project's issues page
- And I click the "Labels" tab
- Then the active main tab should be Labels
+ And I click the "Labels" sub tab
+ Then the active main tab should be Issues
+ Then the active sub tab should be Labels
And no other main tabs should be active
+ And no other sub tabs should be active
diff --git a/features/project/builds/summary.feature b/features/project/builds/summary.feature
index 3c029a973df..550ebccf0d7 100644
--- a/features/project/builds/summary.feature
+++ b/features/project/builds/summary.feature
@@ -24,3 +24,4 @@ Feature: Project Builds Summary
Then recent build has been erased
And recent build summary does not have artifacts widget
And recent build summary contains information saying that build has been erased
+ And the build count cache is updated
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index de7e2b37725..2259b7125c4 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -25,13 +25,6 @@ Feature: Project Issues
Scenario: I visit issue page
Given I click link "Release 0.4"
Then I should see issue "Release 0.4"
- And I should see "1 of 2" in the sidebar
-
- Scenario: I navigate between issues
- Given I click link "Release 0.4"
- Then I click link "Next" in the sidebar
- Then I should see issue "Tweet control"
- And I should see "2 of 2" in the sidebar
@javascript
Scenario: I filter by author
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index ecda4ea8240..0e97e4d5954 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -49,14 +49,12 @@ Feature: Project Merge Requests
Scenario: I visit an open merge request page
Given I click link "Bug NS-04"
Then I should see merge request "Bug NS-04"
- And I should see "1 of 1" in the sidebar
Scenario: I visit a merged merge request page
Given project "Shop" have "Feature NS-05" merged merge request
And I click link "Merged"
And I click link "Feature NS-05"
Then I should see merge request "Feature NS-05"
- And I should see "3 of 3" in the sidebar
Scenario: I close merge request page
Given I click link "Bug NS-04"
@@ -76,18 +74,6 @@ Feature: Project Merge Requests
And I submit new merge request "Wiki Feature"
Then I should see merge request "Wiki Feature"
- Scenario: I download a diff on a public merge request
- Given public project "Community"
- And "John Doe" owns public project "Community"
- And project "Community" has "Bug CO-01" open merge request with diffs inside
- Given I logout directly
- And I visit merge request page "Bug CO-01"
- And I click on "Email Patches"
- Then I should see a patch diff
- And I visit merge request page "Bug CO-01"
- And I click on "Plain Diff"
- Then I should see a patch diff
-
@javascript
Scenario: I comment on a merge request
Given I visit merge request page "Bug NS-04"
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index 10e7c234610..c73d0b32337 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -8,19 +8,21 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to files tab
Given I press "g" and "f"
- Then the active main tab should be Files
+ Then the active main tab should be Code
+ Then the active sub tab should be Files
@javascript
Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c"
- Then the active main tab should be Commits
+ Then the active main tab should be Code
+ Then the active sub tab should be Commits
@javascript
Scenario: Navigate to network tab
Given I press "g" and "n"
Then the active sub tab should be Network
- And the active main tab should be Commits
+ And the active main tab should be Code
@javascript
Scenario: Navigate to graphs tab
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index bd8a270202e..19fedfbfcdf 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -26,7 +26,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
step 'I should see todos assigned to me' do
- page.within('.nav-sidebar') { expect(page).to have_content 'Todos 4' }
expect(page).to have_content 'To do 4'
expect(page).to have_content 'Done 0'
@@ -42,7 +41,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
click_link 'Done'
end
- 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.to_reference}"
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 4a5a71e7e61..80043463188 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -63,10 +63,6 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Tags')
end
- step 'the active sub tab should be Commits' do
- ensure_active_sub_tab('Commits')
- end
-
step 'the active sub tab should be Compare' do
ensure_active_sub_tab('Compare')
end
@@ -81,14 +77,14 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues
- step 'I click the "Milestones" tab' do
- page.within('.layout-nav') do
+ step 'I click the "Milestones" sub tab' do
+ page.within('.sub-nav') do
click_link('Milestones')
end
end
- step 'I click the "Labels" tab' do
- page.within('.layout-nav') do
+ step 'I click the "Labels" sub tab' do
+ page.within('.sub-nav') do
click_link('Labels')
end
end
@@ -97,11 +93,11 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
ensure_active_sub_tab('Issues')
end
- step 'the active main tab should be Milestones' do
- ensure_active_main_tab('Milestones')
+ step 'the active sub tab should be Milestones' do
+ ensure_active_sub_tab('Milestones')
end
- step 'the active main tab should be Labels' do
- ensure_active_main_tab('Labels')
+ step 'the active sub tab should be Labels' do
+ ensure_active_sub_tab('Labels')
end
end
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 1bdb57af9d1..2876e8812e9 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -5,11 +5,11 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
include RepoHelpers
step 'I click artifacts download button' do
- page.within('.artifacts') { click_link 'Download' }
+ click_link 'Download'
end
step 'I click artifacts browse button' do
- page.within('.artifacts') { click_link 'Browse' }
+ click_link 'Browse'
end
step 'I should see content of artifacts archive' do
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index e9e2359146e..374eb0b0e07 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -36,4 +36,8 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
expect(page).to have_content 'Build has been erased'
end
end
+
+ step 'the build count cache is updated' do
+ expect(@build.project.running_or_pending_build_count).to eq @build.project.builds.running_or_pending.count(:all)
+ end
end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index e1b29f1e57a..239036e431d 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -164,12 +164,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'commit has ci status' do
@project.enable_ci
- ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
- create :ci_build, commit: ci_commit
+ pipeline = create :ci_pipeline, project: @project, sha: sample_commit.id
+ create :ci_build, pipeline: pipeline
end
step 'repository contains ".gitlab-ci.yml" file' do
- allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file).and_return(String.new)
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(String.new)
end
step 'I see commit ci info' do
diff --git a/features/steps/project/issues/filter_labels.rb b/features/steps/project/issues/filter_labels.rb
index d82c6856918..d34fa694789 100644
--- a/features/steps/project/issues/filter_labels.rb
+++ b/features/steps/project/issues/filter_labels.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
end
step 'I click link "bug"' do
- page.find('.js-label-select').click
+ page.find('.js-label-select', visible: true).click
sleep 0.5
execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()")
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 5cd431e05d5..439363e6f14 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -191,15 +191,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do
- issue = Issue.find_by(title: 'Release 0.4')
- create_list(:upvote_note, 2, project: project, noteable: issue)
- create(:downvote_note, project: project, noteable: issue)
+ awardable = Issue.find_by(title: 'Release 0.4')
+ create_list(:award_emoji, 2, awardable: awardable)
+ create(:award_emoji, :downvote, awardable: awardable)
end
step 'issue "Tweet control" have 1 upvote and 2 downvotes' do
- issue = Issue.find_by(title: 'Tweet control')
- create(:upvote_note, project: project, noteable: issue)
- create_list(:downvote_note, 2, project: project, noteable: issue)
+ awardable = Issue.find_by(title: 'Tweet control')
+ create(:award_emoji, :upvote, awardable: awardable)
+ create_list(:award_emoji, 2, awardable: awardable, name: 'thumbsdown')
end
step 'The list should be sorted by "Least popular"' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 8d87f6a7a58..2937d5d7ca8 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I remove label \'bug\'' do
page.within "#label_#{bug_label.id}" do
- click_link 'Delete'
+ first(:link, 'Delete').click
end
end
@@ -60,25 +60,25 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I should see label \'feature\'' do
- page.within '.manage-labels-list' do
+ page.within '.other-labels .manage-labels-list' do
expect(page).to have_content 'feature'
end
end
step 'I should see label \'bug\'' do
- page.within '.manage-labels-list' do
+ page.within '.other-labels .manage-labels-list' do
expect(page).to have_content 'bug'
end
end
step 'I should not see label \'bug\'' do
- page.within '.manage-labels-list' do
+ page.within '.other-labels .manage-labels-list' do
expect(page).not_to have_content 'bug'
end
end
step 'I should see label \'support\'' do
- page.within '.manage-labels-list' do
+ page.within '.other-labels .manage-labels-list' do
expect(page).to have_content 'support'
end
end
@@ -90,7 +90,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
end
step 'I should see label \'fix\'' do
- page.within '.manage-labels-list' do
+ page.within '.other-labels .manage-labels-list' do
expect(page).to have_content 'fix'
end
end
diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb
index 5bb02189021..118ffef4774 100644
--- a/features/steps/project/labels.rb
+++ b/features/steps/project/labels.rb
@@ -29,6 +29,6 @@ class Spinach::Features::Labels < Spinach::FeatureSteps
private
def subscribe_button
- first('.label-subscribe-button span')
+ first('.js-subscribe-button', visible: true)
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index b30346790eb..640f1720a6c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -179,14 +179,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'merge request "Bug NS-04" have 2 upvotes and 1 downvote' do
merge_request = MergeRequest.find_by(title: 'Bug NS-04')
- create_list(:upvote_note, 2, project: project, noteable: merge_request)
- create(:downvote_note, project: project, noteable: merge_request)
+ create_list(:award_emoji, 2, awardable: merge_request)
+ create(:award_emoji, :downvote, awardable: merge_request)
end
step 'merge request "Bug NS-06" have 1 upvote and 2 downvotes' do
- merge_request = MergeRequest.find_by(title: 'Bug NS-06')
- create(:upvote_note, project: project, noteable: merge_request)
- create_list(:downvote_note, 2, project: project, noteable: merge_request)
+ awardable = MergeRequest.find_by(title: 'Bug NS-06')
+ create(:award_emoji, awardable: awardable)
+ create_list(:award_emoji, 2, :downvote, awardable: awardable)
end
step 'The list should be sorted by "Least popular"' do
@@ -519,8 +519,8 @@ 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, ref: merge_request.source_branch
- create :ci_build, commit: ci_commit
+ pipeline = create :ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch
+ create :ci_build, pipeline: pipeline
end
step 'I should see merge request "Bug NS-05" with CI status' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index a1785311c2b..2a1a8e776f0 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -126,7 +126,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I click notifications drop down button' do
- click_link 'notifications-button'
+ find('#notifications-button').click
end
step 'I choose Mention setting' do
diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb
index 8c1d09d6cc6..47de4b91df1 100644
--- a/features/steps/project/project_find_file.rb
+++ b/features/steps/project/project_find_file.rb
@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
end
step 'I should see "find file" page' do
- ensure_active_main_tab('Files')
+ ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
step 'I fill in Find by path with "git"' do
- ensure_active_main_tab('Files')
+ ensure_active_main_tab('Code')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 9f6aed1c5b9..3cbf832c728 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -97,7 +97,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
file = Gollum::File.new(wiki.wiki)
Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file)
Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg")
- expect(page).to have_link('image', href: "image.jpg")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/image.jpg")
click_on "image"
end
@@ -113,7 +113,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I click on image link' do
- expect(page).to have_link('image', href: "image.jpg")
+ expect(page).to have_link('image', href: "#{wiki.wiki_base_path}/image.jpg")
click_on "image"
end
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index ace717b9909..4eef7aff213 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -6,7 +6,7 @@ module SharedActiveTab
end
def ensure_active_sub_tab(content)
- expect(find('div.content ul.nav-links li.active')).to have_content(content)
+ expect(find('.sub-nav li.active')).to have_content(content)
end
def ensure_active_sub_nav(content)
@@ -18,7 +18,7 @@ module SharedActiveTab
end
step 'no other sub tabs should be active' do
- expect(page).to have_selector('div.content ul.nav-links li.active', count: 1)
+ expect(page).to have_selector('.sub-nav li.active', count: 1)
end
step 'no other sub navs should be active' do
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index cf30e23b6bd..4d6b258f577 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -10,8 +10,8 @@ module SharedBuilds
end
step 'project has a recent build' do
- @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master')
- @build = create(:ci_build_with_coverage, commit: @ci_commit)
+ @pipeline = create(:ci_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
+ @build = create(:ci_build_with_coverage, pipeline: @pipeline)
end
step 'recent build is successful' do
@@ -23,7 +23,7 @@ module SharedBuilds
end
step 'project has another build that is running' do
- create(:ci_build, commit: @ci_commit, name: 'second build', status: 'running')
+ create(:ci_build, pipeline: @pipeline, name: 'second build', status: 'running')
end
step 'I visit recent build details page' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 733e80b7279..c6572cf386e 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -138,22 +138,6 @@ module SharedIssuable
end
end
- step 'I should see "1 of 1" in the sidebar' do
- expect_sidebar_content('1 of 1')
- end
-
- step 'I should see "1 of 2" in the sidebar' do
- expect_sidebar_content('1 of 2')
- end
-
- step 'I should see "2 of 2" in the sidebar' do
- expect_sidebar_content('2 of 2')
- end
-
- step 'I should see "3 of 3" in the sidebar' do
- expect_sidebar_content('3 of 3')
- end
-
step 'I click link "Next" in the sidebar' do
page.within '.issuable-sidebar' do
click_link 'Next'
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index ce9ea7ee18a..b3411c03118 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, ref: 'master', status: 'skipped'
+ create :ci_pipeline, 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/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index b209020c5a9..bfee8793301 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -8,12 +8,8 @@ module SharedProjectTab
ensure_active_main_tab('Project')
end
- step 'the active main tab should be Files' do
- ensure_active_main_tab('Files')
- end
-
- step 'the active main tab should be Commits' do
- ensure_active_main_tab('Commits')
+ step 'the active main tab should be Code' do
+ ensure_active_main_tab('Code')
end
step 'the active main tab should be Graphs' do
@@ -51,4 +47,12 @@ module SharedProjectTab
step 'the active sub tab should be Network' do
ensure_active_sub_tab('Network')
end
+
+ step 'the active sub tab should be Files' do
+ ensure_active_sub_tab('Files')
+ end
+
+ step 'the active sub tab should be Commits' do
+ ensure_active_sub_tab('Commits')
+ end
end
diff --git a/features/support/env.rb b/features/support/env.rb
index 357d164d87f..edc08cf0986 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -16,6 +16,11 @@ require_relative 'capybara'
require_relative 'db_cleaner'
require_relative 'rerun'
+if ENV['CI']
+ require 'knapsack'
+ Knapsack::Adapters::RSpecAdapter.bind
+end
+
%w(select2_helper test_env repo_helpers).each do |f|
require Rails.root.join('spec', 'support', f)
end
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index 2b104f90aa7..0ff8fa74a84 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -33,7 +33,7 @@ module API
get ':id/repository/commits/:sha/builds' do
authorize_read_builds!
- commit = user_project.ci_commits.find_by_sha(params[:sha])
+ commit = user_project.pipelines.find_by_sha(params[:sha])
return not_found! unless commit
builds = commit.builds.order('id DESC')
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 9bcd33ff19e..323a7086890 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -22,8 +22,8 @@ module API
not_found!('Commit') unless user_project.commit(params[:sha])
- ci_commits = user_project.ci_commits.where(sha: params[:sha])
- statuses = ::CommitStatus.where(commit: ci_commits)
+ pipelines = user_project.pipelines.where(sha: params[:sha])
+ statuses = ::CommitStatus.where(pipeline: pipelines)
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?
@@ -50,7 +50,7 @@ module API
commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit
- # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline)
+ # Since the CommitStatus is attached to Ci::Pipeline (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`
@@ -64,11 +64,11 @@ module API
ref = branches.first
end
- ci_commit = @project.ensure_ci_commit(commit.sha, ref)
+ pipeline = @project.ensure_pipeline(commit.sha, ref)
name = params[:name] || params[:context]
- status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
- status ||= GenericCommitStatus.new(project: @project, commit: ci_commit, user: current_user)
+ status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
+ status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user)
status.update(attrs)
case params[:state].to_s
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 790a1869f73..50d69274b2e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -30,7 +30,7 @@ module API
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
- expose :two_factor_enabled
+ expose :two_factor_enabled?, as: :two_factor_enabled
expose :external
end
@@ -171,15 +171,17 @@ module API
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
+
expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user])
end
expose :user_notes_count
+ expose :upvotes, :downvotes
end
class MergeRequest < ProjectEntity
expose :target_branch, :source_branch
- expose :upvotes, :downvotes
+ expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
@@ -217,8 +219,8 @@ module API
expose :system?, as: :system
expose :noteable_id, :noteable_type
# upvote? and downvote? are deprecated, always return false
- expose :upvote?, as: :upvote
- expose :downvote?, as: :downvote
+ expose(:upvote?) { |note| false }
+ expose(:downvote?) { |note| false }
end
class MRNote < Grape::Entity
@@ -349,6 +351,7 @@ module API
expose :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
+ expose :after_sign_up_text
expose :created_at
expose :updated_at
expose :home_page_url
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2aaa0557ea3..e1d3bbcc02d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -408,5 +408,15 @@ module API
error!(errors[:access_level], 422) if errors[:access_level].any?
not_found!(errors)
end
+
+ def send_git_blob(repository, blob)
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ header(*Gitlab::Workhorse.send_git_blob(repository, blob))
+ end
+
+ def send_git_archive(repository, ref:, format:)
+ header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
+ end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index f59a4d6c012..4c43257c48a 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -51,7 +51,7 @@ module API
# GET /issues?labels=foo,bar
# GET /issues?labels=foo,bar&state=opened
get do
- issues = current_user.issues
+ issues = current_user.issues.inc_notes_with_associations
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues.reorder(issuable_order_by => issuable_sort)
@@ -82,7 +82,7 @@ module API
# GET /projects/:id/issues?milestone=1.0.0&state=closed
# GET /issues?iid=42
get ":id/issues" do
- issues = user_project.issues.visible_to_user(current_user)
+ issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user)
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4e7de8867b4..24df3e397e0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -41,7 +41,7 @@ module API
#
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
- merge_requests = user_project.merge_requests
+ merge_requests = user_project.merge_requests.inc_notes_with_associations
unless params[:iid].nil?
merge_requests = filter_by_iid(merge_requests, params[:iid])
@@ -218,6 +218,7 @@ module API
# merge_commit_message (optional) - Custom merge commit message
# should_remove_source_branch (optional) - When true, the source branch will be deleted if possible
# merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds
+ # sha (optional) - When present, must have the HEAD SHA of the source branch
# Example:
# PUT /projects/:id/merge_requests/:merge_request_id/merge
#
@@ -227,18 +228,21 @@ module API
# Merge request can not be merged
# because user dont have permissions to push into target branch
unauthorized! unless merge_request.can_be_merged_by?(current_user)
- not_allowed! if !merge_request.open? || merge_request.work_in_progress?
- merge_request.check_if_can_be_merged
+ not_allowed! unless merge_request.mergeable_state?
- render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
+ render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?
+
+ if params[:sha] && merge_request.source_sha != params[:sha]
+ render_api_error!("SHA does not match HEAD of source branch: #{merge_request.source_sha}", 409)
+ end
merge_params = {
commit_message: params[:merge_commit_message],
should_remove_source_branch: params[:should_remove_source_branch]
}
- if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
+ if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active?
::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
execute(merge_request)
else
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 62161aadb9a..f55aceed92c 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -56,8 +56,7 @@ module API
blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath])
not_found! "File" unless blob
- content_type 'text/plain'
- header *Gitlab::Workhorse.send_git_blob(repo, blob)
+ send_git_blob repo, blob
end
# Get a raw blob contents by blob sha
@@ -80,10 +79,7 @@ module API
not_found! 'Blob' unless blob
- env['api.format'] = :txt
-
- content_type blob.mime_type
- header *Gitlab::Workhorse.send_git_blob(repo, blob)
+ send_git_blob repo, blob
end
# Get a an archive of the repository
@@ -98,7 +94,7 @@ module API
authorize! :download_code, user_project
begin
- header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format])
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue
not_found!('File')
end
diff --git a/lib/api/session.rb b/lib/api/session.rb
index cc646895914..56e69b2366f 100644
--- a/lib/api/session.rb
+++ b/lib/api/session.rb
@@ -11,8 +11,7 @@ module API
# Example Request:
# POST /session
post "/session" do
- auth = Gitlab::Auth.new
- user = auth.find(params[:email] || params[:login], params[:password])
+ user = Gitlab::Auth.find_in_gitlab_or_ldap(params[:email] || params[:login], params[:password])
return unauthorized! unless user
present user, with: Entities::UserLogin
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
deleted file mode 100644
index b1aecc2e671..00000000000
--- a/lib/award_emoji.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-class AwardEmoji
- CATEGORIES = {
- other: "Other",
- objects: "Objects",
- places: "Places",
- travel_places: "Travel",
- emoticons: "Emoticons",
- objects_symbols: "Symbols",
- nature: "Nature",
- celebration: "Celebration",
- people: "People",
- activity: "Activity",
- flags: "Flags",
- food_drink: "Food"
- }.with_indifferent_access
-
- CATEGORY_ALIASES = {
- symbols: "objects_symbols",
- foods: "food_drink",
- travel: "travel_places"
- }.with_indifferent_access
-
- def self.normilize_emoji_name(name)
- aliases[name] || name
- end
-
- def self.emoji_by_category
- unless @emoji_by_category
- @emoji_by_category = Hash.new { |h, key| h[key] = [] }
-
- emojis.each do |emoji_name, data|
- data["name"] = emoji_name
-
- # Skip Fitzpatrick(tone) modifiers
- next if data["category"] == "modifier"
-
- category = CATEGORY_ALIASES[data["category"]] || data["category"]
-
- @emoji_by_category[category] << data
- end
-
- @emoji_by_category = @emoji_by_category.sort.to_h
- end
-
- @emoji_by_category
- end
-
- def self.emojis
- @emojis ||= begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
- JSON.parse(File.read(json_path))
- end
- end
-
- def self.unicode
- @unicode ||= emojis.map {|key, value| { key => emojis[key]["unicode"] } }.inject(:merge!)
- end
-
- def self.aliases
- @aliases ||= begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
- JSON.parse(File.read(json_path))
- end
- end
-
- # Returns an Array of Emoji names and their asset URLs.
- def self.urls
- @urls ||= begin
- path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
- prefix = Gitlab::Application.config.assets.prefix
- digest = Gitlab::Application.config.assets.digest
-
- JSON.parse(File.read(path)).map do |hash|
- if digest
- fname = "#{hash['unicode']}-#{hash['digest']}"
- else
- fname = hash['unicode']
- end
-
- { name: hash['name'], path: "#{prefix}/#{fname}.png" }
- end
- end
- end
-end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 67b2a64bd10..22319ec6623 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -86,9 +86,9 @@ module Backup
def report_success(success)
if success
- $progress.puts '[DONE]'.green
+ $progress.puts '[DONE]'.color(:green)
else
- $progress.puts '[FAILED]'.red
+ $progress.puts '[FAILED]'.color(:red)
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 660ca8c2923..9dd665441a0 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -27,9 +27,9 @@ module Backup
# Set file permissions on open to prevent chmod races.
tar_system_options = {out: [tar_file, 'w', Gitlab.config.backup.archive_permissions]}
if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
else
- puts "creating archive #{tar_file} failed".red
+ puts "creating archive #{tar_file} failed".color(:red)
abort 'Backup failed'
end
@@ -43,7 +43,7 @@ module Backup
connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank?
- $progress.puts "skipped".yellow
+ $progress.puts "skipped".color(:yellow)
return
end
@@ -53,9 +53,9 @@ module Backup
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
encryption: Gitlab.config.backup.upload.encryption)
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
else
- puts "uploading backup to #{remote_directory} failed".red
+ puts "uploading backup to #{remote_directory} failed".color(:red)
abort 'Backup failed'
end
end
@@ -67,9 +67,9 @@ module Backup
next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
else
- puts "deleting tmp directory '#{dir}' failed".red
+ puts "deleting tmp directory '#{dir}' failed".color(:red)
abort 'Backup failed'
end
end
@@ -95,9 +95,9 @@ module Backup
end
end
- $progress.puts "done. (#{removed} removed)".green
+ $progress.puts "done. (#{removed} removed)".color(:green)
else
- $progress.puts "skipping".yellow
+ $progress.puts "skipping".color(:yellow)
end
end
@@ -124,20 +124,20 @@ module Backup
$progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "unpacking backup failed".red
+ puts "unpacking backup failed".color(:red)
exit 1
else
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems
if settings[:gitlab_version] != Gitlab::VERSION
- puts "GitLab version mismatch:".red
- puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".red
- puts " Please switch to the following version and try again:".red
- puts " version: #{settings[:gitlab_version]}".red
+ puts "GitLab version mismatch:".color(:red)
+ puts " Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!".color(:red)
+ puts " Please switch to the following version and try again:".color(:red)
+ puts " version: #{settings[:gitlab_version]}".color(:red)
puts
puts "Hint: git checkout v#{settings[:gitlab_version]}"
exit 1
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index a82a7e1f7bf..7b91215d50b 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -14,14 +14,14 @@ module Backup
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo?
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
cmd = %W(tar -cf #{path_to_bundle(project)} -C #{path_to_repo(project)} .)
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- $progress.puts "[DONE]".green
+ $progress.puts "[DONE]".color(:green)
else
- puts "[FAILED]".red
+ puts "[FAILED]".color(:red)
puts "failed: #{cmd.join(' ')}"
puts output
abort 'Backup failed'
@@ -33,14 +33,14 @@ module Backup
if File.exists?(path_to_repo(wiki))
$progress.print " * #{wiki.path_with_namespace} ... "
if wiki.repository.empty?
- $progress.puts " [SKIPPED]".cyan
+ $progress.puts " [SKIPPED]".color(:cyan)
else
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- $progress.puts " [DONE]".green
+ $progress.puts " [DONE]".color(:green)
else
- puts " [FAILED]".red
+ puts " [FAILED]".color(:red)
puts "failed: #{cmd.join(' ')}"
abort 'Backup failed'
end
@@ -71,9 +71,9 @@ module Backup
end
if system(*cmd, silent)
- $progress.puts "[DONE]".green
+ $progress.puts "[DONE]".color(:green)
else
- puts "[FAILED]".red
+ puts "[FAILED]".color(:red)
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
@@ -90,21 +90,21 @@ module Backup
cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
if system(*cmd, silent)
- $progress.puts " [DONE]".green
+ $progress.puts " [DONE]".color(:green)
else
- puts " [FAILED]".red
+ puts " [FAILED]".color(:red)
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
end
end
- $progress.print 'Put GitLab hooks in repositories dirs'.yellow
+ $progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow)
cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
if system(cmd)
- $progress.puts " [DONE]".green
+ $progress.puts " [DONE]".color(:green)
else
- puts " [FAILED]".red
+ puts " [FAILED]".color(:red)
puts "failed: #{cmd}"
end
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 38c4219518e..f73ecfc9418 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -15,6 +15,7 @@ module Banzai
next if link.start_with?(internal_url)
node.set_attribute('rel', 'nofollow noreferrer')
+ node.set_attribute('target', '_blank')
end
doc
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index 9e75edd4d4c..beb21b19ab3 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -8,15 +8,19 @@ module Banzai
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
content = node.to_html
- content = content.gsub(/(?:\[\-(.*?)\-\]|\{\-(.*?)\-\})/, '<span class="idiff left right deletion">\1\2</span>')
- content = content.gsub(/(?:\[\+(.*?)\+\]|\{\+(.*?)\+\})/, '<span class="idiff left right addition">\1\2</span>')
+ html_content = inline_diff_filter(content)
- next if html == content
+ next if content == html_content
- node.replace(content)
+ node.replace(html_content)
end
doc
end
+
+ def inline_diff_filter(text)
+ html_content = text.gsub(/(?:\[\-(.*?)\-\]|\{\-(.*?)\-\})/, '<span class="idiff left right deletion">\1\2</span>')
+ html_content.gsub(/(?:\[\+(.*?)\+\]|\{\+(.*?)\+\})/, '<span class="idiff left right addition">\1\2</span>')
+ end
end
end
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 41ae0e1f9cc..2d6f34c9cd8 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -68,6 +68,8 @@ module Banzai
# by `ignore_ancestor_query`. Link tags are not processed if they have a
# "gfm" class or the "href" attribute is empty.
def each_node
+ return to_enum(__method__) unless block_given?
+
query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
| descendant-or-self::a[
not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
@@ -78,6 +80,11 @@ module Banzai
end
end
+ # Returns an Array containing all HTML nodes.
+ def nodes
+ @nodes ||= each_node.to_a
+ end
+
# Yields the link's URL and text whenever the node is a valid <a> tag.
def yield_valid_link(node)
link = CGI.unescape(node.attr('href').to_s)
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 331d8007257..5b0a6d8541b 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -29,7 +29,7 @@ module Banzai
ref_pattern = User.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
- each_node do |node|
+ nodes.each do |node|
if text_node?(node)
replace_text_when_pattern_matches(node, ref_pattern) do |content|
user_link_filter(content)
@@ -59,7 +59,7 @@ module Banzai
self.class.references_in(text) do |match, username|
if username == 'all'
link_to_all(link_text: link_text)
- elsif namespace = Namespace.find_by(path: username)
+ elsif namespace = namespaces[username]
link_to_namespace(namespace, link_text: link_text) || match
else
match
@@ -67,6 +67,31 @@ module Banzai
end
end
+ # Returns a Hash containing all Namespace objects for the username
+ # references in the current document.
+ #
+ # The keys of this Hash are the namespace paths, the values the
+ # corresponding Namespace objects.
+ def namespaces
+ @namespaces ||=
+ Namespace.where(path: usernames).each_with_object({}) do |row, hash|
+ hash[row.path] = row
+ end
+ end
+
+ # Returns all usernames referenced in the current document.
+ def usernames
+ refs = Set.new
+
+ nodes.each do |node|
+ node.to_html.scan(User.reference_pattern) do
+ refs << $~[:user]
+ end
+ end
+
+ refs.to_a
+ end
+
private
def urls
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 7dc771afd71..37a2779d453 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -2,7 +2,8 @@ require 'uri'
module Banzai
module Filter
- # HTML filter that "fixes" relative links to files in a repository.
+ # HTML filter that "fixes" links to pages/files in a wiki.
+ # Rewrite rules are documented in the `WikiPipeline` spec.
#
# Context options:
# :project_wiki
@@ -25,36 +26,15 @@ module Banzai
end
def process_link_attr(html_attr)
- return if html_attr.blank? || file_reference?(html_attr) || hierarchical_link?(html_attr)
+ return if html_attr.blank?
- uri = URI(html_attr.value)
- if uri.relative? && uri.path.present?
- html_attr.value = rebuild_wiki_uri(uri).to_s
- end
+ html_attr.value = apply_rewrite_rules(html_attr.value)
rescue URI::Error
# noop
end
- def rebuild_wiki_uri(uri)
- uri.path = ::File.join(project_wiki_base_path, uri.path)
- uri
- end
-
- def project_wiki
- context[:project_wiki]
- end
-
- def file_reference?(html_attr)
- !File.extname(html_attr.value).blank?
- end
-
- # Of the form `./link`, `../link`, or similar
- def hierarchical_link?(html_attr)
- html_attr.value[0] == '.'
- end
-
- def project_wiki_base_path
- project_wiki && project_wiki.wiki_base_path
+ def apply_rewrite_rules(link_string)
+ Rewriter.new(link_string, wiki: context[:project_wiki], slug: context[:page_slug]).apply_rules
end
end
end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
new file mode 100644
index 00000000000..2e2c8da311e
--- /dev/null
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -0,0 +1,40 @@
+module Banzai
+ module Filter
+ class WikiLinkFilter < HTML::Pipeline::Filter
+ class Rewriter
+ def initialize(link_string, wiki:, slug:)
+ @uri = Addressable::URI.parse(link_string)
+ @wiki_base_path = wiki && wiki.wiki_base_path
+ @slug = slug
+ end
+
+ def apply_rules
+ apply_file_link_rules!
+ apply_hierarchical_link_rules!
+ apply_relative_link_rules!
+ @uri.to_s
+ end
+
+ private
+
+ # Of the form 'file.md'
+ def apply_file_link_rules!
+ @uri = Addressable::URI.join(@slug, @uri) if @uri.extname.present?
+ end
+
+ # Of the form `./link`, `../link`, or similar
+ def apply_hierarchical_link_rules!
+ @uri = Addressable::URI.join(@slug, @uri) if @uri.to_s[0] == '.'
+ end
+
+ # Any link _not_ of the form `http://example.com/`
+ def apply_relative_link_rules!
+ if @uri.relative? && @uri.path.present?
+ link = ::File.join(@wiki_base_path, @uri.path)
+ @uri = Addressable::URI.parse(link)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index b25e0e573a8..a902ced35d7 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -56,7 +56,7 @@ module Ci
class TriggerRequest < Grape::Entity
expose :id, :variables
- expose :commit, using: Commit
+ expose :pipeline, using: Commit, as: :commit
end
end
end
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index e1636636934..5270108ef0f 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -60,7 +60,7 @@ module Ci
class BuildTime < Chart
def collect
- commits = project.ci_commits.last(30)
+ commits = project.pipelines.last(30)
commits.each do |commit|
@labels << commit.short_sha
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 026a5ac97ca..130f5b0892e 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -12,18 +12,14 @@ module Ci
attr_reader :before_script, :after_script, :image, :services, :path, :cache
def initialize(config, path = nil)
- @config = YAML.safe_load(config, [Symbol], [], true)
+ @config = Gitlab::Ci::Config.new(config).to_hash
@path = path
- unless @config.is_a? Hash
- raise ValidationError, "YAML should be a hash"
- end
-
- @config = @config.deep_symbolize_keys
-
initial_parsing
validate!
+ rescue Gitlab::Ci::Config::Loader::FormatError => e
+ raise ValidationError, e.message
end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 30509528b8b..076e2af7d38 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,17 +1,86 @@
module Gitlab
- class Auth
- def find(login, password)
- user = User.by_login(login)
-
- # If no user is found, or it's an LDAP server, try LDAP.
- # LDAP users are only authenticated via LDAP
- if user.nil? || user.ldap_user?
- # Second chance - try LDAP authentication
- return nil unless Gitlab::LDAP::Config.enabled?
-
- Gitlab::LDAP::Authentication.login(login, password)
- else
- user if user.valid_password?(password)
+ module Auth
+ Result = Struct.new(:user, :type)
+
+ class << self
+ def find(login, password, project:, ip:)
+ raise "Must provide an IP for rate limiting" if ip.nil?
+
+ result = Result.new
+
+ if valid_ci_request?(login, password, project)
+ result.type = :ci
+ elsif result.user = find_in_gitlab_or_ldap(login, password)
+ result.type = :gitlab_or_ldap
+ elsif result.user = oauth_access_token_check(login, password)
+ result.type = :oauth
+ end
+
+ rate_limit!(ip, success: !!result.user || (result.type == :ci), login: login)
+ result
+ end
+
+ def find_in_gitlab_or_ldap(login, password)
+ user = User.by_login(login)
+
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
+ if user.nil? || user.ldap_user?
+ # Second chance - try LDAP authentication
+ return nil unless Gitlab::LDAP::Config.enabled?
+
+ Gitlab::LDAP::Authentication.login(login, password)
+ else
+ user if user.valid_password?(password)
+ end
+ end
+
+ def rate_limit!(ip, success:, login:)
+ rate_limiter = Gitlab::Auth::IpRateLimiter.new(ip)
+ return unless rate_limiter.enabled?
+
+ if success
+ # Repeated login 'failures' are normal behavior for some Git clients so
+ # it is important to reset the ban counter once the client has proven
+ # they are not a 'bad guy'.
+ rate_limiter.reset!
+ else
+ # Register a login failure so that Rack::Attack can block the next
+ # request from this IP if needed.
+ rate_limiter.register_fail!
+
+ if rate_limiter.banned?
+ Rails.logger.info "IP #{ip} failed to login " \
+ "as #{login} but has been temporarily banned from Git auth"
+ end
+ end
+ end
+
+ private
+
+ def valid_ci_request?(login, password, project)
+ matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
+
+ return false unless project && matched_login.present?
+
+ underscored_service = matched_login['service'].underscore
+
+ if underscored_service == 'gitlab_ci'
+ project && project.valid_build_token?(password)
+ elsif Service.available_services_names.include?(underscored_service)
+ # We treat underscored_service as a trusted input because it is included
+ # in the Service.available_services_names whitelist.
+ service = project.public_send("#{underscored_service}_service")
+
+ service && service.activated? && service.valid_token?(password)
+ end
+ end
+
+ def oauth_access_token_check(login, password)
+ if login == "oauth2" && password.present?
+ token = Doorkeeper::AccessToken.by_token(password)
+ token && token.accessible? && User.find_by(id: token.resource_owner_id)
+ end
end
end
end
diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb
new file mode 100644
index 00000000000..1089bc9f89e
--- /dev/null
+++ b/lib/gitlab/auth/ip_rate_limiter.rb
@@ -0,0 +1,42 @@
+module Gitlab
+ module Auth
+ class IpRateLimiter
+ attr_reader :ip
+
+ def initialize(ip)
+ @ip = ip
+ @banned = false
+ end
+
+ def enabled?
+ config.enabled
+ end
+
+ def reset!
+ Rack::Attack::Allow2Ban.reset(ip, config)
+ end
+
+ def register_fail!
+ # Allow2Ban.filter will return false if this IP has not failed too often yet
+ @banned = Rack::Attack::Allow2Ban.filter(ip, config) do
+ # If we return false here, the failure for this IP is ignored by Allow2Ban
+ ip_can_be_banned?
+ end
+ end
+
+ def banned?
+ @banned
+ end
+
+ private
+
+ def config
+ Gitlab.config.rack_attack.git_basic_auth
+ end
+
+ def ip_can_be_banned?
+ config.ip_whitelist.exclude?(ip)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
new file mode 100644
index 00000000000..51b1df9ecbd
--- /dev/null
+++ b/lib/gitlab/award_emoji.rb
@@ -0,0 +1,84 @@
+module Gitlab
+ class AwardEmoji
+ CATEGORIES = {
+ other: "Other",
+ objects: "Objects",
+ places: "Places",
+ travel_places: "Travel",
+ emoticons: "Emoticons",
+ objects_symbols: "Symbols",
+ nature: "Nature",
+ celebration: "Celebration",
+ people: "People",
+ activity: "Activity",
+ flags: "Flags",
+ food_drink: "Food"
+ }.with_indifferent_access
+
+ CATEGORY_ALIASES = {
+ symbols: "objects_symbols",
+ foods: "food_drink",
+ travel: "travel_places"
+ }.with_indifferent_access
+
+ def self.normalize_emoji_name(name)
+ aliases[name] || name
+ end
+
+ def self.emoji_by_category
+ unless @emoji_by_category
+ @emoji_by_category = Hash.new { |h, key| h[key] = [] }
+
+ emojis.each do |emoji_name, data|
+ data["name"] = emoji_name
+
+ # Skip Fitzpatrick(tone) modifiers
+ next if data["category"] == "modifier"
+
+ category = CATEGORY_ALIASES[data["category"]] || data["category"]
+
+ @emoji_by_category[category] << data
+ end
+
+ @emoji_by_category = @emoji_by_category.sort.to_h
+ end
+
+ @emoji_by_category
+ end
+
+ def self.emojis
+ @emojis ||=
+ begin
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
+ JSON.parse(File.read(json_path))
+ end
+ end
+
+ def self.aliases
+ @aliases ||=
+ begin
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
+ JSON.parse(File.read(json_path))
+ end
+ end
+
+ # Returns an Array of Emoji names and their asset URLs.
+ def self.urls
+ @urls ||= begin
+ path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
+ prefix = Gitlab::Application.config.assets.prefix
+ digest = Gitlab::Application.config.assets.digest
+
+ JSON.parse(File.read(path)).map do |hash|
+ if digest
+ fname = "#{hash['unicode']}-#{hash['digest']}"
+ else
+ fname = hash['unicode']
+ end
+
+ { name: hash['name'], path: "#{prefix}/#{fname}.png" }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index baa81d92dd9..f07ff1daf55 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -36,10 +36,7 @@ module Grack
lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
return lfs_response unless lfs_response.nil?
- if project && authorized_request?
- # Tell gitlab-workhorse the request is OK, and what the GL_ID is
- render_grack_auth_ok
- elsif @user.nil? && !@ci
+ if @user.nil? && !@ci
unauthorized
else
render_not_found
@@ -98,7 +95,7 @@ module Grack
end
def authenticate_user(login, password)
- user = Gitlab::Auth.new.find(login, password)
+ user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
unless user
user = oauth_access_token_check(login, password)
@@ -141,36 +138,6 @@ module Grack
user
end
- def authorized_request?
- return true if @ci
-
- case git_cmd
- when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
- if !Gitlab.config.gitlab_shell.upload_pack
- false
- elsif user
- Gitlab::GitAccess.new(user, project).download_access_check.allowed?
- elsif project.public?
- # Allow clone/fetch for public projects
- true
- else
- false
- end
- when *Gitlab::GitAccess::PUSH_COMMANDS
- if !Gitlab.config.gitlab_shell.receive_pack
- false
- elsif user
- # Skip user authorization on upload request.
- # It will be done by the pre-receive hook in the repository.
- true
- else
- false
- end
- else
- false
- end
- end
-
def git_cmd
if @request.get?
@request.params['service']
@@ -197,24 +164,6 @@ module Grack
end
end
- def render_grack_auth_ok
- repo_path =
- if @request.path_info =~ /^([\w\.\/-]+)\.wiki\.git/
- ProjectWiki.new(project).repository.path_to_repo
- else
- project.repository.path_to_repo
- end
-
- [
- 200,
- { "Content-Type" => "application/json" },
- [JSON.dump({
- 'GL_ID' => Gitlab::ShellEnv.gl_id(@user),
- 'RepoPath' => repo_path,
- })]
- ]
- end
-
def render_not_found
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
end
diff --git a/lib/gitlab/build_data_builder.rb b/lib/gitlab/build_data_builder.rb
index 34e949130da..9f45aefda0f 100644
--- a/lib/gitlab/build_data_builder.rb
+++ b/lib/gitlab/build_data_builder.rb
@@ -3,7 +3,7 @@ module Gitlab
class << self
def build(build)
project = build.project
- commit = build.commit
+ commit = build.pipeline
user = build.user
data = {
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
new file mode 100644
index 00000000000..ffe633d4b63
--- /dev/null
+++ b/lib/gitlab/ci/config.rb
@@ -0,0 +1,16 @@
+module Gitlab
+ module Ci
+ class Config
+ class LoaderError < StandardError; end
+
+ def initialize(config)
+ loader = Loader.new(config)
+ @config = loader.load!
+ end
+
+ def to_hash
+ @config
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/loader.rb b/lib/gitlab/ci/config/loader.rb
new file mode 100644
index 00000000000..dbf6eb0edbe
--- /dev/null
+++ b/lib/gitlab/ci/config/loader.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Ci
+ class Config
+ class Loader
+ class FormatError < StandardError; end
+
+ def initialize(config)
+ @config = YAML.safe_load(config, [Symbol], [], true)
+ end
+
+ def valid?
+ @config.is_a?(Hash)
+ end
+
+ def load!
+ unless valid?
+ raise FormatError, 'Invalid configuration format'
+ end
+
+ @config.deep_symbolize_keys
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 92c7e8b9d88..5e7532f57ae 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -26,7 +26,10 @@ module Gitlab
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
- sign_in_text: Settings.extra['sign_in_text'],
+ sign_in_text: nil,
+ after_sign_up_text: nil,
+ help_page_text: nil,
+ shared_runners_text: nil,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 42bec913a45..04fa6a3a5de 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -16,6 +16,20 @@ module Gitlab
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
+ def self.nulls_last_order(field, direction = 'ASC')
+ order = "#{field} #{direction}"
+
+ if Gitlab::Database.postgresql?
+ order << ' NULLS LAST'
+ else
+ # `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
+ # columns. In the (default) ascending order, `0` comes first.
+ order.prepend("#{field} IS NULL, ") if direction == 'ASC'
+ end
+
+ order
+ end
+
def true_value
if Gitlab::Database.postgresql?
"'t'"
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index fd14234c558..978c3f7896d 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -11,7 +11,7 @@ module Gitlab
# add_concurrent_index :users, :some_column
#
# See Rails' `add_index` for more info on the available arguments.
- def add_concurrent_index(*args)
+ def add_concurrent_index(table_name, column_name, options = {})
if transaction_open?
raise 'add_concurrent_index can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \
@@ -19,10 +19,10 @@ module Gitlab
end
if Database.postgresql?
- args << { algorithm: :concurrently }
+ options = options.merge({ algorithm: :concurrently })
end
- add_index(*args)
+ add_index(table_name, column_name, options)
end
# Updates the value of a column in batches.
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
index 202263c6742..72992baffd4 100644
--- a/lib/gitlab/github_import/base_formatter.rb
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -9,6 +9,10 @@ module Gitlab
@formatter = Gitlab::ImportFormatter.new
end
+ def create!
+ self.klass.create!(self.attributes)
+ end
+
private
def gl_user_id(github_id)
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index 67988ea3460..d325eca6d99 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -1,6 +1,9 @@
module Gitlab
module GithubImport
class Client
+ GITHUB_SAFE_REMAINING_REQUESTS = 100
+ GITHUB_SAFE_SLEEP_TIME = 500
+
attr_reader :client, :api
def initialize(access_token)
@@ -11,7 +14,7 @@ module Gitlab
)
if access_token
- ::Octokit.auto_paginate = true
+ ::Octokit.auto_paginate = false
@api = ::Octokit::Client.new(
access_token: access_token,
@@ -36,7 +39,7 @@ module Gitlab
def method_missing(method, *args, &block)
if api.respond_to?(method)
- api.send(method, *args, &block)
+ request { api.send(method, *args, &block) }
else
super(method, *args, &block)
end
@@ -55,6 +58,34 @@ module Gitlab
def github_options
config["args"]["client_options"].deep_symbolize_keys
end
+
+ def rate_limit
+ api.rate_limit!
+ end
+
+ def rate_limit_exceed?
+ rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
+ end
+
+ def rate_limit_sleep_time
+ rate_limit.resets_in + GITHUB_SAFE_SLEEP_TIME
+ end
+
+ def request
+ sleep rate_limit_sleep_time if rate_limit_exceed?
+
+ data = yield
+
+ last_response = api.last_response
+
+ while last_response.rels[:next]
+ sleep rate_limit_sleep_time if rate_limit_exceed?
+ last_response = last_response.rels[:next].get
+ data.concat(last_response.data) if last_response.data.is_a?(Array)
+ end
+
+ data
+ end
end
end
end
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
index 7d679eaec6a..2c1b94ef2cd 100644
--- a/lib/gitlab/github_import/comment_formatter.rb
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -8,6 +8,7 @@ module Gitlab
commit_id: raw_data.commit_id,
line_code: line_code,
author_id: author_id,
+ type: type,
created_at: raw_data.created_at,
updated_at: raw_data.updated_at
}
@@ -53,6 +54,10 @@ module Gitlab
def note
formatter.author_line(author) + body
end
+
+ def type
+ 'LegacyDiffNote' if on_diff?
+ end
end
end
end
diff --git a/lib/gitlab/github_import/hook_formatter.rb b/lib/gitlab/github_import/hook_formatter.rb
new file mode 100644
index 00000000000..db1fabaa18a
--- /dev/null
+++ b/lib/gitlab/github_import/hook_formatter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module GithubImport
+ class HookFormatter
+ EVENTS = %w[* create delete pull_request push].freeze
+
+ attr_reader :raw
+
+ delegate :id, :name, :active, to: :raw
+
+ def initialize(raw)
+ @raw = raw
+ end
+
+ def config
+ raw.config.attrs
+ end
+
+ def valid?
+ (EVENTS & raw.events).any? && active
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 408d9b79632..e5cf66a0371 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -30,9 +30,8 @@ module Gitlab
end
def import_labels
- client.labels(repo).each do |raw_data|
- Label.create!(LabelFormatter.new(project, raw_data).attributes)
- end
+ labels = client.labels(repo, per_page: 100)
+ labels.each { |raw| LabelFormatter.new(project, raw).create! }
true
rescue ActiveRecord::RecordInvalid => e
@@ -40,9 +39,8 @@ module Gitlab
end
def import_milestones
- client.list_milestones(repo, state: :all).each do |raw_data|
- Milestone.create!(MilestoneFormatter.new(project, raw_data).attributes)
- end
+ milestones = client.milestones(repo, state: :all, per_page: 100)
+ milestones.each { |raw| MilestoneFormatter.new(project, raw).create! }
true
rescue ActiveRecord::RecordInvalid => e
@@ -50,16 +48,15 @@ module Gitlab
end
def import_issues
- client.list_issues(repo, state: :all, sort: :created, direction: :asc).each do |raw_data|
- gh_issue = IssueFormatter.new(project, raw_data)
+ issues = client.issues(repo, state: :all, sort: :created, direction: :asc, per_page: 100)
- if gh_issue.valid?
- issue = Issue.create!(gh_issue.attributes)
- apply_labels(gh_issue.number, issue)
+ issues.each do |raw|
+ gh_issue = IssueFormatter.new(project, raw)
- if gh_issue.has_comments?
- import_comments(gh_issue.number, issue)
- end
+ if gh_issue.valid?
+ issue = gh_issue.create!
+ apply_labels(issue)
+ import_comments(issue) if gh_issue.has_comments?
end
end
@@ -69,34 +66,48 @@ module Gitlab
end
def import_pull_requests
- pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc)
- .map { |raw| PullRequestFormatter.new(project, raw) }
- .select(&:valid?)
+ hooks = client.hooks(repo).map { |raw| HookFormatter.new(raw) }.select(&:valid?)
+ disable_webhooks(hooks)
+
+ pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100)
+ pull_requests = pull_requests.map { |raw| PullRequestFormatter.new(project, raw) }.select(&:valid?)
source_branches_removed = pull_requests.reject(&:source_branch_exists?).map { |pr| [pr.source_branch_name, pr.source_branch_sha] }
target_branches_removed = pull_requests.reject(&:target_branch_exists?).map { |pr| [pr.target_branch_name, pr.target_branch_sha] }
branches_removed = source_branches_removed | target_branches_removed
- create_refs(branches_removed)
+ restore_branches(branches_removed)
pull_requests.each do |pull_request|
- 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
+ merge_request = pull_request.create!
+ apply_labels(merge_request)
+ import_comments(merge_request)
+ import_comments_on_diff(merge_request)
end
- delete_refs(branches_removed)
-
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
+ ensure
+ clean_up_restored_branches(branches_removed)
+ clean_up_disabled_webhooks(hooks)
+ end
+
+ def disable_webhooks(hooks)
+ update_webhooks(hooks, active: false)
+ end
+
+ def clean_up_disabled_webhooks(hooks)
+ update_webhooks(hooks, active: true)
+ end
+
+ def update_webhooks(hooks, options)
+ hooks.each do |hook|
+ client.edit_hook(repo, hook.id, hook.name, hook.config, options)
+ end
end
- def create_refs(branches)
+ def restore_branches(branches)
branches.each do |name, sha|
client.create_ref(repo, "refs/heads/#{name}", sha)
end
@@ -104,15 +115,15 @@ module Gitlab
project.repository.fetch_ref(repo_url, '+refs/heads/*', 'refs/heads/*')
end
- def delete_refs(branches)
+ def clean_up_restored_branches(branches)
branches.each do |name, _|
client.delete_ref(repo, "heads/#{name}")
project.repository.rm_branch(project.creator, name)
end
end
- def apply_labels(number, issuable)
- issue = client.issue(repo, number)
+ def apply_labels(issuable)
+ issue = client.issue(repo, issuable.iid)
if issue.labels.count > 0
label_ids = issue.labels.map do |raw|
@@ -123,20 +134,20 @@ module Gitlab
end
end
- def import_comments(issue_number, noteable)
- comments = client.issue_comments(repo, issue_number)
- create_comments(comments, noteable)
+ def import_comments(issuable)
+ comments = client.issue_comments(repo, issuable.iid, per_page: 100)
+ create_comments(issuable, comments)
end
- def import_comments_on_diff(pull_request_number, merge_request)
- comments = client.pull_request_comments(repo, pull_request_number)
- create_comments(comments, merge_request)
+ def import_comments_on_diff(merge_request)
+ comments = client.pull_request_comments(repo, merge_request.iid, per_page: 100)
+ create_comments(merge_request, comments)
end
- def create_comments(comments, noteable)
- comments.each do |raw_data|
- comment = CommentFormatter.new(project, raw_data)
- noteable.notes.create!(comment.attributes)
+ def create_comments(issuable, comments)
+ comments.each do |raw|
+ comment = CommentFormatter.new(project, raw)
+ issuable.notes.create!(comment.attributes)
end
end
diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb
index c8173913b4e..835ec858b35 100644
--- a/lib/gitlab/github_import/issue_formatter.rb
+++ b/lib/gitlab/github_import/issue_formatter.rb
@@ -20,6 +20,10 @@ module Gitlab
raw_data.comments > 0
end
+ def klass
+ Issue
+ end
+
def number
raw_data.number
end
diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb
index c2b9d40b511..9f18244e7d7 100644
--- a/lib/gitlab/github_import/label_formatter.rb
+++ b/lib/gitlab/github_import/label_formatter.rb
@@ -9,6 +9,10 @@ module Gitlab
}
end
+ def klass
+ Label
+ end
+
private
def color
diff --git a/lib/gitlab/github_import/milestone_formatter.rb b/lib/gitlab/github_import/milestone_formatter.rb
index e91a7e328cf..53d4b3102d1 100644
--- a/lib/gitlab/github_import/milestone_formatter.rb
+++ b/lib/gitlab/github_import/milestone_formatter.rb
@@ -14,6 +14,10 @@ module Gitlab
}
end
+ def klass
+ Milestone
+ end
+
private
def number
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index a2947b56ad9..498b00cb658 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -24,6 +24,10 @@ module Gitlab
}
end
+ def klass
+ MergeRequest
+ end
+
def number
raw_data.number
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index ab900b641c4..f751a3a12fd 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -8,6 +8,7 @@ module Gitlab
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
+ gon.award_menu_url = emojis_path
if current_user
gon.current_user_id = current_user.id
diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb
index baf52ff750d..8684b4636ea 100644
--- a/lib/gitlab/key_fingerprint.rb
+++ b/lib/gitlab/key_fingerprint.rb
@@ -17,9 +17,9 @@ module Gitlab
file.rewind
cmd = []
- cmd.push *%W(ssh-keygen)
- cmd.push *%W(-E md5) if explicit_fingerprint_algorithm?
- cmd.push *%W(-lf #{file.path})
+ cmd.push('ssh-keygen')
+ cmd.push('-E', 'md5') if explicit_fingerprint_algorithm?
+ cmd.push('-lf', file.path)
cmd_output, cmd_status = popen(cmd, '/tmp')
end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index aff7ccb157f..f9bb5775323 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -93,6 +93,7 @@ module Gitlab
end
protected
+
def base_config
Gitlab.config.ldap
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 356e96fcbab..78f3ecb4cb4 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -69,13 +69,20 @@ module Gitlab
return unless ldap_person
# If a corresponding person exists with same uid in a LDAP server,
- # set up a Gitlab user with dual LDAP and Omniauth identities.
- if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
- # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
+ # check if the user already has a GitLab account.
+ user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
+ if user
+ # Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account.
+ log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
else
- # No account in Gitlab yet: create it and add the LDAP identity
- user = build_new_user
+ log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account."
+ user = find_by_uid_and_provider
+ if user.nil?
+ log.info "No user found using #{auth_hash.provider} provider. Creating a new one."
+ user = build_new_user
+ end
+ log.info "Correct account has been found. Adding LDAP identity to user: #{user.username}."
user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
end
diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb
index dba4bbfc899..8943022612c 100644
--- a/lib/gitlab/saml/user.rb
+++ b/lib/gitlab/saml/user.rb
@@ -12,12 +12,12 @@ module Gitlab
end
def gl_user
- @user ||= find_by_uid_and_provider
-
if auto_link_ldap_user?
@user ||= find_or_create_ldap_user
end
+ @user ||= find_by_uid_and_provider
+
if auto_link_saml_user?
@user ||= find_by_email
end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 2ef0e982256..7cf506ebe64 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -5,7 +5,7 @@ module Gitlab
SeedFu.quiet = true
yield
SeedFu.quiet = false
- puts "\nOK".green
+ puts "\nOK".color(:green)
end
def self.by_user(user)
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c3ddd4c2680..388f84dbe0e 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -6,6 +6,13 @@ module Gitlab
SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
class << self
+ def git_http_ok(repository, user)
+ {
+ 'GL_ID' => Gitlab::ShellEnv.gl_id(user),
+ 'RepoPath' => repository.path_to_repo,
+ }
+ end
+
def send_git_blob(repository, blob)
params = {
'RepoPath' => repository.path_to_repo,
@@ -14,24 +21,39 @@ module Gitlab
[
SEND_DATA_HEADER,
- "git-blob:#{encode(params)}",
+ "git-blob:#{encode(params)}"
]
end
- def send_git_archive(project, ref, format)
+ def send_git_archive(repository, ref:, format:)
format ||= 'tar.gz'
format.downcase!
- params = project.repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
+ params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
raise "Repository or ref not found" if params.empty?
[
SEND_DATA_HEADER,
- "git-archive:#{encode(params)}",
+ "git-archive:#{encode(params)}"
]
end
-
+
+ def send_git_diff(repository, diff_refs)
+ from, to = diff_refs
+
+ params = {
+ 'RepoPath' => repository.path_to_repo,
+ 'ShaFrom' => from.sha,
+ 'ShaTo' => to.sha
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "git-diff:#{encode(params)}"
+ ]
+ end
+
protected
-
+
def encode(hash)
Base64.urlsafe_encode64(JSON.dump(hash))
end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 596eaca6d0d..9ee72fde92f 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -40,14 +40,14 @@ namespace :gitlab do
removed.
MSG
ask_to_continue
- puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.yellow
+ puts 'Removing all tables. Press `Ctrl-C` within 5 seconds to abort'.color(:yellow)
sleep(5)
end
# Drop all tables Load the schema to ensure we don't have any newer tables
# hanging out from a failed upgrade
- $progress.puts 'Cleaning the database ... '.blue
+ $progress.puts 'Cleaning the database ... '.color(:blue)
Rake::Task['gitlab:db:drop_tables'].invoke
- $progress.puts 'done'.green
+ $progress.puts 'done'.color(:green)
Rake::Task['gitlab:backup:db:restore'].invoke
end
Rake::Task['gitlab:backup:repo:restore'].invoke unless backup.skipped?('repositories')
@@ -63,141 +63,141 @@ namespace :gitlab do
namespace :repo do
task create: :environment do
- $progress.puts "Dumping repositories ...".blue
+ $progress.puts "Dumping repositories ...".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("repositories")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Repository.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring repositories ...".blue
+ $progress.puts "Restoring repositories ...".color(:blue)
Backup::Repository.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :db do
task create: :environment do
- $progress.puts "Dumping database ... ".blue
+ $progress.puts "Dumping database ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("db")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Database.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring database ... ".blue
+ $progress.puts "Restoring database ... ".color(:blue)
Backup::Database.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :builds do
task create: :environment do
- $progress.puts "Dumping builds ... ".blue
+ $progress.puts "Dumping builds ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("builds")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Builds.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring builds ... ".blue
+ $progress.puts "Restoring builds ... ".color(:blue)
Backup::Builds.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :uploads do
task create: :environment do
- $progress.puts "Dumping uploads ... ".blue
+ $progress.puts "Dumping uploads ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("uploads")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Uploads.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring uploads ... ".blue
+ $progress.puts "Restoring uploads ... ".color(:blue)
Backup::Uploads.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :artifacts do
task create: :environment do
- $progress.puts "Dumping artifacts ... ".blue
+ $progress.puts "Dumping artifacts ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("artifacts")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Artifacts.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring artifacts ... ".blue
+ $progress.puts "Restoring artifacts ... ".color(:blue)
Backup::Artifacts.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :lfs do
task create: :environment do
- $progress.puts "Dumping lfs objects ... ".blue
+ $progress.puts "Dumping lfs objects ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("lfs")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Lfs.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
task restore: :environment do
- $progress.puts "Restoring lfs objects ... ".blue
+ $progress.puts "Restoring lfs objects ... ".color(:blue)
Backup::Lfs.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
end
namespace :registry do
task create: :environment do
- $progress.puts "Dumping container registry images ... ".blue
+ $progress.puts "Dumping container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled
if ENV["SKIP"] && ENV["SKIP"].include?("registry")
- $progress.puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".color(:cyan)
else
Backup::Registry.new.dump
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
end
else
- $progress.puts "[DISABLED]".cyan
+ $progress.puts "[DISABLED]".color(:cyan)
end
end
task restore: :environment do
- $progress.puts "Restoring container registry images ... ".blue
+ $progress.puts "Restoring container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled
Backup::Registry.new.restore
- $progress.puts "done".green
+ $progress.puts "done".color(:green)
else
- $progress.puts "[DISABLED]".cyan
+ $progress.puts "[DISABLED]".color(:cyan)
end
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index fad89c73762..12d6ac45fb6 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -50,14 +50,14 @@ namespace :gitlab do
end
if correct_options.all?
- puts "yes".green
+ puts "yes".color(:green)
else
print "Trying to fix Git error automatically. ..."
if auto_fix_git_config(options)
- puts "Success".green
+ puts "Success".color(:green)
else
- puts "Failed".red
+ puts "Failed".color(:red)
try_fixing_it(
sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
)
@@ -74,9 +74,9 @@ namespace :gitlab do
database_config_file = Rails.root.join("config", "database.yml")
if File.exists?(database_config_file)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Copy config/database.yml.<your db> to config/database.yml",
"Check that the information in config/database.yml is correct"
@@ -95,9 +95,9 @@ namespace :gitlab do
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
if File.exists?(gitlab_config_file)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Copy config/gitlab.yml.example to config/gitlab.yml",
"Update config/gitlab.yml to match your setup"
@@ -114,14 +114,14 @@ namespace :gitlab do
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
unless File.exists?(gitlab_config_file)
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
end
# omniauth or ldap could have been deleted from the file
unless Gitlab.config['git_host']
- puts "no".green
+ puts "no".color(:green)
else
- puts "yes".red
+ puts "yes".color(:red)
try_fixing_it(
"Backup your config/gitlab.yml",
"Copy config/gitlab.yml.example to config/gitlab.yml",
@@ -138,16 +138,16 @@ namespace :gitlab do
print "Init script exists? ... "
if omnibus_gitlab?
- puts 'skipped (omnibus-gitlab has no init script)'.magenta
+ puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
return
end
script_path = "/etc/init.d/gitlab"
if File.exists?(script_path)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Install the init script"
)
@@ -162,7 +162,7 @@ namespace :gitlab do
print "Init script up-to-date? ... "
if omnibus_gitlab?
- puts 'skipped (omnibus-gitlab has no init script)'.magenta
+ puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
return
end
@@ -170,7 +170,7 @@ namespace :gitlab do
script_path = "/etc/init.d/gitlab"
unless File.exists?(script_path)
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
return
end
@@ -178,9 +178,9 @@ namespace :gitlab do
script_content = File.read(script_path)
if recipe_content == script_content
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Redownload the init script"
)
@@ -197,9 +197,9 @@ namespace :gitlab do
migration_status, _ = Gitlab::Popen.popen(%W(bundle exec rake db:migrate:status))
unless migration_status =~ /down\s+\d{14}/
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
sudo_gitlab("bundle exec rake db:migrate RAILS_ENV=production")
)
@@ -210,13 +210,13 @@ namespace :gitlab do
def check_orphaned_group_members
print "Database contains orphaned GroupMembers? ... "
if GroupMember.where("user_id not in (select id from users)").count > 0
- puts "yes".red
+ puts "yes".color(:red)
try_fixing_it(
"You can delete the orphaned records using something along the lines of:",
sudo_gitlab("bundle exec rails runner -e production 'GroupMember.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'")
)
else
- puts "no".green
+ puts "no".color(:green)
end
end
@@ -226,9 +226,9 @@ namespace :gitlab do
log_path = Rails.root.join("log")
if File.writable?(log_path)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo chown -R gitlab #{log_path}",
"sudo chmod -R u+rwX #{log_path}"
@@ -246,9 +246,9 @@ namespace :gitlab do
tmp_path = Rails.root.join("tmp")
if File.writable?(tmp_path)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo chown -R gitlab #{tmp_path}",
"sudo chmod -R u+rwX #{tmp_path}"
@@ -264,7 +264,7 @@ namespace :gitlab do
print "Uploads directory setup correctly? ... "
unless File.directory?(Rails.root.join('public/uploads'))
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo -u #{gitlab_user} mkdir #{Rails.root}/public/uploads"
)
@@ -280,16 +280,16 @@ namespace :gitlab do
if File.stat(upload_path).mode == 040700
unless Dir.exists?(upload_path_tmp)
- puts 'skipped (no tmp uploads folder yet)'.magenta
+ puts 'skipped (no tmp uploads folder yet)'.color(:magenta)
return
end
# If tmp upload dir has incorrect permissions, assume others do as well
# Verify drwx------ permissions
if File.stat(upload_path_tmp).mode == 040700 && File.owned?(upload_path_tmp)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo chown -R #{gitlab_user} #{upload_path}",
"sudo find #{upload_path} -type f -exec chmod 0644 {} \\;",
@@ -301,7 +301,7 @@ namespace :gitlab do
fix_and_rerun
end
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo chmod 700 #{upload_path}"
)
@@ -320,9 +320,9 @@ namespace :gitlab do
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Update your redis server to a version >= #{min_redis_version}"
)
@@ -361,10 +361,10 @@ namespace :gitlab do
repo_base_path = Gitlab.config.gitlab_shell.repos_path
if File.exists?(repo_base_path)
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
- puts "#{repo_base_path} is missing".red
+ puts "no".color(:red)
+ puts "#{repo_base_path} is missing".color(:red)
try_fixing_it(
"This should have been created when setting up GitLab Shell.",
"Make sure it's set correctly in config/gitlab.yml",
@@ -382,14 +382,14 @@ namespace :gitlab do
repo_base_path = Gitlab.config.gitlab_shell.repos_path
unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
return
end
unless File.symlink?(repo_base_path)
- puts "no".green
+ puts "no".color(:green)
else
- puts "yes".red
+ puts "yes".color(:red)
try_fixing_it(
"Make sure it's set to the real directory in config/gitlab.yml"
)
@@ -402,14 +402,14 @@ namespace :gitlab do
repo_base_path = Gitlab.config.gitlab_shell.repos_path
unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
return
end
if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770")
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"sudo chmod -R ug+rwX,o-rwx #{repo_base_path}",
"sudo chmod -R ug-s #{repo_base_path}",
@@ -429,17 +429,17 @@ namespace :gitlab do
repo_base_path = Gitlab.config.gitlab_shell.repos_path
unless File.exists?(repo_base_path)
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
return
end
uid = uid_for(gitlab_shell_ssh_user)
gid = gid_for(gitlab_shell_owner_group)
if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
- puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".blue
+ puts "no".color(:red)
+ puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".color(:blue)
try_fixing_it(
"sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
)
@@ -456,7 +456,7 @@ namespace :gitlab do
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
unless Project.count > 0
- puts "can't check, you have no projects".magenta
+ puts "can't check, you have no projects".color(:magenta)
return
end
puts ""
@@ -466,12 +466,12 @@ namespace :gitlab do
project_hook_directory = File.join(project.repository.path_to_repo, "hooks")
if project.empty_repo?
- puts "repository is empty".magenta
+ puts "repository is empty".color(:magenta)
elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
(File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
- puts 'ok'.green
+ puts 'ok'.color(:green)
else
- puts "wrong or missing hooks".red
+ puts "wrong or missing hooks".color(:red)
try_fixing_it(
sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')}"),
'Check the hooks_path in config/gitlab.yml',
@@ -491,9 +491,9 @@ namespace :gitlab do
check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
puts "Running #{check_cmd}"
if system(check_cmd, chdir: gitlab_shell_repo_base)
- puts 'gitlab-shell self-check successful'.green
+ puts 'gitlab-shell self-check successful'.color(:green)
else
- puts 'gitlab-shell self-check failed'.red
+ puts 'gitlab-shell self-check failed'.color(:red)
try_fixing_it(
'Make sure GitLab is running;',
'Check the gitlab-shell configuration file:',
@@ -507,7 +507,7 @@ namespace :gitlab do
print "projects have namespace: ... "
unless Project.count > 0
- puts "can't check, you have no projects".magenta
+ puts "can't check, you have no projects".color(:magenta)
return
end
puts ""
@@ -516,9 +516,9 @@ namespace :gitlab do
print sanitized_message(project)
if project.namespace
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Migrate global projects"
)
@@ -576,9 +576,9 @@ namespace :gitlab do
print "Running? ... "
if sidekiq_process_count > 0
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
sudo_gitlab("RAILS_ENV=production bin/background_jobs start")
)
@@ -596,9 +596,9 @@ namespace :gitlab do
print 'Number of Sidekiq processes ... '
if process_count == 1
- puts '1'.green
+ puts '1'.color(:green)
else
- puts "#{process_count}".red
+ puts "#{process_count}".color(:red)
try_fixing_it(
'sudo service gitlab stop',
"sudo pkill -u #{gitlab_user} -f sidekiq",
@@ -646,16 +646,16 @@ namespace :gitlab do
print "Init.d configured correctly? ... "
if omnibus_gitlab?
- puts 'skipped (omnibus-gitlab has no init script)'.magenta
+ puts 'skipped (omnibus-gitlab has no init script)'.color(:magenta)
return
end
path = "/etc/default/gitlab"
if File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Enable mail_room in the init.d configuration."
)
@@ -672,9 +672,9 @@ namespace :gitlab do
path = Rails.root.join("Procfile")
if File.exist?(path) && File.read(path) =~ /^mail_room:/
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Enable mail_room in your Procfile."
)
@@ -691,14 +691,14 @@ namespace :gitlab do
path = "/etc/default/gitlab"
unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
- puts "can't check because of previous errors".magenta
+ puts "can't check because of previous errors".color(:magenta)
return
end
if mail_room_running?
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
sudo_gitlab("RAILS_ENV=production bin/mail_room start")
)
@@ -729,9 +729,9 @@ namespace :gitlab do
end
if connected
- puts "yes".green
+ puts "yes".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Check that the information in config/gitlab.yml is correct"
)
@@ -799,7 +799,7 @@ namespace :gitlab do
namespace :user do
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
- username = args[:username] || prompt("Check repository integrity for which username? ".blue)
+ username = args[:username] || prompt("Check repository integrity for which username? ".color(:blue))
user = User.find_by(username: username)
if user
repo_dirs = user.authorized_projects.map do |p|
@@ -811,7 +811,7 @@ namespace :gitlab do
repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
else
- puts "\nUser '#{username}' not found".red
+ puts "\nUser '#{username}' not found".color(:red)
end
end
end
@@ -820,13 +820,13 @@ namespace :gitlab do
##########################
def fix_and_rerun
- puts " Please #{"fix the error above"} and rerun the checks.".red
+ puts " Please #{"fix the error above"} and rerun the checks.".color(:red)
end
def for_more_information(*sources)
sources = sources.shift if sources.first.is_a?(Array)
- puts " For more information see:".blue
+ puts " For more information see:".color(:blue)
sources.each do |source|
puts " #{source}"
end
@@ -834,7 +834,7 @@ namespace :gitlab do
def finished_checking(component)
puts ""
- puts "Checking #{component.yellow} ... #{"Finished".green}"
+ puts "Checking #{component.color(:yellow)} ... #{"Finished".color(:green)}"
puts ""
end
@@ -855,14 +855,14 @@ namespace :gitlab do
end
def start_checking(component)
- puts "Checking #{component.yellow} ..."
+ puts "Checking #{component.color(:yellow)} ..."
puts ""
end
def try_fixing_it(*steps)
steps = steps.shift if steps.first.is_a?(Array)
- puts " Try fixing it:".blue
+ puts " Try fixing it:".color(:blue)
steps.each do |step|
puts " #{step}"
end
@@ -874,9 +874,9 @@ namespace :gitlab do
print "GitLab Shell version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
- puts "OK (#{current_version})".green
+ puts "OK (#{current_version})".color(:green)
else
- puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".red
+ puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".color(:red)
end
end
@@ -887,9 +887,9 @@ namespace :gitlab do
print "Ruby version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
- puts "yes (#{current_version})".green
+ puts "yes (#{current_version})".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Update your ruby to a version >= #{required_version} from #{current_version}"
)
@@ -905,9 +905,9 @@ namespace :gitlab do
print "Git version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
- puts "yes (#{current_version})".green
+ puts "yes (#{current_version})".color(:green)
else
- puts "no".red
+ puts "no".color(:red)
try_fixing_it(
"Update your git to a version >= #{required_version} from #{current_version}"
)
@@ -925,9 +925,9 @@ namespace :gitlab do
def sanitized_message(project)
if should_sanitize?
- "#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... "
+ "#{project.namespace_id.to_s.color(:yellow)}/#{project.id.to_s.color(:yellow)} ... "
else
- "#{project.name_with_namespace.yellow} ... "
+ "#{project.name_with_namespace.color(:yellow)} ... "
end
end
@@ -940,7 +940,7 @@ namespace :gitlab do
end
def check_repo_integrity(repo_dir)
- puts "\nChecking repo at #{repo_dir.yellow}"
+ puts "\nChecking repo at #{repo_dir.color(:yellow)}"
git_fsck(repo_dir)
check_config_lock(repo_dir)
@@ -948,25 +948,25 @@ namespace :gitlab do
end
def git_fsck(repo_dir)
- puts "Running `git fsck`".yellow
+ puts "Running `git fsck`".color(:yellow)
system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir)
end
def check_config_lock(repo_dir)
config_exists = File.exist?(File.join(repo_dir,'config.lock'))
- config_output = config_exists ? 'yes'.red : 'no'.green
- puts "'config.lock' file exists?".yellow + " ... #{config_output}"
+ config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
+ puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
end
def check_ref_locks(repo_dir)
lock_files = Dir.glob(File.join(repo_dir,'refs/heads/*.lock'))
if lock_files.present?
- puts "Ref lock files exist:".red
+ puts "Ref lock files exist:".color(:red)
lock_files.each do |lock_file|
puts " #{lock_file}"
end
else
- puts "No ref lock files exist".green
+ puts "No ref lock files exist".color(:green)
end
end
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 9f5852ac613..ab0028d6603 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -10,7 +10,7 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path
all_dirs = Dir.glob(git_base_path + '/*')
- puts git_base_path.yellow
+ puts git_base_path.color(:yellow)
puts "Looking for directories to remove... "
all_dirs.reject! do |dir|
@@ -29,17 +29,17 @@ namespace :gitlab do
if remove_flag
if FileUtils.rm_rf dir_path
- puts "Removed...#{dir_path}".red
+ puts "Removed...#{dir_path}".color(:red)
else
- puts "Cannot remove #{dir_path}".red
+ puts "Cannot remove #{dir_path}".color(:red)
end
else
- puts "Can be removed: #{dir_path}".red
+ puts "Can be removed: #{dir_path}".color(:red)
end
end
unless remove_flag
- puts "To cleanup this directories run this command with REMOVE=true".yellow
+ puts "To cleanup this directories run this command with REMOVE=true".color(:yellow)
end
end
@@ -75,19 +75,19 @@ namespace :gitlab do
next unless user.ldap_user?
print "#{user.name} (#{user.ldap_identity.extern_uid}) ..."
if Gitlab::LDAP::Access.allowed?(user)
- puts " [OK]".green
+ puts " [OK]".color(:green)
else
if block_flag
user.block! unless user.blocked?
- puts " [BLOCKED]".red
+ puts " [BLOCKED]".color(:red)
else
- puts " [NOT IN LDAP]".yellow
+ puts " [NOT IN LDAP]".color(:yellow)
end
end
end
unless block_flag
- puts "To block these users run this command with BLOCK=true".yellow
+ puts "To block these users run this command with BLOCK=true".color(:yellow)
end
end
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 86f5d65f128..7230b9485be 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -3,22 +3,22 @@ namespace :gitlab do
desc 'GitLab | Manually insert schema migration version'
task :mark_migration_complete, [:version] => :environment do |_, args|
unless args[:version]
- puts "Must specify a migration version as an argument".red
+ puts "Must specify a migration version as an argument".color(:red)
exit 1
end
version = args[:version].to_i
if version == 0
- puts "Version '#{args[:version]}' must be a non-zero integer".red
+ puts "Version '#{args[:version]}' must be a non-zero integer".color(:red)
exit 1
end
sql = "INSERT INTO schema_migrations (version) VALUES (#{version})"
begin
ActiveRecord::Base.connection.execute(sql)
- puts "Successfully marked '#{version}' as complete".green
+ puts "Successfully marked '#{version}' as complete".color(:green)
rescue ActiveRecord::RecordNotUnique
- puts "Migration version '#{version}' is already marked complete".yellow
+ puts "Migration version '#{version}' is already marked complete".color(:yellow)
end
end
@@ -34,7 +34,7 @@ namespace :gitlab do
# 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") }
+ tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
end
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake
index 65ee430d550..f9834a4dae8 100644
--- a/lib/tasks/gitlab/git.rake
+++ b/lib/tasks/gitlab/git.rake
@@ -5,7 +5,7 @@ namespace :gitlab do
task repack: :environment do
failures = perform_git_cmd(%W(git repack -a --quiet), "Repacking repo")
if failures.empty?
- puts "Done".green
+ puts "Done".color(:green)
else
output_failures(failures)
end
@@ -15,7 +15,7 @@ namespace :gitlab do
task gc: :environment do
failures = perform_git_cmd(%W(git gc --auto --quiet), "Garbage Collecting")
if failures.empty?
- puts "Done".green
+ puts "Done".color(:green)
else
output_failures(failures)
end
@@ -25,7 +25,7 @@ namespace :gitlab do
task prune: :environment do
failures = perform_git_cmd(%W(git prune), "Git Prune")
if failures.empty?
- puts "Done".green
+ puts "Done".color(:green)
else
output_failures(failures)
end
@@ -47,7 +47,7 @@ namespace :gitlab do
end
def output_failures(failures)
- puts "The following repositories reported errors:".red
+ puts "The following repositories reported errors:".color(:red)
failures.each { |f| puts "- #{f}" }
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 1c04f47f08f..4753f00c26a 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -23,7 +23,7 @@ namespace :gitlab do
group_name, name = File.split(path)
group_name = nil if group_name == '.'
- puts "Processing #{repo_path}".yellow
+ puts "Processing #{repo_path}".color(:yellow)
if path.end_with?('.wiki')
puts " * Skipping wiki repo"
@@ -51,9 +51,9 @@ namespace :gitlab do
group.path = group_name
group.owner = user
if group.save
- puts " * Created Group #{group.name} (#{group.id})".green
+ puts " * Created Group #{group.name} (#{group.id})".color(:green)
else
- puts " * Failed trying to create group #{group.name}".red
+ puts " * Failed trying to create group #{group.name}".color(:red)
end
end
# set project group
@@ -63,17 +63,17 @@ namespace :gitlab do
project = Projects::CreateService.new(user, project_params).execute
if project.persisted?
- puts " * Created #{project.name} (#{repo_path})".green
+ puts " * Created #{project.name} (#{repo_path})".color(:green)
project.update_repository_size
project.update_commit_count
else
- puts " * Failed trying to create #{project.name} (#{repo_path})".red
- puts " Errors: #{project.errors.messages}".red
+ puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
+ puts " Errors: #{project.errors.messages}".color(:red)
end
end
end
- puts "Done!".green
+ puts "Done!".color(:green)
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index d6883a563ee..352b566df24 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -15,15 +15,15 @@ namespace :gitlab do
rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
puts ""
- puts "System information".yellow
- puts "System:\t\t#{os_name || "unknown".red}"
+ puts "System information".color(:yellow)
+ puts "System:\t\t#{os_name || "unknown".color(:red)}"
puts "Current User:\t#{run(%W(whoami))}"
- puts "Using RVM:\t#{rvm_version.present? ? "yes".green : "no"}"
+ puts "Using RVM:\t#{rvm_version.present? ? "yes".color(:green) : "no"}"
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
- puts "Ruby Version:\t#{ruby_version || "unknown".red}"
- puts "Gem Version:\t#{gem_version || "unknown".red}"
- puts "Bundler Version:#{bunder_version || "unknown".red}"
- puts "Rake Version:\t#{rake_version || "unknown".red}"
+ puts "Ruby Version:\t#{ruby_version || "unknown".color(:red)}"
+ puts "Gem Version:\t#{gem_version || "unknown".color(:red)}"
+ puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
+ puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
@@ -39,7 +39,7 @@ namespace :gitlab do
omniauth_providers.map! { |provider| provider['name'] }
puts ""
- puts "GitLab information".yellow
+ puts "GitLab information".color(:yellow)
puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab::REVISION}"
puts "Directory:\t#{Rails.root}"
@@ -47,9 +47,9 @@ namespace :gitlab do
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
puts "HTTP Clone URL:\t#{http_clone_url}"
puts "SSH Clone URL:\t#{ssh_clone_url}"
- puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".green : "no"}"
- puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".green : "no"}"
- puts "Omniauth Providers: #{omniauth_providers.map(&:magenta).join(', ')}" if Gitlab.config.omniauth.enabled
+ puts "Using LDAP:\t#{Gitlab.config.ldap.enabled ? "yes".color(:green) : "no"}"
+ puts "Using Omniauth:\t#{Gitlab.config.omniauth.enabled ? "yes".color(:green) : "no"}"
+ puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab.config.omniauth.enabled
@@ -60,8 +60,8 @@ namespace :gitlab do
end
puts ""
- puts "GitLab Shell".yellow
- puts "Version:\t#{gitlab_shell_version || "unknown".red}"
+ puts "GitLab Shell".color(:yellow)
+ puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
puts "Repositories:\t#{Gitlab.config.gitlab_shell.repos_path}"
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 48baecfd2a2..05fcb8e3da5 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -19,7 +19,7 @@ namespace :gitlab do
Rake::Task["setup_postgresql"].invoke
Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError
- puts "Quitting...".red
+ puts "Quitting...".color(:red)
exit 1
end
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index dd61632e557..b1648a4602a 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -118,12 +118,12 @@ namespace :gitlab do
puts ""
unless $?.success?
- puts "Failed to add keys...".red
+ puts "Failed to add keys...".color(:red)
exit 1
end
rescue Gitlab::TaskAbortedByUserError
- puts "Quitting...".red
+ puts "Quitting...".color(:red)
exit 1
end
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index d33b5b31e18..d0c019044b7 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -2,7 +2,7 @@ module Gitlab
class TaskAbortedByUserError < StandardError; end
end
-String.disable_colorization = true unless STDOUT.isatty
+require 'rainbow/ext/string'
# Prevent StateMachine warnings from outputting during a cron task
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
@@ -14,7 +14,7 @@ namespace :gitlab do
# Returns "yes" the user chose to continue
# Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue
def ask_to_continue
- answer = prompt("Do you want to continue (yes/no)? ".blue, %w{yes no})
+ answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no})
raise Gitlab::TaskAbortedByUserError unless answer == "yes"
end
@@ -98,10 +98,10 @@ namespace :gitlab do
gitlab_user = Gitlab.config.gitlab.user
current_user = run(%W(whoami)).chomp
unless current_user == gitlab_user
- puts " Warning ".colorize(:black).on_yellow
- puts " You are running as user #{current_user.magenta}, we hope you know what you are doing."
+ puts " Warning ".color(:black).background(:yellow)
+ puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
puts " Things may work\/fail for the wrong reasons."
- puts " For correct results you should run this as user #{gitlab_user.magenta}."
+ puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}."
puts ""
end
@warned_user_not_gitlab = true
diff --git a/lib/tasks/gitlab/two_factor.rake b/lib/tasks/gitlab/two_factor.rake
index 9196677a017..fc0ccc726ed 100644
--- a/lib/tasks/gitlab/two_factor.rake
+++ b/lib/tasks/gitlab/two_factor.rake
@@ -6,17 +6,17 @@ namespace :gitlab do
count = scope.count
if count > 0
- puts "This will disable 2FA for #{count.to_s.red} users..."
+ puts "This will disable 2FA for #{count.to_s.color(:red)} users..."
begin
ask_to_continue
scope.find_each(&:disable_two_factor!)
- puts "Successfully disabled 2FA for #{count} users.".green
+ puts "Successfully disabled 2FA for #{count} users.".color(:green)
rescue Gitlab::TaskAbortedByUserError
- puts "Quitting...".red
+ puts "Quitting...".color(:red)
end
else
- puts "There are currently no users with 2FA enabled.".yellow
+ puts "There are currently no users with 2FA enabled.".color(:yellow)
end
end
end
diff --git a/lib/tasks/gitlab/update_commit_count.rake b/lib/tasks/gitlab/update_commit_count.rake
index 9b636f12d9f..3bd10b0208b 100644
--- a/lib/tasks/gitlab/update_commit_count.rake
+++ b/lib/tasks/gitlab/update_commit_count.rake
@@ -6,15 +6,15 @@ namespace :gitlab do
ask_to_continue unless ENV['force'] == 'yes'
projects.find_each(batch_size: 100) do |project|
- print "#{project.name_with_namespace.yellow} ... "
+ print "#{project.name_with_namespace.color(:yellow)} ... "
unless project.repo_exists?
- puts "skipping, because the repo is empty".magenta
+ puts "skipping, because the repo is empty".color(:magenta)
next
end
project.update_commit_count
- puts project.commit_count.to_s.green
+ puts project.commit_count.to_s.color(:green)
end
end
end
diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake
index 84aa312002b..4fd48cccb1d 100644
--- a/lib/tasks/gitlab/update_gitignore.rake
+++ b/lib/tasks/gitlab/update_gitignore.rake
@@ -2,14 +2,14 @@ namespace :gitlab do
desc "GitLab | Update gitignore"
task :update_gitignore do
unless clone_gitignores
- puts "Cloning the gitignores failed".red
+ puts "Cloning the gitignores failed".color(:red)
return
end
remove_unneeded_files(gitignore_directory)
remove_unneeded_files(global_directory)
- puts "Done".green
+ puts "Done".color(:green)
end
def clone_gitignores
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index cc0f668474e..f467cc0ee29 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -12,9 +12,9 @@ namespace :gitlab do
print "- #{project.name} ... "
web_hook = project.hooks.new(url: web_hook_url)
if web_hook.save
- puts "added".green
+ puts "added".color(:green)
else
- print "failed".red
+ print "failed".color(:red)
puts " [#{web_hook.errors.full_messages.to_sentence}]"
end
end
@@ -57,7 +57,7 @@ namespace :gitlab do
if namespace
Project.in_namespace(namespace.id)
else
- puts "Namespace not found: #{namespace_path}".red
+ puts "Namespace not found: #{namespace_path}".color(:red)
exit 2
end
end
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
index d258c6fd08d..4f2486157b7 100644
--- a/lib/tasks/migrate/migrate_iids.rake
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -1,6 +1,6 @@
desc "GitLab | Build internal ids for issues and merge requests"
task migrate_iids: :environment do
- puts 'Issues'.yellow
+ puts 'Issues'.color(:yellow)
Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
begin
issue.set_iid
@@ -15,7 +15,7 @@ task migrate_iids: :environment do
end
puts 'done'
- puts 'Merge Requests'.yellow
+ puts 'Merge Requests'.color(:yellow)
MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
begin
mr.set_iid
@@ -30,7 +30,7 @@ task migrate_iids: :environment do
end
puts 'done'
- puts 'Milestones'.yellow
+ puts 'Milestones'.color(:yellow)
Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
begin
m.set_iid
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 01d23b89bb7..da255f5464b 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -52,7 +52,7 @@ def run_spinach_tests(tags)
tests = File.foreach('tmp/spinach-rerun.txt').map(&:chomp)
puts ''
- puts "Spinach tests for #{tags}: Retrying tests... #{tests}".red
+ puts "Spinach tests for #{tags}: Retrying tests... #{tests}".color(:red)
puts ''
sleep(3)
success = run_spinach_command(tests)
diff --git a/scripts/merge-reports b/scripts/merge-reports
new file mode 100755
index 00000000000..f7b574001ac
--- /dev/null
+++ b/scripts/merge-reports
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+
+require 'json'
+require 'yaml'
+
+main_report_file = ARGV.shift
+unless main_report_file
+ puts 'usage: merge_reports <main-report> [extra reports...]'
+ exit 1
+end
+
+puts "Loading #{main_report_file}..."
+main_report = JSON.parse(File.read(main_report_file))
+new_report = main_report.dup
+
+ARGV.each do |report_file|
+ report = JSON.parse(File.read(report_file))
+
+ # Remove existing values
+ updates = report.delete_if do |key, value|
+ main_report[key] && main_report[key] == value
+ end
+ new_report.merge!(updates)
+
+ puts "Merged #{report_file} adding #{updates.size} results."
+end
+
+File.write(main_report_file, JSON.pretty_generate(new_report))
+puts "Saved #{main_report_file}."
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 247383aa46c..d6fb1a34e8c 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,12 +1,16 @@
#!/bin/bash
retry() {
- for i in $(seq 1 3); do
+ if eval "$@"; then
+ return 0
+ fi
+
+ for i in 2 1; do
+ sleep 3s
+ echo "Retrying $i..."
if eval "$@"; then
return 0
fi
- sleep 3s
- echo "Retrying..."
done
return 1
}
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 465531b2b36..cd98fecd0c7 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -31,9 +31,9 @@ describe GroupsController do
let(:issue_2) { create(:issue, project: project) }
before do
- create_list(:upvote_note, 3, project: project, noteable: issue_2)
- create_list(:upvote_note, 2, project: project, noteable: issue_1)
- create_list(:downvote_note, 2, project: project, noteable: issue_2)
+ create_list(:award_emoji, 3, awardable: issue_2)
+ create_list(:award_emoji, 2, awardable: issue_1)
+ create_list(:award_emoji, 2, :downvote, awardable: issue_2,)
sign_in(user)
end
@@ -56,9 +56,9 @@ describe GroupsController do
let(:merge_request_2) { create(:merge_request, :simple, source_project: project) }
before do
- create_list(:upvote_note, 3, project: project, noteable: merge_request_2)
- create_list(:upvote_note, 2, project: project, noteable: merge_request_1)
- create_list(:downvote_note, 2, project: project, noteable: merge_request_2)
+ create_list(:award_emoji, 3, awardable: merge_request_2)
+ create_list(:award_emoji, 2, awardable: merge_request_1)
+ create_list(:award_emoji, 2, :downvote, awardable: merge_request_2)
sign_in(user)
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 81c03c9059b..07bf8d2d1c3 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::BitbucketController do
include ImportSpecHelper
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index 27b11267d2a..5f0f6dea821 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::FogbugzController do
include ImportSpecHelper
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index bcc713dce2a..c55a3c28208 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::GithubController do
include ImportSpecHelper
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 198d006af76..e8cf6aa7767 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::GitlabController do
include ImportSpecHelper
diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb
index 7cb1b85a46d..4ae2b78e11c 100644
--- a/spec/controllers/import/gitorious_controller_spec.rb
+++ b/spec/controllers/import/gitorious_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::GitoriousController do
include ImportSpecHelper
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 66088139a69..4241db6e771 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -1,5 +1,4 @@
require 'spec_helper'
-require_relative 'import_spec_helper'
describe Import::GoogleCodeController do
include ImportSpecHelper
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
new file mode 100644
index 00000000000..af378304893
--- /dev/null
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Oauth::ApplicationsController do
+ let(:user) { create(:user) }
+
+ context 'project members' do
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ it 'shows list of applications' do
+ get :index
+
+ expect(response.status).to eq(200)
+ end
+
+ it 'redirects back to profile page if OAuth applications are disabled' do
+ settings = double(user_oauth_applications?: false)
+ allow_any_instance_of(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return(settings)
+
+ get :index
+
+ expect(response.status).to eq(302)
+ expect(response).to redirect_to(profile_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index 4fb1473c2d2..d08d0018b35 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -8,21 +8,21 @@ describe Profiles::TwoFactorAuthsController do
allow(subject).to receive(:current_user).and_return(user)
end
- describe 'GET new' do
+ describe 'GET show' do
let(:user) { create(:user) }
it 'generates otp_secret for user' do
expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once
- get :new
- get :new # Second hit shouldn't re-generate it
+ get :show
+ get :show # Second hit shouldn't re-generate it
end
it 'assigns qr_code' do
code = double('qr code')
expect(subject).to receive(:build_qr_code).and_return(code)
- get :new
+ get :show
expect(assigns[:qr_code]).to eq code
end
end
@@ -40,7 +40,7 @@ describe Profiles::TwoFactorAuthsController do
expect(user).to receive(:validate_and_consume_otp!).with(pin).and_return(true)
end
- it 'sets two_factor_enabled' do
+ it 'enables 2fa for the user' do
go
user.reload
@@ -79,9 +79,9 @@ describe Profiles::TwoFactorAuthsController do
expect(assigns[:qr_code]).to eq code
end
- it 'renders new' do
+ it 'renders show' do
go
- expect(response).to render_template(:new)
+ expect(response).to render_template(:show)
end
end
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 8ad73472117..c4b4a888b4e 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -122,27 +122,23 @@ describe Projects::BranchesController do
let(:branch) { "feature" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "valid branch name with unencoded slashes" do
let(:branch) { "improve/awesome" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
end
context "invalid branch name, valid ref" do
let(:branch) { "no-branch" }
it { expect(response.status).to eq(404) }
- it { expect(subject).to render_template('destroy') }
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index c469480b086..78be7e3dc35 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -250,4 +250,20 @@ describe Projects::IssuesController do
end
end
end
+
+ describe 'POST #toggle_award_emoji' do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it "toggles the award emoji" do
+ expect do
+ post(:toggle_award_emoji, namespace_id: project.namespace.path,
+ project_id: project.path, id: issue.iid, name: "thumbsup")
+ end.to change { issue.award_emoji.count }.by(1)
+
+ expect(response.status).to eq(200)
+ end
+ end
end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
new file mode 100644
index 00000000000..ab1dd34ed57
--- /dev/null
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Projects::LabelsController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ def create_label(attributes)
+ create(:label, attributes.merge(project: project))
+ end
+
+ before do
+ 15.times { |i| create_label(priority: (i % 3) + 1, title: "label #{15 - i}") }
+ 5.times { |i| create_label(title: "label #{100 - i}") }
+
+
+ get :index, namespace_id: project.namespace.to_param, project_id: project.to_param
+ end
+
+ context '@prioritized_labels' do
+ let(:prioritized_labels) { assigns(:prioritized_labels) }
+
+ it 'contains only prioritized labels' do
+ expect(prioritized_labels).to all(have_attributes(priority: a_value > 0))
+ end
+
+ it 'is sorted by priority, then label title' do
+ priorities_and_titles = prioritized_labels.pluck(:priority, :title)
+
+ expect(priorities_and_titles.sort).to eq(priorities_and_titles)
+ end
+ end
+
+ context '@labels' do
+ let(:labels) { assigns(:labels) }
+
+ it 'contains only unprioritized labels' do
+ expect(labels).to all(have_attributes(priority: nil))
+ end
+
+ it 'is sorted by label title' do
+ titles = labels.pluck(:title)
+
+ expect(titles.sort).to eq(titles)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 4f621a43d7e..4b408c03703 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -84,17 +84,14 @@ describe Projects::MergeRequestsController do
end
describe "as diff" do
- include_examples "export merge as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
+ it "triggers workhorse to serve the request" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
- format: format)
+ format: :diff)
- expect(response.body).to start_with("diff --git")
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:")
end
end
@@ -185,6 +182,92 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'POST #merge' do
+ let(:base_params) do
+ {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: merge_request.iid,
+ format: 'raw'
+ }
+ end
+
+ context 'when the user does not have access' do
+ before do
+ project.team.truncate
+ project.team << [user, :reporter]
+ post :merge, base_params
+ end
+
+ it 'returns not found' do
+ expect(response).to be_not_found
+ end
+ end
+
+ context 'when the merge request is not mergeable' do
+ before do
+ merge_request.update_attributes(title: "WIP: #{merge_request.title}")
+
+ post :merge, base_params
+ end
+
+ it 'returns :failed' do
+ expect(assigns(:status)).to eq(:failed)
+ end
+ end
+
+ context 'when the sha parameter does not match the source SHA' do
+ before { post :merge, base_params.merge(sha: 'foo') }
+
+ it 'returns :sha_mismatch' do
+ expect(assigns(:status)).to eq(:sha_mismatch)
+ end
+ end
+
+ context 'when the sha parameter matches the source SHA' do
+ def merge_with_sha
+ post :merge, base_params.merge(sha: merge_request.source_sha)
+ end
+
+ it 'returns :success' do
+ merge_with_sha
+
+ expect(assigns(:status)).to eq(:success)
+ end
+
+ it 'starts the merge immediately' do
+ expect(MergeWorker).to receive(:perform_async).with(merge_request.id, anything, anything)
+
+ merge_with_sha
+ end
+
+ context 'when merge_when_build_succeeds is passed' do
+ def merge_when_build_succeeds
+ post :merge, base_params.merge(sha: merge_request.source_sha, merge_when_build_succeeds: '1')
+ end
+
+ before do
+ create(:ci_empty_pipeline, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch)
+ end
+
+ it 'returns :merge_when_build_succeeds' do
+ merge_when_build_succeeds
+
+ expect(assigns(:status)).to eq(:merge_when_build_succeeds)
+ end
+
+ it 'sets the MR to merge when the build succeeds' do
+ service = double(:merge_when_build_succeeds_service)
+
+ expect(MergeRequests::MergeWhenBuildSucceedsService).to receive(:new).with(project, anything, anything).and_return(service)
+ expect(service).to receive(:execute).with(merge_request)
+
+ merge_when_build_succeeds
+ end
+ end
+ end
+ end
+
describe "DELETE #destroy" do
it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
new file mode 100644
index 00000000000..00bc38b6071
--- /dev/null
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -0,0 +1,36 @@
+require('spec_helper')
+
+describe Projects::NotesController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:note) { create(:note, noteable: issue, project: project) }
+
+ describe 'POST #toggle_award_emoji' do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it "toggles the award emoji" do
+ expect do
+ post(:toggle_award_emoji, namespace_id: project.namespace.path,
+ project_id: project.path, id: note.id, name: "thumbsup")
+ end.to change { note.award_emoji.count }.by(1)
+
+ expect(response.status).to eq(200)
+ end
+
+ it "removes the already awarded emoji" do
+ post(:toggle_award_emoji, namespace_id: project.namespace.path,
+ project_id: project.path, id: note.id, name: "thumbsup")
+
+ expect do
+ post(:toggle_award_emoji, namespace_id: project.namespace.path,
+ project_id: project.path, id: note.id, name: "thumbsup")
+ end.to change { AwardEmoji.count }.by(-1)
+
+ expect(response.status).to eq(200)
+ end
+ end
+end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index fb29274c687..33c35161da3 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -17,6 +17,7 @@ describe Projects::RawController do
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
expect(response.header['Content-Disposition']).
to eq("inline")
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
end
end
@@ -31,6 +32,7 @@ describe Projects::RawController do
expect(response.status).to eq(200)
expect(response.header['Content-Type']).to eq('image/jpeg')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-blob:")
end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 0ddbec9eac2..aad62cf20e3 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -20,10 +20,11 @@ describe Projects::RepositoriesController do
project.team << [user, :developer]
sign_in(user)
end
- it "uses Gitlab::Workhorse" do
- expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
+ it "uses Gitlab::Workhorse" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
context "when the service raises an error" do
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 5dc8724fb50..4e9bfb0c69b 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -25,10 +25,15 @@ describe SessionsController do
expect(response).to set_flash.to /Signed in successfully/
expect(subject.current_user). to eq user
end
+
+ it "creates an audit log record" do
+ expect { post(:create, user: { login: user.username, password: user.password }) }.to change { SecurityEvent.count }.by(1)
+ expect(SecurityEvent.last.details[:with]).to eq("standard")
+ end
end
end
- context 'when using two-factor authentication' do
+ context 'when using two-factor authentication via OTP' do
let(:user) { create(:user, :two_factor) }
def authenticate_2fa(user_params)
@@ -117,6 +122,25 @@ describe SessionsController do
end
end
end
+
+ it "creates an audit log record" do
+ expect { authenticate_2fa(login: user.username, otp_attempt: user.current_otp) }.to change { SecurityEvent.count }.by(1)
+ expect(SecurityEvent.last.details[:with]).to eq("two-factor")
+ end
+ end
+
+ context 'when using two-factor authentication via U2F device' do
+ let(:user) { create(:user, :two_factor) }
+
+ def authenticate_2fa_u2f(user_params)
+ post(:create, { user: user_params }, { otp_user_id: user.id })
+ end
+
+ it "creates an audit log record" do
+ allow(U2fRegistration).to receive(:authenticate).and_return(true)
+ expect { authenticate_2fa_u2f(login: user.username, device_response: "{}") }.to change { SecurityEvent.count }.by(1)
+ expect(SecurityEvent.last.details[:with]).to eq("two-factor-via-u2f-device")
+ end
end
end
end
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
new file mode 100644
index 00000000000..4b858df52c9
--- /dev/null
+++ b/spec/factories/award_emoji.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :award_emoji do
+ name "thumbsup"
+ user
+ awardable factory: :issue
+
+ trait :upvote
+ trait :downvote do
+ name "thumbsdown"
+ end
+ end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index cd49e559b7d..fe05a0cfc00 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -16,7 +16,7 @@ FactoryGirl.define do
}
end
- commit factory: :ci_commit
+ pipeline factory: :ci_pipeline
trait :success do
status 'success'
@@ -43,7 +43,7 @@ FactoryGirl.define do
end
after(:build) do |build, evaluator|
- build.project = build.commit.project
+ build.project = build.pipeline.project
end
factory :ci_not_started_build do
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb
index 645cd7ae766..a039bef6f3c 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/commits.rb
@@ -17,30 +17,30 @@
#
FactoryGirl.define do
- factory :ci_empty_commit, class: Ci::Commit do
+ factory :ci_empty_pipeline, class: Ci::Pipeline do
sha '97de212e80737a608d939f648d959671fb0a0142'
project factory: :empty_project
- factory :ci_commit_without_jobs do
+ factory :ci_pipeline_without_jobs do
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) }
end
end
- factory :ci_commit_with_one_job do
+ factory :ci_pipeline_with_one_job do
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) }
end
end
- factory :ci_commit_with_two_jobs do
+ factory :ci_pipeline_with_two_job do
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) }
end
end
- factory :ci_commit do
+ factory :ci_pipeline do
after(:build) do |commit|
allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index b7c2b32cb13..1e5c479616c 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -3,12 +3,12 @@ FactoryGirl.define do
name 'default'
status 'success'
description 'commit status'
- commit factory: :ci_commit_with_one_job
+ pipeline factory: :ci_pipeline_with_one_job
started_at 'Tue, 26 Jan 2016 08:21:42 +0100'
finished_at 'Tue, 26 Jan 2016 08:23:42 +0100'
after(:build) do |build, evaluator|
- build.project = build.commit.project
+ build.project = build.pipeline.project
end
factory :generic_commit_status, class: GenericCommitStatus do
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index c32e205ee69..696cf276e57 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -16,8 +16,6 @@ FactoryGirl.define do
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
- factory :downvote_note, traits: [:award, :downvote]
- factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do
noteable nil
@@ -46,10 +44,6 @@ FactoryGirl.define do
system true
end
- trait :award do
- is_award true
- end
-
trait :downvote do
note "thumbsdown"
end
diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb
new file mode 100644
index 00000000000..df92b079581
--- /dev/null
+++ b/spec/factories/u2f_registrations.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :u2f_registration do
+ certificate { FFaker::BaconIpsum.characters(728) }
+ key_handle { FFaker::BaconIpsum.characters(86) }
+ public_key { FFaker::BaconIpsum.characters(88) }
+ counter 0
+ end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index a9b2148bd2a..c6f7869516e 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -15,14 +15,26 @@ FactoryGirl.define do
end
trait :two_factor do
+ two_factor_via_otp
+ end
+
+ trait :two_factor_via_otp do
before(:create) do |user|
- user.two_factor_enabled = true
+ user.otp_required_for_login = true
user.otp_secret = User.generate_otp_secret(32)
user.otp_grace_period_started_at = Time.now
user.generate_otp_backup_codes!
end
end
+ trait :two_factor_via_u2f do
+ transient { registrations_count 5 }
+
+ after(:create) do |user, evaluator|
+ create_list(:u2f_registration, evaluator.registrations_count, user: user)
+ end
+ end
+
factory :omniauth_user do
transient do
extern_uid '123456'
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index 938ccf2306b..efa6cbe5bb1 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -2,7 +2,7 @@ require 'ostruct'
FactoryGirl.define do
factory :wiki_page do
- page = OpenStruct.new(url_path: 'some-name')
+ page { OpenStruct.new(url_path: 'some-name') }
association :wiki, factory: :project_wiki, strategy: :build
initialize_with { new(wiki, page, true) }
end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 7bbe20fec43..a6198389f04 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -6,15 +6,15 @@ describe 'Admin Builds' do
end
describe 'GET /admin/builds' do
- let(:commit) { create(:ci_commit) }
+ let(:pipeline) { create(:ci_pipeline) }
context 'All tab' do
context 'when have builds' do
it 'shows all builds' do
- create(:ci_build, commit: commit, status: :pending)
- create(:ci_build, commit: commit, status: :running)
- create(:ci_build, commit: commit, status: :success)
- create(:ci_build, commit: commit, status: :failed)
+ create(:ci_build, pipeline: pipeline, status: :pending)
+ create(:ci_build, pipeline: pipeline, status: :running)
+ create(:ci_build, pipeline: pipeline, status: :success)
+ create(:ci_build, pipeline: pipeline, status: :failed)
visit admin_builds_path
@@ -39,9 +39,9 @@ describe 'Admin Builds' do
context 'Running tab' do
context 'when have running builds' do
it 'shows running builds' do
- build1 = create(:ci_build, commit: commit, status: :pending)
- build2 = create(:ci_build, commit: commit, status: :success)
- build3 = create(:ci_build, commit: commit, status: :failed)
+ build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build2 = create(:ci_build, pipeline: pipeline, status: :success)
+ build3 = create(:ci_build, pipeline: pipeline, status: :failed)
visit admin_builds_path(scope: :running)
@@ -55,7 +55,7 @@ describe 'Admin Builds' do
context 'when have no builds running' do
it 'shows a message' do
- create(:ci_build, commit: commit, status: :success)
+ create(:ci_build, pipeline: pipeline, status: :success)
visit admin_builds_path(scope: :running)
@@ -69,9 +69,9 @@ describe 'Admin Builds' do
context 'Finished tab' do
context 'when have finished builds' do
it 'shows finished builds' do
- build1 = create(:ci_build, commit: commit, status: :pending)
- build2 = create(:ci_build, commit: commit, status: :running)
- build3 = create(:ci_build, commit: commit, status: :success)
+ build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build2 = create(:ci_build, pipeline: pipeline, status: :running)
+ build3 = create(:ci_build, pipeline: pipeline, status: :success)
visit admin_builds_path(scope: :finished)
@@ -85,7 +85,7 @@ describe 'Admin Builds' do
context 'when have no builds finished' do
it 'shows a message' do
- create(:ci_build, commit: commit, status: :running)
+ create(:ci_build, pipeline: pipeline, status: :running)
visit admin_builds_path(scope: :finished)
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 8ebd4a6808e..9499cd4e025 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -8,8 +8,8 @@ describe "Admin Runners" do
describe "Runners page" do
before do
runner = FactoryGirl.create(:ci_runner)
- commit = FactoryGirl.create(:ci_commit)
- FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id)
+ pipeline = FactoryGirl.create(:ci_pipeline)
+ FactoryGirl.create(:ci_build, pipeline: pipeline, runner_id: runner.id)
visit admin_runners_path
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 96621843b30..1cb709c1de3 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -19,7 +19,7 @@ describe "Admin::Users", feature: true do
describe 'Two-factor Authentication filters' do
it 'counts users who have enabled 2FA' do
- create(:user, two_factor_enabled: true)
+ create(:user, :two_factor)
visit admin_users_path
@@ -29,7 +29,7 @@ describe "Admin::Users", feature: true do
end
it 'filters by users who have enabled 2FA' do
- user = create(:user, two_factor_enabled: true)
+ user = create(:user, :two_factor)
visit admin_users_path
click_link '2FA Enabled'
@@ -38,7 +38,7 @@ describe "Admin::Users", feature: true do
end
it 'counts users who have not enabled 2FA' do
- create(:user, two_factor_enabled: false)
+ create(:user)
visit admin_users_path
@@ -48,7 +48,7 @@ describe "Admin::Users", feature: true do
end
it 'filters by users who have not enabled 2FA' do
- user = create(:user, two_factor_enabled: false)
+ user = create(:user)
visit admin_users_path
click_link '2FA Disabled'
@@ -144,8 +144,8 @@ describe "Admin::Users", feature: true do
before { click_link 'Impersonate' }
it 'logs in as the user when impersonate is clicked' do
- page.within '.sidebar-user .username' do
- expect(page).to have_content(another_user.username)
+ page.within '.sidebar-wrapper' do
+ expect(page.find('.sidebar-user')['data-user']).to eql(another_user.username)
end
end
@@ -158,8 +158,8 @@ describe "Admin::Users", feature: true do
it 'can log out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click
- page.within '.sidebar-user .username' do
- expect(page).to have_content(@user.username)
+ page.within '.sidebar-wrapper' do
+ expect(page.find('.sidebar-user')['data-user']).to eql(@user.username)
end
end
@@ -173,7 +173,7 @@ describe "Admin::Users", feature: true do
describe 'Two-factor Authentication status' do
it 'shows when enabled' do
- @user.update_attribute(:two_factor_enabled, true)
+ @user.update_attribute(:otp_required_for_login, true)
visit admin_user_path(@user)
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index 7a05d30e8b5..b8ecc356b4d 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -5,8 +5,9 @@ describe "Builds" do
before do
login_as(:user)
- @commit = FactoryGirl.create :ci_commit
- @build = FactoryGirl.create :ci_build, commit: @commit
+ @commit = FactoryGirl.create :ci_pipeline
+ @build = FactoryGirl.create :ci_build, pipeline: @commit
+ @build2 = FactoryGirl.create :ci_build
@project = @commit.project
@project.team << [@user, :developer]
end
@@ -66,13 +67,24 @@ describe "Builds" do
end
describe "GET /:project/builds/:id" do
- before do
- visit namespace_project_build_path(@project.namespace, @project, @build)
+ context "Build from project" do
+ before do
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content @commit.sha[0..7] }
+ it { expect(page).to have_content @commit.git_commit_message }
+ it { expect(page).to have_content @commit.git_author_name }
end
- it { expect(page).to have_content @commit.sha[0..7] }
- it { expect(page).to have_content @commit.git_commit_message }
- it { expect(page).to have_content @commit.git_author_name }
+ context "Build from other project" do
+ before do
+ visit namespace_project_build_path(@project.namespace, @project, @build2)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
context "Download artifacts" do
before do
@@ -81,9 +93,7 @@ describe "Builds" do
end
it 'has button to download artifacts' do
- page.within('.artifacts') do
- expect(page).to have_content 'Download'
- end
+ expect(page).to have_content 'Download'
end
end
@@ -95,59 +105,144 @@ describe "Builds" do
end
it do
- page.within('.build-controls') do
- expect(page).to have_link 'Raw'
- end
+ expect(page).to have_link 'Raw'
end
end
end
describe "POST /:project/builds/:id/cancel" do
- before do
- @build.run!
- visit namespace_project_build_path(@project.namespace, @project, @build)
- click_link "Cancel"
+ context "Build from project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link "Cancel"
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content 'canceled' }
+ it { expect(page).to have_content 'Retry' }
end
- it { expect(page).to have_content 'canceled' }
- it { expect(page).to have_content 'Retry' }
+ context "Build from other project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2))
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
end
describe "POST /:project/builds/:id/retry" do
- before do
- @build.run!
- visit namespace_project_build_path(@project.namespace, @project, @build)
- click_link "Cancel"
- click_link 'Retry'
+ context "Build from project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link 'Cancel'
+ click_link 'Retry'
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content 'pending' }
+ it { expect(page).to have_content 'Cancel' }
end
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
+ context "Build from other project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link 'Cancel'
+ page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
end
describe "GET /:project/builds/:id/download" do
before do
@build.update_attributes(artifacts_file: artifacts_file)
visit namespace_project_build_path(@project.namespace, @project, @build)
- page.within('.artifacts') { click_link 'Download' }
+ click_link 'Download'
end
- it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) }
+ context "Build from other project" do
+ before do
+ @build2.update_attributes(artifacts_file: artifacts_file)
+ visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
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)
+ context "Build from project" 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)
+ page.within('.js-build-sidebar') { click_link 'Raw' }
+ end
+
+ it 'sends the right headers' do
+ expect(page.status_code).to eq(200)
+ 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
- it 'sends the right headers' do
- page.within('.build-controls') { click_link 'Raw' }
+ context "Build from other project" do
+ before do
+ Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ @build2.run!
+ @build2.trace = 'BUILD TRACE'
+ visit raw_namespace_project_build_path(@project.namespace, @project, @build2)
+ puts page.status_code
+ puts current_url
+ end
+
+ it 'sends the right headers' do
+ expect(page.status_code).to eq(404)
+ end
+ end
+ end
+
+ describe "GET /:project/builds/:id/trace.json" do
+ context "Build from project" do
+ before do
+ visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json)
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ end
+
+ context "Build from other project" do
+ before do
+ visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
+ end
+
+ describe "GET /:project/builds/:id/status" do
+ context "Build from project" do
+ before do
+ visit status_namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ end
+
+ context "Build from other project" do
+ before do
+ visit status_namespace_project_build_path(@project.namespace, @project, @build2)
+ end
- 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)
+ it { expect(page.status_code).to eq(404) }
end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 20f0b27bcc1..45e1a157a1f 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -8,15 +8,15 @@ describe 'Commits' do
describe 'CI' do
before do
login_as :user
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
- let!(:commit) do
- FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
+ let!(:pipeline) do
+ FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha
end
context 'commit status is Generic Commit Status' do
- let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
+ let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
before do
project.team << [@user, :reporter]
@@ -24,10 +24,10 @@ describe 'Commits' do
describe 'Commit builds' do
before do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
- it { expect(page).to have_content commit.sha[0..7] }
+ it { expect(page).to have_content pipeline.sha[0..7] }
it 'contains generic commit status build' do
page.within('.table-holder') do
@@ -39,7 +39,7 @@ describe 'Commits' do
end
context 'commit status is Ci Build' do
- let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+ let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
@@ -53,7 +53,7 @@ describe 'Commits' do
end
it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
+ page.within("//li[@id='commit-#{pipeline.short_sha}']") do
expect(page).to have_css(".ci-status-link")
end
end
@@ -61,12 +61,12 @@ describe 'Commits' do
describe 'Commit builds' do
before do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
- it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.git_commit_message }
- it { expect(page).to have_content commit.git_author_name }
+ it { expect(page).to have_content pipeline.sha[0..7] }
+ it { expect(page).to have_content pipeline.git_commit_message }
+ it { expect(page).to have_content pipeline.git_author_name }
end
context 'Download artifacts' do
@@ -75,7 +75,7 @@ describe 'Commits' do
end
it do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Download artifacts'
expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
end
@@ -83,7 +83,7 @@ describe 'Commits' do
describe 'Cancel all builds' do
it 'cancels commit' do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
end
@@ -91,7 +91,7 @@ describe 'Commits' do
describe 'Cancel build' do
it 'cancels build' do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Cancel'
expect(page).to have_content 'canceled'
end
@@ -100,13 +100,13 @@ describe 'Commits' do
describe '.gitlab-ci.yml not found warning' do
context 'ci builds enabled' do
it "does not show warning" do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
it 'shows warning' do
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
+ stub_ci_pipeline_yaml_file(nil)
+ visit ci_status_path(pipeline)
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
end
end
@@ -114,8 +114,8 @@ describe 'Commits' do
context 'ci builds disabled' do
before do
stub_ci_builds_disabled
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
+ stub_ci_pipeline_yaml_file(nil)
+ visit ci_status_path(pipeline)
end
it 'does not show warning' do
@@ -129,13 +129,13 @@ describe 'Commits' do
before do
project.team << [@user, :reporter]
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
it do
- expect(page).to have_content commit.sha[0..7]
- expect(page).to have_content commit.git_commit_message
- expect(page).to have_content commit.git_author_name
+ expect(page).to have_content pipeline.sha[0..7]
+ expect(page).to have_content pipeline.git_commit_message
+ expect(page).to have_content pipeline.git_author_name
expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry failed')
@@ -148,13 +148,13 @@ describe 'Commits' do
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
it do
- expect(page).to have_content commit.sha[0..7]
- expect(page).to have_content commit.git_commit_message
- expect(page).to have_content commit.git_author_name
+ expect(page).to have_content pipeline.sha[0..7]
+ expect(page).to have_content pipeline.git_commit_message
+ expect(page).to have_content pipeline.git_author_name
expect(page).not_to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry failed')
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
new file mode 100644
index 00000000000..365cb445df1
--- /dev/null
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+feature 'Tooltips on .timeago dates', feature: true, js: true do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+ let(:created_date) { Date.yesterday.to_time }
+ let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P UTC') }
+
+ context 'on the activity tab' do
+ before do
+ project.team << [user, :master]
+
+ Event.create( project: project, author_id: user.id, action: Event::JOINED,
+ updated_at: created_date, created_at: created_date)
+
+ login_as user
+ visit user_path(user)
+ wait_for_ajax()
+
+ page.find('.js-timeago').hover
+ end
+
+ it 'has the datetime formated correctly' do
+ expect(page).to have_selector('.local-timeago', text: expected_format)
+ end
+ end
+
+ context 'on the snippets tab' do
+ before do
+ project.team << [user, :master]
+ create(:snippet, author: user, updated_at: created_date, created_at: created_date)
+
+ login_as user
+ visit user_snippets_path(user)
+ wait_for_ajax()
+
+ page.find('.js-timeago').hover
+ end
+
+ it 'has the datetime formated correctly' do
+ expect(page).to have_selector('.local-timeago', text: expected_format)
+ end
+ end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 41af789aae2..07a854ea014 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -28,7 +28,6 @@ describe 'Awards Emoji', feature: true do
end
context 'click the thumbsup emoji' do
-
it 'should increment the thumbsup emoji', js: true do
find('[data-emoji="thumbsup"]').click
sleep 2
@@ -41,7 +40,6 @@ describe 'Awards Emoji', feature: true do
end
context 'click the thumbsdown emoji' do
-
it 'should increment the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click
sleep 2
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
new file mode 100644
index 00000000000..63efecf8780
--- /dev/null
+++ b/spec/features/issues/award_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'Issue awards', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+
+ describe 'logged in' do
+ before do
+ login_as(user)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'should add award to issue' do
+ first('.js-emoji-btn').click
+ expect(page).to have_selector('.js-emoji-btn.active')
+ expect(first('.js-emoji-btn')).to have_content '1'
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(first('.js-emoji-btn')).to have_content '1'
+ end
+
+ it 'should remove award from issue' do
+ first('.js-emoji-btn').click
+ find('.js-emoji-btn.active').click
+ expect(first('.js-emoji-btn')).to have_content '0'
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(first('.js-emoji-btn')).to have_content '0'
+ end
+
+ it 'should only have one menu on the page' do
+ first('.js-add-award').click
+ expect(page).to have_selector('.emoji-menu')
+
+ expect(page).to have_selector('.emoji-menu', count: 1)
+ end
+ end
+
+ describe 'logged out' do
+ before do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'should not see award menu button' do
+ expect(page).not_to have_selector('.js-award-holder')
+ end
+ end
+end
diff --git a/spec/features/issues/bulk_assigment_labels_spec.rb b/spec/features/issues/bulk_assigment_labels_spec.rb
new file mode 100644
index 00000000000..c58b87281a3
--- /dev/null
+++ b/spec/features/issues/bulk_assigment_labels_spec.rb
@@ -0,0 +1,196 @@
+require 'rails_helper'
+
+feature 'Issues > Labels bulk assignment', feature: true do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let!(:project) { create(:project) }
+ let!(:issue1) { create(:issue, project: project, title: "Issue 1") }
+ let!(:issue2) { create(:issue, project: project, title: "Issue 2") }
+ let!(:bug) { create(:label, project: project, title: 'bug') }
+ let!(:feature) { create(:label, project: project, title: 'feature') }
+
+ context 'as a allowed user', js: true do
+ before do
+ project.team << [user, :master]
+
+ login_as user
+ end
+
+ context 'can bulk assign' do
+ before do
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ context 'a label' do
+ context 'to all issues' do
+ before do
+ check 'check_all_issues'
+ open_labels_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ end
+ end
+
+ context 'to a issue' do
+ before do
+ check "selected_issue_#{issue1.id}"
+ open_labels_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ end
+ end
+ end
+
+ context 'multiple labels' do
+ context 'to all issues' do
+ before do
+ check 'check_all_issues'
+ open_labels_dropdown ['bug', 'feature']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ end
+ end
+
+ context 'to a issue' do
+ before do
+ check "selected_issue_#{issue1.id}"
+ open_labels_dropdown ['bug', 'feature']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'feature'
+ end
+ end
+ end
+ end
+
+ context 'can bulk un-assign' do
+ context 'all labels to all issues' do
+ before do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << bug
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+
+ check 'check_all_issues'
+ unmark_labels_in_dropdown ['bug', 'feature']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'feature'
+ end
+ end
+
+ context 'a label to a issue' do
+ before do
+ issue1.labels << bug
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+
+ check_issue issue1
+ unmark_labels_in_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ end
+ end
+
+ context 'a label and keep the others label' do
+ before do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << bug
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+
+ check_issue issue1
+ check_issue issue2
+ unmark_labels_in_dropdown ['bug']
+ update_issues
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ end
+ end
+ end
+ end
+
+ context 'as a guest' do
+ before do
+ login_as user
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ context 'cannot bulk assign labels' do
+ it do
+ expect(page).not_to have_css '.check_all_issues'
+ expect(page).not_to have_css '.issue-check'
+ end
+ end
+ end
+
+ def open_labels_dropdown(items = [], unmark = false)
+ page.within('.issues_bulk_update') do
+ click_button 'Label'
+ wait_for_ajax
+ items.map do |item|
+ click_link item
+ end
+ if unmark
+ items.map do |item|
+ click_link item
+ end
+ end
+ end
+ end
+
+ def unmark_labels_in_dropdown(items = [])
+ open_labels_dropdown(items, true)
+ end
+
+ def check_issue(issue)
+ page.within('.issues-list') do
+ check "selected_issue_#{issue.id}"
+ end
+ end
+
+ def update_issues
+ click_button 'Update issues'
+ wait_for_ajax
+ end
+end
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
index 7f654684143..16c619c9288 100644
--- a/spec/features/issues/filter_by_labels_spec.rb
+++ b/spec/features/issues/filter_by_labels_spec.rb
@@ -54,6 +54,11 @@ feature 'Issue filtering by Labels', feature: true do
expect(find('.filtered-labels')).not_to have_content "feature"
expect(find('.filtered-labels')).not_to have_content "enhancement"
end
+
+ it 'should remove label "bug"' do
+ first('.js-label-filter-remove').click
+ expect(find('.filtered-labels')).to have_no_content "bug"
+ end
end
context 'filter by label feature', js: true do
@@ -135,6 +140,11 @@ feature 'Issue filtering by Labels', feature: true do
it 'should not show label "bug" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "bug"
end
+
+ it 'should remove label "enhancement"' do
+ first('.js-label-filter-remove').click
+ expect(find('.filtered-labels')).to have_no_content "enhancement"
+ end
end
context 'filter by label enhancement and bug in issues list', js: true do
@@ -164,4 +174,44 @@ feature 'Issue filtering by Labels', feature: true do
expect(find('.filtered-labels')).not_to have_content "feature"
end
end
+
+ context 'remove filtered labels', js: true do
+ before do
+ page.within '.labels-filter' do
+ click_button 'Label'
+ click_link 'bug'
+ find('.dropdown-menu-close').click
+ end
+
+ page.within '.filtered-labels' do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ it 'should allow user to remove filtered labels' do
+ page.within '.filtered-labels' do
+ first('.js-label-filter-remove').click
+ expect(page).not_to have_content 'bug'
+ end
+
+ page.within '.labels-filter' do
+ expect(page).not_to have_content 'bug'
+ end
+ end
+ end
+
+ context 'dropdown filtering', js: true do
+ it 'should filter by label name' do
+ page.within '.labels-filter' do
+ click_button 'Label'
+ wait_for_ajax
+ fill_in 'label-name', with: 'bug'
+
+ page.within '.dropdown-content' do
+ expect(page).not_to have_content 'enhancement'
+ expect(page).to have_content 'bug'
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 7efbaaa048c..1f0594e6b02 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -294,40 +294,4 @@ describe 'Filter issues', feature: true do
end
end
end
-
- describe 'filter by any author', js: true do
- before do
- user2 = create(:user, name: "tester")
- create(:issue, project: project, author: user)
- create(:issue, project: project, author: user2)
-
- visit namespace_project_issues_path(project.namespace, project)
- end
-
- it 'should show filter by any author link' do
- click_button "Author"
- fill_in "Search authors", with: "tester"
-
- page.within ".dropdown-menu-author" do
- expect(page).to have_content "tester"
- end
- end
-
- it 'should show filter issues by any author' do
- page.within '.issues-list' do
- expect(page).to have_selector ".issue", count: 2
- end
-
- click_button "Author"
- fill_in "Search authors", with: "tester"
-
- page.within ".dropdown-menu-author" do
- click_link "tester"
- end
-
- page.within '.issues-list' do
- expect(page).to have_selector ".issue", count: 1
- end
- end
- end
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 466a6f7dfa7..ddbd69b2891 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
feature 'Multiple issue updating from issues#index', feature: true do
+ include WaitForAjax
+
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
@@ -24,9 +26,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
it 'should be set to open' do
create_closed
- visit namespace_project_issues_path(project.namespace, project)
-
- find('.issues-state-filters a', text: 'Closed').click
+ visit namespace_project_issues_path(project.namespace, project, state: 'closed')
find('#check_all_issues').click
find('.js-issue-status').click
@@ -42,7 +42,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click
- find('.js-update-assignee').click
+ click_update_assignee_button
find('.dropdown-menu-user-link', text: user.username).click
click_update_issues_button
@@ -57,14 +57,11 @@ feature 'Multiple issue updating from issues#index', feature: true do
visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click
- find('.js-update-assignee').click
+ click_update_assignee_button
click_link 'Unassigned'
click_update_issues_button
-
- within first('.issue .controls') do
- expect(page).to have_no_selector('.author_link')
- end
+ expect(find('.issue:first-child .controls')).not_to have_css('.author_link')
end
end
@@ -95,7 +92,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
find('.dropdown-menu-milestone a', text: "No Milestone").click
click_update_issues_button
- expect(first('.issue')).not_to have_content milestone.title
+ expect(find('.issue:first-child')).not_to have_content milestone.title
end
end
@@ -111,7 +108,13 @@ feature 'Multiple issue updating from issues#index', feature: true do
create(:issue, project: project, milestone: milestone)
end
+ def click_update_assignee_button
+ find('.js-update-assignee').click
+ wait_for_ajax
+ end
+
def click_update_issues_button
find('.update_selected_issues').click
+ wait_for_ajax
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 9271964166a..f6fb6a72d22 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -75,12 +75,13 @@ describe 'Issues', feature: true do
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
+ find('#issuable-due-date').click
- page.within '.datepicker' do
+ page.within '.ui-datepicker' do
click_link date.day
end
- expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+ expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Submit issue'
@@ -100,18 +101,19 @@ describe 'Issues', feature: true do
it 'should save with due date' do
date = Date.today.at_beginning_of_month
- expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+ expect(find('#issuable-due-date').value).to eq date.to_s
date = date.tomorrow
fill_in 'issue_title', with: 'bug 345'
fill_in 'issue_description', with: 'bug description'
+ find('#issuable-due-date').click
- page.within '.datepicker' do
+ page.within '.ui-datepicker' do
click_link date.day
end
- expect(find('#issuable-due-date', visible: false).value).to eq date.to_s
+ expect(find('#issuable-due-date').value).to eq date.to_s
click_button 'Save changes'
@@ -125,7 +127,7 @@ describe 'Issues', feature: true do
describe 'Issue info' do
it 'excludes award_emoji from comment count' do
issue = create(:issue, author: @user, assignee: @user, project: project, title: 'foobar')
- create(:upvote_note, noteable: issue, project: project)
+ create(:award_emoji, awardable: issue)
visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
@@ -365,13 +367,9 @@ describe 'Issues', feature: true do
page.within('.assignee') do
expect(page).to have_content "#{@user.name}"
- end
- find('.block.assignee .edit-link').click
- sleep 2 # wait for ajax stuff to complete
- first('.dropdown-menu-user-link').click
- sleep 2
- page.within('.assignee') do
+ click_link 'Edit'
+ click_link 'Unassigned'
expect(page).to have_content 'No assignee'
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index c1b178c3b6c..72b5ff231f7 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -33,11 +33,11 @@ feature 'Login', feature: true do
before do
login_with(user, remember: true)
- expect(page).to have_content('Two-factor Authentication')
+ expect(page).to have_content('Two-Factor Authentication')
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
@@ -143,12 +143,12 @@ feature 'Login', feature: true do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
- expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must enable Two-factor Authentication for your account before')
+ expect(current_path).to eq profile_two_factor_auth_path
+ expect(page).to have_content('You must enable Two-Factor Authentication for your account before')
end
- it 'disallows skipping two-factor configuration' do
- expect(current_path).to eq new_profile_two_factor_auth_path
+ it 'allows skipping two-factor configuration', js: true do
+ expect(current_path).to eq profile_two_factor_auth_path
click_link 'Configure it later'
expect(current_path).to eq root_path
@@ -159,26 +159,26 @@ feature 'Login', feature: true do
let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
it 'redirects to two-factor configuration page' do
- expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must enable Two-factor Authentication for your account.')
+ expect(current_path).to eq profile_two_factor_auth_path
+ expect(page).to have_content('You must enable Two-Factor Authentication for your account.')
end
- it 'disallows skipping two-factor configuration' do
- expect(current_path).to eq new_profile_two_factor_auth_path
+ it 'disallows skipping two-factor configuration', js: true do
+ expect(current_path).to eq profile_two_factor_auth_path
expect(page).not_to have_link('Configure it later')
end
end
end
- context 'without grace pariod defined' do
+ context 'without grace period defined' do
before(:each) do
stub_application_setting(two_factor_grace_period: 0)
login_with(user)
end
it 'redirects to two-factor configuration page' do
- expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must enable Two-factor Authentication for your account.')
+ expect(current_path).to eq profile_two_factor_auth_path
+ expect(page).to have_content('You must enable Two-Factor Authentication for your account.')
end
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 1d892fe1a55..09ccc77c101 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -165,22 +165,32 @@ 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 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 'adds _blank to target attribute for external links' do
+ link = doc.at_css('a:contains("Google")')
+
+ expect(link.attr('target')).to match('_blank')
+ end
+
it 'ignores internal link' do
link = doc.at_css('a:contains("GitLab Root")')
+
expect(link.attr('rel')).not_to match 'nofollow'
+ expect(link.attr('target')).not_to match '_blank'
end
end
end
- before(:all) do
+ before do
@feat = MarkdownFeature.new
# `markdown` helper expects a `@project` variable
@@ -188,7 +198,7 @@ describe 'GitLab Markdown', feature: true do
end
context 'default pipeline' do
- before(:all) do
+ before do
@html = markdown(@feat.raw_markdown)
end
@@ -231,13 +241,14 @@ describe 'GitLab Markdown', feature: true do
context 'wiki pipeline' do
before do
@project_wiki = @feat.project_wiki
+ @project_wiki_page = @feat.project_wiki_page
file = Gollum::File.new(@project_wiki.wiki)
expect(file).to receive(:path).and_return('images/example.jpg')
expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file)
allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
- @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki })
+ @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki, page_slug: @project_wiki_page.slug })
end
it_behaves_like 'all pipelines'
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
new file mode 100644
index 00000000000..007f67d6080
--- /dev/null
+++ b/spec/features/merge_requests/award_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'Merge request awards', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ describe 'logged in' do
+ before do
+ login_as(user)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'should add award to merge request' do
+ first('.js-emoji-btn').click
+ expect(page).to have_selector('.js-emoji-btn.active')
+ expect(first('.js-emoji-btn')).to have_content '1'
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ expect(first('.js-emoji-btn')).to have_content '1'
+ end
+
+ it 'should remove award from merge request' do
+ first('.js-emoji-btn').click
+ find('.js-emoji-btn.active').click
+ expect(first('.js-emoji-btn')).to have_content '0'
+
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ expect(first('.js-emoji-btn')).to have_content '0'
+ end
+
+ it 'should only have one menu on the page' do
+ first('.js-add-award').click
+ expect(page).to have_selector('.emoji-menu')
+
+ expect(page).to have_selector('.emoji-menu', count: 1)
+ end
+ end
+
+ describe 'logged out' do
+ before do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'should not see award menu button' do
+ expect(page).not_to have_selector('.js-award-holder')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index edc0bdec3db..b4d2201c729 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -29,9 +29,9 @@ feature 'Merge request created from fork' do
include WaitForAjax
given(:pipeline) do
- create(:ci_commit_with_two_jobs, project: fork_project,
- sha: merge_request.last_commit.id,
- ref: merge_request.source_branch)
+ create(:ci_pipeline_with_two_job, project: fork_project,
+ sha: merge_request.last_commit.id,
+ ref: merge_request.source_branch)
end
background { pipeline.create_builds(user) }
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index 7aa7eb965e9..c5e6412d7bf 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -12,8 +12,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
context "Active build for Merge Request" do
- let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
- let!(:ci_build) { create(:ci_build, commit: ci_commit) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
login_as user
@@ -47,8 +47,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
merge_user: user, title: "MepMep", merge_when_build_succeeds: true)
end
- let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
- let!(:ci_build) { create(:ci_build, commit: ci_commit) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
login_as user
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
new file mode 100644
index 00000000000..65e9185ec24
--- /dev/null
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
@@ -0,0 +1,105 @@
+require 'spec_helper'
+
+feature 'Only allow merge requests to be merged if the build succeeds', feature: true do
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: project) }
+
+ before do
+ login_as merge_request.author
+
+ project.team << [merge_request.author, :master]
+ end
+
+ context 'project does not have CI enabled' do
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+
+ context 'when project has CI enabled' do
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+
+ context 'when merge requests can only be merged if the build succeeds' do
+ before do
+ project.update_attribute(:only_allow_merge_if_build_succeeds, true)
+ end
+
+ context 'when CI is running' do
+ before { pipeline.update_column(:status, :running) }
+
+ it 'does not allow to merge immediately' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Merge When Build Succeeds'
+ expect(page).not_to have_button 'Select Merge Moment'
+ end
+ end
+
+ context 'when CI failed' do
+ before { pipeline.update_column(:status, :failed) }
+
+ it 'does not allow MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).not_to have_button 'Accept Merge Request'
+ expect(page).to have_content('Please retry the build or push a new commit to fix the failure.')
+ end
+ end
+
+ context 'when CI succeeded' do
+ before { pipeline.update_column(:status, :success) }
+
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+ end
+
+ context 'when merge requests can be merged when the build failed' do
+ before do
+ project.update_attribute(:only_allow_merge_if_build_succeeds, false)
+ end
+
+ context 'when CI is running' do
+ before { pipeline.update_column(:status, :running) }
+
+ it 'allows MR to be merged immediately', js: true do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Merge When Build Succeeds'
+
+ click_button 'Select Merge Moment'
+ expect(page).to have_content 'Merge Immediately'
+ end
+ end
+
+ context 'when CI failed' do
+ before { pipeline.update_column(:status, :failed) }
+
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+
+ context 'when CI succeeded' do
+ before { pipeline.update_column(:status, :success) }
+
+ it 'allows MR to be merged' do
+ visit_merge_request(merge_request)
+
+ expect(page).to have_button 'Accept Merge Request'
+ end
+ end
+ end
+ end
+
+ def visit_merge_request(merge_request)
+ visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 2835cf44494..737efcef45d 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -4,20 +4,6 @@ describe 'Comments', feature: true do
include RepoHelpers
include WaitForAjax
- describe 'On merge requests page', feature: true do
- it 'excludes award_emoji from comment count' do
- merge_request = create(:merge_request)
- project = merge_request.source_project
- create(:upvote_note, noteable: merge_request, project: project)
-
- login_as :admin
- visit namespace_project_merge_requests_path(project.namespace, project)
-
- expect(merge_request.mr_and_commit_notes.count).to eq 1
- expect(page.all('.merge-request-no-comments').first.text).to eq "0"
- end
- end
-
describe 'On a merge request', js: true, feature: true do
let!(:project) { create(:project) }
let!(:merge_request) do
@@ -147,17 +133,6 @@ describe 'Comments', feature: true do
end
end
end
-
- describe 'comment info' do
- it 'excludes award_emoji from comment count' do
- create(:upvote_note, noteable: merge_request, project: project)
-
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
-
- expect(merge_request.mr_and_commit_notes.count).to eq 2
- expect(find('.notes-tab span.badge').text).to eq "1"
- end
- end
end
describe 'On a merge request diff', js: true, feature: true do
diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb
index acd6fb3538c..98703ef3ac4 100644
--- a/spec/features/pipelines_spec.rb
+++ b/spec/features/pipelines_spec.rb
@@ -12,7 +12,7 @@ describe "Pipelines" do
end
describe 'GET /:project/pipelines' do
- let!(:pipeline) { create(:ci_commit, project: project, ref: 'master', status: 'running') }
+ let!(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', status: 'running') }
[:all, :running, :branches].each do |scope|
context "displaying #{scope}" do
@@ -31,7 +31,7 @@ describe "Pipelines" do
end
context 'cancelable pipeline' do
- let!(:running) { create(:ci_build, :running, commit: pipeline, stage: 'test', commands: 'test') }
+ let!(:running) { create(:ci_build, :running, pipeline: pipeline, stage: 'test', commands: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
@@ -47,7 +47,7 @@ describe "Pipelines" do
end
context 'retryable pipelines' do
- let!(:failed) { create(:ci_build, :failed, commit: pipeline, stage: 'test', commands: 'test') }
+ let!(:failed) { create(:ci_build, :failed, pipeline: pipeline, stage: 'test', commands: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
@@ -64,7 +64,7 @@ describe "Pipelines" do
context 'for generic statuses' do
context 'when running' do
- let!(:running) { create(:generic_commit_status, status: 'running', commit: pipeline, stage: 'test') }
+ let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
@@ -78,7 +78,7 @@ describe "Pipelines" do
end
context 'when failed' do
- let!(:running) { create(:generic_commit_status, status: 'failed', commit: pipeline, stage: 'test') }
+ let!(:running) { create(:generic_commit_status, status: 'failed', pipeline: pipeline, stage: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
@@ -94,7 +94,7 @@ describe "Pipelines" do
context 'downloadable pipelines' do
context 'with artifacts' do
- let!(:with_artifacts) { create(:ci_build, :artifacts, :success, commit: pipeline, name: 'rspec tests', stage: 'test') }
+ let!(:with_artifacts) { create(:ci_build, :artifacts, :success, pipeline: pipeline, name: 'rspec tests', stage: 'test') }
before { visit namespace_project_pipelines_path(project.namespace, project) }
@@ -103,7 +103,7 @@ describe "Pipelines" do
end
context 'without artifacts' do
- let!(:without_artifacts) { create(:ci_build, :success, commit: pipeline, name: 'rspec', stage: 'test') }
+ let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
it { expect(page).not_to have_selector('.build-artifacts') }
end
@@ -111,13 +111,13 @@ describe "Pipelines" do
end
describe 'GET /:project/pipelines/:id' do
- let(:pipeline) { create(:ci_commit, project: project, ref: 'master') }
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
before do
- @success = create(:ci_build, :success, commit: pipeline, stage: 'build', name: 'build')
- @failed = create(:ci_build, :failed, commit: pipeline, stage: 'test', name: 'test', commands: 'test')
- @running = create(:ci_build, :running, commit: pipeline, stage: 'deploy', name: 'deploy')
- @external = create(:generic_commit_status, status: 'success', commit: pipeline, name: 'jenkins', stage: 'external')
+ @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
+ @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+ @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
@@ -165,9 +165,9 @@ describe "Pipelines" do
before { fill_in('Create for', with: 'master') }
context 'with gitlab-ci.yml' do
- before { stub_ci_commit_to_return_yaml_file }
+ before { stub_ci_pipeline_to_return_yaml_file }
- it { expect{ click_on 'Create pipeline' }.to change{ Ci::Commit.count }.by(1) }
+ it { expect{ click_on 'Create pipeline' }.to change{ Ci::Pipeline.count }.by(1) }
end
context 'without gitlab-ci.yml' do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index 40ba0bdc115..15c381c0f5a 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -11,9 +11,9 @@ feature 'project commit builds' do
context 'when no builds triggered yet' do
background do
- create(:ci_commit, project: project,
- sha: project.commit.sha,
- ref: 'master')
+ create(:ci_pipeline, project: project,
+ sha: project.commit.sha,
+ ref: 'master')
end
scenario 'user views commit builds page' do
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
new file mode 100644
index 00000000000..461f1737928
--- /dev/null
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+feature 'Issue prioritization', feature: true do
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+
+ # Labels
+ let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
+ let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) }
+ let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) }
+ let(:label_4) { create(:label, title: 'label_4', project: project, priority: 4) }
+ let(:label_5) { create(:label, title: 'label_5', project: project) } # no priority
+
+ # According to https://gitlab.com/gitlab-org/gitlab-ce/issues/14189#note_4360653
+ context 'when issues have one label' do
+ scenario 'Are sorted properly' do
+
+ # Issues
+ issue_1 = create(:issue, title: 'issue_1', project: project)
+ issue_2 = create(:issue, title: 'issue_2', project: project)
+ issue_3 = create(:issue, title: 'issue_3', project: project)
+ issue_4 = create(:issue, title: 'issue_4', project: project)
+ issue_5 = create(:issue, title: 'issue_5', project: project)
+
+ # Assign labels to issues disorderly
+ issue_4.labels << label_1
+ issue_3.labels << label_2
+ issue_5.labels << label_3
+ issue_2.labels << label_4
+ issue_1.labels << label_5
+
+ login_as user
+ visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+
+ # Ensure we are indicating that issues are sorted by priority
+ expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+
+ page.within('.issues-holder') do
+ issue_titles = all('.issues-list .issue-title-text').map(&:text)
+
+ expect(issue_titles).to eq(['issue_4', 'issue_3', 'issue_5', 'issue_2', 'issue_1'])
+ end
+ end
+ end
+
+ context 'when issues have multiple labels' do
+ scenario 'Are sorted properly' do
+
+ # Issues
+ issue_1 = create(:issue, title: 'issue_1', project: project)
+ issue_2 = create(:issue, title: 'issue_2', project: project)
+ issue_3 = create(:issue, title: 'issue_3', project: project)
+ issue_4 = create(:issue, title: 'issue_4', project: project)
+ issue_5 = create(:issue, title: 'issue_5', project: project)
+ issue_6 = create(:issue, title: 'issue_6', project: project)
+ issue_7 = create(:issue, title: 'issue_7', project: project)
+ issue_8 = create(:issue, title: 'issue_8', project: project)
+
+ # Assign labels to issues disorderly
+ issue_5.labels << label_1 # 1
+ issue_5.labels << label_2
+ issue_8.labels << label_1 # 2
+ issue_1.labels << label_2 # 3
+ issue_1.labels << label_3
+ issue_3.labels << label_2 # 4
+ issue_3.labels << label_4
+ issue_7.labels << label_2 # 5
+ issue_2.labels << label_3 # 6
+ issue_4.labels << label_4 # 7
+ issue_6.labels << label_5 # 8 - No priority
+
+ login_as user
+ visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
+
+ expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+
+ page.within('.issues-holder') do
+ issue_titles = all('.issues-list .issue-title-text').map(&:text)
+
+ expect(issue_titles[0..1]).to contain_exactly('issue_5', 'issue_8')
+ expect(issue_titles[2..4]).to contain_exactly('issue_1', 'issue_3', 'issue_7')
+ expect(issue_titles[5..-1]).to eq(['issue_2', 'issue_4', 'issue_6'])
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
new file mode 100644
index 00000000000..8550d279d09
--- /dev/null
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+feature 'Prioritize labels', feature: true do
+ include WaitForAjax
+
+ context 'when project belongs to user' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, name: 'test', namespace: user.namespace) }
+
+ scenario 'user can prioritize a label', js: true do
+ bug = create(:label, title: 'bug')
+ wontfix = create(:label, title: 'wontfix')
+
+ project.labels << bug
+ project.labels << wontfix
+
+ login_as user
+ visit namespace_project_labels_path(project.namespace, project)
+
+ expect(page).to have_content('No prioritized labels yet')
+
+ page.within('.other-labels') do
+ first('.js-toggle-priority').click
+ wait_for_ajax
+ expect(page).not_to have_content('bug')
+ end
+
+ page.within('.prioritized-labels') do
+ expect(page).not_to have_content('No prioritized labels yet')
+ expect(page).to have_content('bug')
+ end
+ end
+
+ scenario 'user can unprioritize a label', js: true do
+ bug = create(:label, title: 'bug', priority: 1)
+ wontfix = create(:label, title: 'wontfix')
+
+ project.labels << bug
+ project.labels << wontfix
+
+ login_as user
+ visit namespace_project_labels_path(project.namespace, project)
+
+ expect(page).to have_content('bug')
+
+ page.within('.prioritized-labels') do
+ first('.js-toggle-priority').click
+ wait_for_ajax
+ expect(page).not_to have_content('bug')
+ end
+
+ page.within('.other-labels') do
+ expect(page).to have_content('bug')
+ expect(page).to have_content('wontfix')
+ end
+ end
+
+ scenario 'user can sort prioritized labels and persist across reloads', js: true do
+ bug = create(:label, title: 'bug', priority: 1)
+ wontfix = create(:label, title: 'wontfix', priority: 2)
+
+ project.labels << bug
+ project.labels << wontfix
+
+ login_as user
+ visit namespace_project_labels_path(project.namespace, project)
+
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'wontfix'
+
+ # Sort labels
+ find("#label_#{bug.id}").drag_to find("#label_#{wontfix.id}")
+
+ page.within('.prioritized-labels') do
+ expect(first('li')).to have_content('wontfix')
+ expect(page.all('li').last).to have_content('bug')
+ end
+
+ visit current_url
+
+ page.within('.prioritized-labels') do
+ expect(first('li')).to have_content('wontfix')
+ expect(page.all('li').last).to have_content('bug')
+ end
+ end
+ end
+
+ context 'as a guest' do
+ it 'can not prioritize labels' do
+ user = create(:user)
+ guest = create(:user)
+ project = create(:project, name: 'test', namespace: user.namespace)
+
+ create(:label, title: 'bug')
+
+ login_as guest
+ visit namespace_project_labels_path(project.namespace, project)
+
+ expect(page).not_to have_css('.prioritized-labels')
+ end
+ end
+
+ context 'as a non signed in user' do
+ it 'can not prioritize labels' do
+ user = create(:user)
+ project = create(:project, name: 'test', namespace: user.namespace)
+
+ create(:label, title: 'bug')
+
+ visit namespace_project_labels_path(project.namespace, project)
+
+ expect(page).not_to have_css('.prioritized-labels')
+ end
+ end
+end
diff --git a/spec/features/project/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index 54aa9c66a08..54aa9c66a08 100644
--- a/spec/features/project/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 4def4f99bc0..c5f741709ad 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -142,8 +142,8 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/builds/:id" do
- let(:commit) { create(:ci_commit, project: project) }
- let(:build) { create(:ci_build, commit: commit) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
subject { namespace_project_build_path(project.namespace, project, build.id) }
context "when allowed for public" do
diff --git a/spec/features/todos/target_state_spec.rb b/spec/features/todos/target_state_spec.rb
index 72491ac7e61..32fa88a2b21 100644
--- a/spec/features/todos/target_state_spec.rb
+++ b/spec/features/todos/target_state_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
feature 'Todo target states', feature: true do
let(:user) { create(:user) }
let(:author) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
before do
login_as user
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 4e627753cc7..8e1833a069e 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Dashboard Todos', feature: true do
let(:user) { create(:user) }
let(:author) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:issue) { create(:issue) }
describe 'GET /dashboard/todos' do
@@ -49,7 +49,7 @@ describe 'Dashboard Todos', feature: true do
note1 = create(:note_on_issue, note: "Hello #{label1.to_reference(format: :name)}", noteable_id: issue.id, noteable_type: 'Issue', project: issue.project)
create(:todo, :mentioned, project: project, target: issue, user: user, note_id: note1.id)
- project2 = create(:project)
+ project2 = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
label2 = create(:label, project: project2)
issue2 = create(:issue, project: project2)
note2 = create(:note_on_issue, note: "Test #{label2.to_reference(format: :name)}", noteable_id: issue2.id, noteable_type: 'Issue', project: project2)
@@ -98,5 +98,18 @@ describe 'Dashboard Todos', feature: true do
end
end
end
+
+ context 'User has a Todo in a project pending deletion' do
+ before do
+ deleted_project = create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC, pending_delete: true)
+ create(:todo, :mentioned, user: user, project: deleted_project, target: issue, author: author)
+ 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
end
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
new file mode 100644
index 00000000000..366a90228b1
--- /dev/null
+++ b/spec/features/u2f_spec.rb
@@ -0,0 +1,239 @@
+require 'spec_helper'
+
+feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
+ def register_u2f_device(u2f_device = nil)
+ u2f_device ||= FakeU2fDevice.new(page)
+ u2f_device.respond_to_u2f_registration
+ click_on 'Setup New U2F Device'
+ expect(page).to have_content('Your device was successfully set up')
+ click_on 'Register U2F Device'
+ u2f_device
+ end
+
+ describe "registration" do
+ let(:user) { create(:user) }
+ before { login_as(user) }
+
+ describe 'when 2FA via OTP is disabled' do
+ it 'allows registering a new device' do
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+
+ register_u2f_device
+
+ expect(page.body).to match('Your U2F device was registered')
+ end
+
+ it 'allows registering more than one device' do
+ visit profile_account_path
+
+ # First device
+ click_on 'Enable Two-Factor Authentication'
+ register_u2f_device
+ expect(page.body).to match('Your U2F device was registered')
+
+ # Second device
+ click_on 'Manage Two-Factor Authentication'
+ register_u2f_device
+ expect(page.body).to match('Your U2F device was registered')
+ click_on 'Manage Two-Factor Authentication'
+
+ expect(page.body).to match('You have 2 U2F devices registered')
+ end
+ end
+
+ describe 'when 2FA via OTP is enabled' do
+ before { user.update_attributes(otp_required_for_login: true) }
+
+ it 'allows registering a new device' do
+ visit profile_account_path
+ click_on 'Manage Two-Factor Authentication'
+ expect(page.body).to match("You've already enabled two-factor authentication using mobile")
+
+ register_u2f_device
+
+ expect(page.body).to match('Your U2F device was registered')
+ end
+
+ it 'allows registering more than one device' do
+ visit profile_account_path
+
+ # First device
+ click_on 'Manage Two-Factor Authentication'
+ register_u2f_device
+ expect(page.body).to match('Your U2F device was registered')
+
+ # Second device
+ click_on 'Manage Two-Factor Authentication'
+ register_u2f_device
+ expect(page.body).to match('Your U2F device was registered')
+
+ click_on 'Manage Two-Factor Authentication'
+ expect(page.body).to match('You have 2 U2F devices registered')
+ end
+ end
+
+ it 'allows the same device to be registered for multiple users' do
+ # First user
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ u2f_device = register_u2f_device
+ expect(page.body).to match('Your U2F device was registered')
+ logout
+
+ # Second user
+ login_as(:user)
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ register_u2f_device(u2f_device)
+ expect(page.body).to match('Your U2F device was registered')
+
+ expect(U2fRegistration.count).to eq(2)
+ end
+
+ context "when there are form errors" do
+ it "doesn't register the device if there are errors" do
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+
+ # Have the "u2f device" respond with bad data
+ page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
+ click_on 'Setup New U2F Device'
+ expect(page).to have_content('Your device was successfully set up')
+ click_on 'Register U2F Device'
+
+ expect(U2fRegistration.count).to eq(0)
+ expect(page.body).to match("The form contains the following error")
+ expect(page.body).to match("did not send a valid JSON response")
+ end
+
+ it "allows retrying registration" do
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+
+ # Failed registration
+ page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
+ click_on 'Setup New U2F Device'
+ expect(page).to have_content('Your device was successfully set up')
+ click_on 'Register U2F Device'
+ expect(page.body).to match("The form contains the following error")
+
+ # Successful registration
+ register_u2f_device
+
+ expect(page.body).to match('Your U2F device was registered')
+ expect(U2fRegistration.count).to eq(1)
+ end
+ end
+ end
+
+ describe "authentication" do
+ let(:user) { create(:user) }
+
+ before do
+ # Register and logout
+ login_as(user)
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ @u2f_device = register_u2f_device
+ logout
+ end
+
+ describe "when 2FA via OTP is disabled" do
+ it "allows logging in with the U2F device" do
+ login_with(user)
+
+ @u2f_device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
+
+ expect(page.body).to match('Signed in successfully')
+ end
+ end
+
+ describe "when 2FA via OTP is enabled" do
+ it "allows logging in with the U2F device" do
+ user.update_attributes(otp_required_for_login: true)
+ login_with(user)
+
+ @u2f_device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
+
+ expect(page.body).to match('Signed in successfully')
+ end
+ end
+
+ describe "when a given U2F device has already been registered by another user" do
+ describe "but not the current user" do
+ it "does not allow logging in with that particular device" do
+ # Register current user with the different U2F device
+ current_user = login_as(:user)
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ register_u2f_device
+ logout
+
+ # Try authenticating user with the old U2F device
+ login_as(current_user)
+ @u2f_device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
+
+ expect(page.body).to match('Authentication via U2F device failed')
+ end
+ end
+
+ describe "and also the current user" do
+ it "allows logging in with that particular device" do
+ # Register current user with the same U2F device
+ current_user = login_as(:user)
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ register_u2f_device(@u2f_device)
+ logout
+
+ # Try authenticating user with the same U2F device
+ login_as(current_user)
+ @u2f_device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
+
+ expect(page.body).to match('Signed in successfully')
+ end
+ end
+ end
+
+ describe "when a given U2F device has not been registered" do
+ it "does not allow logging in with that particular device" do
+ unregistered_device = FakeU2fDevice.new(page)
+ login_as(user)
+ unregistered_device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
+
+ expect(page.body).to match('Authentication via U2F device failed')
+ end
+ end
+ end
+
+ describe "when two-factor authentication is disabled" do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ visit profile_account_path
+ click_on 'Enable Two-Factor Authentication'
+ register_u2f_device
+ end
+
+ it "deletes u2f registrations" do
+ expect { click_on "Disable" }.to change { U2fRegistration.count }.from(1).to(0)
+ end
+ end
+end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 34ce7c4f033..c75d28d9801 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -136,7 +136,7 @@ But it shouldn't autolink text inside certain tags:
### ExternalLinkFilter
-External links get a `rel="nofollow"` attribute:
+External links get a `rel="nofollow noreferrer"` and `target="_blank"` attributes:
- [Google](https://google.com/)
- [GitLab Root](<%= Gitlab.config.gitlab.url %>)
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index f942695b6f0..45199d0f09d 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -3,8 +3,8 @@ require 'spec_helper'
describe CiStatusHelper do
include IconsHelper
- let(:success_commit) { double("Ci::Commit", status: 'success') }
- let(:failed_commit) { double("Ci::Commit", status: 'failed') }
+ let(:success_commit) { double("Ci::Pipeline", status: 'success') }
+ let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do
it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 13de88e2f21..ade5c3b02d9 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -121,13 +121,14 @@ describe GitlabMarkdownHelper do
before do
@wiki = double('WikiPage')
allow(@wiki).to receive(:content).and_return('wiki content')
+ allow(@wiki).to receive(:slug).and_return('nested/page')
helper.instance_variable_set(:@project_wiki, @wiki)
end
it "should use Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
- expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki)
+ expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki, page_slug: "nested/page")
helper.render_wiki_content(@wiki)
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index bffe2c18b6f..eae61a54dfc 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -163,18 +163,15 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") }
end
- describe "note_active_class" do
- before do
- @note = create :note
- @note1 = create :note
- end
+ describe '#award_active_class' do
+ let!(:upvote) { create(:award_emoji) }
it "returns empty string for unauthenticated user" do
- expect(note_active_class(Note.all, nil)).to eq("")
+ expect(award_active_class(AwardEmoji.all, nil)).to eq("")
end
it "returns active string for author" do
- expect(note_active_class(Note.all, @note.author)).to eq("active")
+ expect(award_active_class(AwardEmoji.all, upvote.user)).to eq("active")
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 8e7ed42e883..a3336c87173 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -5,7 +5,7 @@ describe MergeRequestsHelper do
let(:project) { create :project }
let(:merge_request) { MergeRequest.new }
let(:ci_service) { CiService.new }
- let(:last_commit) { Ci::Commit.new({}) }
+ let(:last_commit) { Ci::Pipeline.new({}) }
before do
allow(merge_request).to receive(:source_project).and_return(project)
diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee
new file mode 100644
index 00000000000..ba191199dc7
--- /dev/null
+++ b/spec/javascripts/awards_handler_spec.js.coffee
@@ -0,0 +1,201 @@
+#= require awards_handler
+#= require jquery
+#= require jquery.cookie
+#= require ./fixtures/emoji_menu
+
+awardsHandler = null
+window.gl or= {}
+window.gon or= {}
+gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
+gon.award_menu_url = '/emojis'
+
+
+lazyAssert = (done, assertFn) ->
+
+ setTimeout -> # Maybe jasmine.clock here?
+ assertFn()
+ done()
+ , 333
+
+
+describe 'AwardsHandler', ->
+
+ fixture.preload 'awards_handler.html'
+
+ beforeEach ->
+ fixture.load 'awards_handler.html'
+ awardsHandler = new AwardsHandler
+ spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
+ spyOn(jQuery, 'get').and.callFake (req, cb) -> cb window.emojiMenu
+
+
+ describe '::showEmojiMenu', ->
+
+ it 'should show emoji menu when Add emoji button clicked', (done) ->
+
+ $('.js-add-award').eq(0).click()
+
+ lazyAssert done, ->
+ $emojiMenu = $ '.emoji-menu'
+ expect($emojiMenu.length).toBe 1
+ expect($emojiMenu.hasClass('is-visible')).toBe yes
+ expect($emojiMenu.find('#emoji_search').length).toBe 1
+ expect($('.js-awards-block.current').length).toBe 1
+
+
+ it 'should also show emoji menu for the smiley icon in notes', (done) ->
+
+ $('.note-action-button').click()
+
+ lazyAssert done, ->
+ $emojiMenu = $ '.emoji-menu'
+ expect($emojiMenu.length).toBe 1
+
+
+ it 'should remove emoji menu when body is clicked', (done) ->
+
+ $('.js-add-award').eq(0).click()
+
+ lazyAssert done, ->
+ $emojiMenu = $('.emoji-menu')
+ $('body').click()
+ expect($emojiMenu.length).toBe 1
+ expect($emojiMenu.hasClass('is-visible')).toBe no
+ expect($('.js-awards-block.current').length).toBe 0
+
+
+ describe '::addAwardToEmojiBar', ->
+
+ it 'should add emoji to votes block', ->
+
+ $votesBlock = $('.js-awards-block').eq 0
+ awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
+
+ $emojiButton = $votesBlock.find '[data-emoji=heart]'
+
+ expect($emojiButton.length).toBe 1
+ expect($emojiButton.next('.js-counter').text()).toBe '1'
+ expect($votesBlock.hasClass('hidden')).toBe no
+
+
+ it 'should remove the emoji when we click again', ->
+
+ $votesBlock = $('.js-awards-block').eq 0
+ awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
+ awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
+ $emojiButton = $votesBlock.find '[data-emoji=heart]'
+
+ expect($emojiButton.length).toBe 0
+
+
+ it 'should decrement the emoji counter', ->
+
+ $votesBlock = $('.js-awards-block').eq 0
+ awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
+
+ $emojiButton = $votesBlock.find '[data-emoji=heart]'
+ $emojiButton.next('.js-counter').text 5
+
+ awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
+
+ expect($emojiButton.length).toBe 1
+ expect($emojiButton.next('.js-counter').text()).toBe '4'
+
+
+ describe '::getAwardUrl', ->
+
+ it 'should return the url for request', ->
+
+ expect(awardsHandler.getAwardUrl()).toBe '/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'
+
+
+ describe '::addAward and ::checkMutuality', ->
+
+ it 'should handle :+1: and :-1: mutuality', ->
+
+ awardUrl = awardsHandler.getAwardUrl()
+ $votesBlock = $('.js-awards-block').eq 0
+ $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent()
+ $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent()
+
+ awardsHandler.addAward $votesBlock, awardUrl, 'thumbsup', no
+
+ expect($thumbsUpEmoji.hasClass('active')).toBe yes
+ expect($thumbsDownEmoji.hasClass('active')).toBe no
+
+ $thumbsUpEmoji.tooltip()
+ $thumbsDownEmoji.tooltip()
+
+ awardsHandler.addAward $votesBlock, awardUrl, 'thumbsdown', yes
+
+ expect($thumbsUpEmoji.hasClass('active')).toBe no
+ expect($thumbsDownEmoji.hasClass('active')).toBe yes
+
+
+ describe '::removeEmoji', ->
+
+ it 'should remove emoji', ->
+
+ awardUrl = awardsHandler.getAwardUrl()
+ $votesBlock = $('.js-awards-block').eq 0
+
+ awardsHandler.addAward $votesBlock, awardUrl, 'fire', no
+ expect($votesBlock.find('[data-emoji=fire]').length).toBe 1
+
+ awardsHandler.removeEmoji $votesBlock.find('[data-emoji=fire]').closest('button')
+ expect($votesBlock.find('[data-emoji=fire]').length).toBe 0
+
+
+ describe 'search', ->
+
+ it 'should filter the emoji', ->
+
+ $('.js-add-award').eq(0).click()
+
+ expect($('[data-emoji=angel]').is(':visible')).toBe yes
+ expect($('[data-emoji=anger]').is(':visible')).toBe yes
+
+ $('#emoji_search').val('ali').trigger 'keyup'
+
+ expect($('[data-emoji=angel]').is(':visible')).toBe no
+ expect($('[data-emoji=anger]').is(':visible')).toBe no
+ expect($('[data-emoji=alien]').is(':visible')).toBe yes
+ expect($('h5.emoji-search').is(':visible')).toBe yes
+
+
+ describe 'emoji menu', ->
+
+ selector = '[data-emoji=sunglasses]'
+
+ openEmojiMenuAndAddEmoji = ->
+
+ $('.js-add-award').eq(0).click()
+
+ $menu = $ '.emoji-menu'
+ $block = $ '.js-awards-block'
+ $emoji = $menu.find ".emoji-menu-list-item #{selector}"
+
+ expect($emoji.length).toBe 1
+ expect($block.find(selector).length).toBe 0
+
+ $emoji.click()
+
+ expect($menu.hasClass('.is-visible')).toBe no
+ expect($block.find(selector).length).toBe 1
+
+
+ it 'should add selected emoji to awards block', ->
+
+ openEmojiMenuAndAddEmoji()
+
+
+ it 'should remove already selected emoji', ->
+
+ openEmojiMenuAndAddEmoji()
+ $('.js-add-award').eq(0).click()
+
+ $block = $ '.js-awards-block'
+ $emoji = $('.emoji-menu').find ".emoji-menu-list-item #{selector}"
+
+ $emoji.click()
+ expect($block.find(selector).length).toBe 0
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js.coffee b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
index 09708c12ed4..d3b003a328a 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js.coffee
+++ b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
@@ -14,17 +14,17 @@ describe 'Quick Submit behavior', ->
}
it 'does not respond to other keyCodes', ->
- $('input').trigger(keydownEvent(keyCode: 32))
+ $('input.quick-submit-input').trigger(keydownEvent(keyCode: 32))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to Enter alone', ->
- $('input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
+ $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to repeated events', ->
- $('input').trigger(keydownEvent(repeat: true))
+ $('input.quick-submit-input').trigger(keydownEvent(repeat: true))
expect(@spies.submit).not.toHaveBeenTriggered()
@@ -38,26 +38,26 @@ describe 'Quick Submit behavior', ->
# only run the tests that apply to the current platform
if navigator.userAgent.match(/Macintosh/)
it 'responds to Meta+Enter', ->
- $('input').trigger(keydownEvent())
+ $('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
- $('input').trigger(keydownEvent(altKey: true))
- $('input').trigger(keydownEvent(ctrlKey: true))
- $('input').trigger(keydownEvent(shiftKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
else
it 'responds to Ctrl+Enter', ->
- $('input').trigger(keydownEvent())
+ $('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
- $('input').trigger(keydownEvent(altKey: true))
- $('input').trigger(keydownEvent(metaKey: true))
- $('input').trigger(keydownEvent(shiftKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(metaKey: true))
+ $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml
new file mode 100644
index 00000000000..d55936ee4f9
--- /dev/null
+++ b/spec/javascripts/fixtures/awards_handler.html.haml
@@ -0,0 +1,52 @@
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ %h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem.
+ .description.js-task-list-container.is-task-list-enabled
+ .wiki
+ %p Qui exercitationem magnam optio quae fuga earum odio.
+ %textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio.
+ %small.edited-text
+ .content-block.content-block-small
+ .awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"}
+ %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
+ .icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"}
+ %span.award-control-text.js-counter 0
+ %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
+ .icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"}
+ %span.award-control-text.js-counter 0
+ .award-menu-holder.js-award-holder
+ %button.btn.award-control.js-add-award{:type => "button"}
+ %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
+ %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
+ %span.award-control-text Add
+ %section.issuable-discussion
+ #notes
+ %ul#notes-list.notes.main-notes-list.timeline
+ %li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""}
+ .timeline-entry-inner
+ .timeline-icon
+ %a{:href => "/u/agustin"}
+ %img.avatar.s40{:alt => "", :src => "#"}/
+ .timeline-content
+ .note-header
+ %a.author_link{:href => "/u/agustin"}
+ %span.author Brenna Stokes
+ .inline.note-headline-light
+ @agustin commented
+ %a{:href => "#note_348"}
+ %time 11 days ago
+ .note-actions
+ %span.note-role Reporter
+ %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"}
+ %i.fa.fa-spinner.fa-spin
+ %i.fa.fa-smile-o
+ .js-task-list-container.note-body.is-task-list-enabled
+ .note-text
+ %p Suscipit sunt quia quisquam sed eveniet ipsam.
+ .note-awards
+ .awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"}
+ .award-menu-holder.js-award-holder
+ %button.btn.award-control.js-add-award{:type => "button"}
+ %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
+ %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
+ %span.award-control-text Add
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
index e3788bee813..dc2ceed42f4 100644
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
@@ -1,5 +1,5 @@
%form.js-quick-submit{ action: '/foo' }
- %input{ type: 'text' }
+ %input{ type: 'text', class: 'quick-submit-input'}
%textarea
%input{ type: 'submit'} Submit
diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee
new file mode 100644
index 00000000000..e529dd5f1cd
--- /dev/null
+++ b/spec/javascripts/fixtures/emoji_menu.coffee
@@ -0,0 +1,957 @@
+window.emojiMenu = """
+ <div class='emoji-menu'>
+ <div class='emoji-menu-content'>
+ <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" />
+ <h5 class='emoji-menu-title'>
+ Emoticons
+ </h5>
+ <ul class='clearfix emoji-menu-list'>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F47D" title="alien" data-aliases="" data-emoji="alien" data-unicode-name="1F47D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F47C" title="angel" data-aliases="" data-emoji="angel" data-unicode-name="1F47C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A2" title="anger" data-aliases="" data-emoji="anger" data-unicode-name="1F4A2"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F620" title="angry" data-aliases="" data-emoji="angry" data-unicode-name="1F620"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F627" title="anguished" data-aliases="" data-emoji="anguished" data-unicode-name="1F627"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F632" title="astonished" data-aliases="" data-emoji="astonished" data-unicode-name="1F632"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45F" title="athletic_shoe" data-aliases="" data-emoji="athletic_shoe" data-unicode-name="1F45F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F476" title="baby" data-aliases="" data-emoji="baby" data-unicode-name="1F476"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F459" title="bikini" data-aliases="" data-emoji="bikini" data-unicode-name="1F459"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F499" title="blue_heart" data-aliases="" data-emoji="blue_heart" data-unicode-name="1F499"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60A" title="blush" data-aliases="" data-emoji="blush" data-unicode-name="1F60A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A5" title="boom" data-aliases="" data-emoji="boom" data-unicode-name="1F4A5"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F462" title="boot" data-aliases="" data-emoji="boot" data-unicode-name="1F462"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F647" title="bow" data-aliases="" data-emoji="bow" data-unicode-name="1F647"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F466" title="boy" data-aliases="" data-emoji="boy" data-unicode-name="1F466"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F470" title="bride_with_veil" data-aliases="" data-emoji="bride_with_veil" data-unicode-name="1F470"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4BC" title="briefcase" data-aliases="" data-emoji="briefcase" data-unicode-name="1F4BC"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F494" title="broken_heart" data-aliases="" data-emoji="broken_heart" data-unicode-name="1F494"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F464" title="bust_in_silhouette" data-aliases="" data-emoji="bust_in_silhouette" data-unicode-name="1F464"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F465" title="busts_in_silhouette" data-aliases="" data-emoji="busts_in_silhouette" data-unicode-name="1F465"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44F" title="clap" data-aliases="" data-emoji="clap" data-unicode-name="1F44F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F302" title="closed_umbrella" data-aliases="" data-emoji="closed_umbrella" data-unicode-name="1F302"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F630" title="cold_sweat" data-aliases="" data-emoji="cold_sweat" data-unicode-name="1F630"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F616" title="confounded" data-aliases="" data-emoji="confounded" data-unicode-name="1F616"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F615" title="confused" data-aliases="" data-emoji="confused" data-unicode-name="1F615"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F477" title="construction_worker" data-aliases="" data-emoji="construction_worker" data-unicode-name="1F477"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46E" title="cop" data-aliases="" data-emoji="cop" data-unicode-name="1F46E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46B" title="couple" data-aliases="" data-emoji="couple" data-unicode-name="1F46B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F491" title="couple_with_heart" data-aliases="" data-emoji="couple_with_heart" data-unicode-name="1F491"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F48F" title="couplekiss" data-aliases="" data-emoji="couplekiss" data-unicode-name="1F48F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F451" title="crown" data-aliases="" data-emoji="crown" data-unicode-name="1F451"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F622" title="cry" data-aliases="" data-emoji="cry" data-unicode-name="1F622"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63F" title="crying_cat_face" data-aliases="" data-emoji="crying_cat_face" data-unicode-name="1F63F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F498" title="cupid" data-aliases="" data-emoji="cupid" data-unicode-name="1F498"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F483" title="dancer" data-aliases="" data-emoji="dancer" data-unicode-name="1F483"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46F" title="dancers" data-aliases="" data-emoji="dancers" data-unicode-name="1F46F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A8" title="dash" data-aliases="" data-emoji="dash" data-unicode-name="1F4A8"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61E" title="disappointed" data-aliases="" data-emoji="disappointed" data-unicode-name="1F61E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F625" title="disappointed_relieved" data-aliases="" data-emoji="disappointed_relieved" data-unicode-name="1F625"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4AB" title="dizzy" data-aliases="" data-emoji="dizzy" data-unicode-name="1F4AB"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F635" title="dizzy_face" data-aliases="" data-emoji="dizzy_face" data-unicode-name="1F635"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F457" title="dress" data-aliases="" data-emoji="dress" data-unicode-name="1F457"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A7" title="droplet" data-aliases="" data-emoji="droplet" data-unicode-name="1F4A7"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F442" title="ear" data-aliases="" data-emoji="ear" data-unicode-name="1F442"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F611" title="expressionless" data-aliases="" data-emoji="expressionless" data-unicode-name="1F611"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F453" title="eyeglasses" data-aliases="" data-emoji="eyeglasses" data-unicode-name="1F453"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F440" title="eyes" data-aliases="" data-emoji="eyes" data-unicode-name="1F440"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46A" title="family" data-aliases="" data-emoji="family" data-unicode-name="1F46A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F628" title="fearful" data-aliases="" data-emoji="fearful" data-unicode-name="1F628"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F525" title="fire" data-aliases=":flame:" data-emoji="fire" data-unicode-name="1F525"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-270A" title="fist" data-aliases="" data-emoji="fist" data-unicode-name="270A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F633" title="flushed" data-aliases="" data-emoji="flushed" data-unicode-name="1F633"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F463" title="footprints" data-aliases="" data-emoji="footprints" data-unicode-name="1F463"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F626" title="frowning" data-aliases=":anguished:" data-emoji="frowning" data-unicode-name="1F626"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F48E" title="gem" data-aliases="" data-emoji="gem" data-unicode-name="1F48E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F467" title="girl" data-aliases="" data-emoji="girl" data-unicode-name="1F467"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F49A" title="green_heart" data-aliases="" data-emoji="green_heart" data-unicode-name="1F49A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62C" title="grimacing" data-aliases="" data-emoji="grimacing" data-unicode-name="1F62C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F601" title="grin" data-aliases="" data-emoji="grin" data-unicode-name="1F601"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F600" title="grinning" data-aliases="" data-emoji="grinning" data-unicode-name="1F600"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F482" title="guardsman" data-aliases="" data-emoji="guardsman" data-unicode-name="1F482"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F487" title="haircut" data-aliases="" data-emoji="haircut" data-unicode-name="1F487"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45C" title="handbag" data-aliases="" data-emoji="handbag" data-unicode-name="1F45C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F649" title="hear_no_evil" data-aliases="" data-emoji="hear_no_evil" data-unicode-name="1F649"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-2764" title="heart" data-aliases="" data-emoji="heart" data-unicode-name="2764"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60D" title="heart_eyes" data-aliases="" data-emoji="heart_eyes" data-unicode-name="1F60D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63B" title="heart_eyes_cat" data-aliases="" data-emoji="heart_eyes_cat" data-unicode-name="1F63B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F493" title="heartbeat" data-aliases="" data-emoji="heartbeat" data-unicode-name="1F493"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F497" title="heartpulse" data-aliases="" data-emoji="heartpulse" data-unicode-name="1F497"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F460" title="high_heel" data-aliases="" data-emoji="high_heel" data-unicode-name="1F460"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62F" title="hushed" data-aliases="" data-emoji="hushed" data-unicode-name="1F62F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F47F" title="imp" data-aliases="" data-emoji="imp" data-unicode-name="1F47F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F481" title="information_desk_person" data-aliases="" data-emoji="information_desk_person" data-unicode-name="1F481"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F607" title="innocent" data-aliases="" data-emoji="innocent" data-unicode-name="1F607"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F47A" title="japanese_goblin" data-aliases="" data-emoji="japanese_goblin" data-unicode-name="1F47A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F479" title="japanese_ogre" data-aliases="" data-emoji="japanese_ogre" data-unicode-name="1F479"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F456" title="jeans" data-aliases="" data-emoji="jeans" data-unicode-name="1F456"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F602" title="joy" data-aliases="" data-emoji="joy" data-unicode-name="1F602"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F639" title="joy_cat" data-aliases="" data-emoji="joy_cat" data-unicode-name="1F639"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F458" title="kimono" data-aliases="" data-emoji="kimono" data-unicode-name="1F458"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F48B" title="kiss" data-aliases="" data-emoji="kiss" data-unicode-name="1F48B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F617" title="kissing" data-aliases="" data-emoji="kissing" data-unicode-name="1F617"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63D" title="kissing_cat" data-aliases="" data-emoji="kissing_cat" data-unicode-name="1F63D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61A" title="kissing_closed_eyes" data-aliases="" data-emoji="kissing_closed_eyes" data-unicode-name="1F61A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F618" title="kissing_heart" data-aliases="" data-emoji="kissing_heart" data-unicode-name="1F618"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F619" title="kissing_smiling_eyes" data-aliases="" data-emoji="kissing_smiling_eyes" data-unicode-name="1F619"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F606" title="laughing" data-aliases=":satisfied:" data-emoji="laughing" data-unicode-name="1F606"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F444" title="lips" data-aliases="" data-emoji="lips" data-unicode-name="1F444"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F484" title="lipstick" data-aliases="" data-emoji="lipstick" data-unicode-name="1F484"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F48C" title="love_letter" data-aliases="" data-emoji="love_letter" data-unicode-name="1F48C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F468" title="man" data-aliases="" data-emoji="man" data-unicode-name="1F468"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F472" title="man_with_gua_pi_mao" data-aliases="" data-emoji="man_with_gua_pi_mao" data-unicode-name="1F472"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F473" title="man_with_turban" data-aliases="" data-emoji="man_with_turban" data-unicode-name="1F473"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45E" title="mans_shoe" data-aliases="" data-emoji="mans_shoe" data-unicode-name="1F45E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F637" title="mask" data-aliases="" data-emoji="mask" data-unicode-name="1F637"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F486" title="massage" data-aliases="" data-emoji="massage" data-unicode-name="1F486"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4AA" title="muscle" data-aliases="" data-emoji="muscle" data-unicode-name="1F4AA"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F485" title="nail_care" data-aliases="" data-emoji="nail_care" data-unicode-name="1F485"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F454" title="necktie" data-aliases="" data-emoji="necktie" data-unicode-name="1F454"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F610" title="neutral_face" data-aliases="" data-emoji="neutral_face" data-unicode-name="1F610"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F645" title="no_good" data-aliases="" data-emoji="no_good" data-unicode-name="1F645"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F636" title="no_mouth" data-aliases="" data-emoji="no_mouth" data-unicode-name="1F636"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F443" title="nose" data-aliases="" data-emoji="nose" data-unicode-name="1F443"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44C" title="ok_hand" data-aliases="" data-emoji="ok_hand" data-unicode-name="1F44C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F646" title="ok_woman" data-aliases="" data-emoji="ok_woman" data-unicode-name="1F646"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F474" title="older_man" data-aliases="" data-emoji="older_man" data-unicode-name="1F474"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F475" title="older_woman" data-aliases=":grandma:" data-emoji="older_woman" data-unicode-name="1F475"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F450" title="open_hands" data-aliases="" data-emoji="open_hands" data-unicode-name="1F450"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62E" title="open_mouth" data-aliases="" data-emoji="open_mouth" data-unicode-name="1F62E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F614" title="pensive" data-aliases="" data-emoji="pensive" data-unicode-name="1F614"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F623" title="persevere" data-aliases="" data-emoji="persevere" data-unicode-name="1F623"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64D" title="person_frowning" data-aliases="" data-emoji="person_frowning" data-unicode-name="1F64D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F471" title="person_with_blond_hair" data-aliases="" data-emoji="person_with_blond_hair" data-unicode-name="1F471"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64E" title="person_with_pouting_face" data-aliases="" data-emoji="person_with_pouting_face" data-unicode-name="1F64E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F447" title="point_down" data-aliases="" data-emoji="point_down" data-unicode-name="1F447"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F448" title="point_left" data-aliases="" data-emoji="point_left" data-unicode-name="1F448"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F449" title="point_right" data-aliases="" data-emoji="point_right" data-unicode-name="1F449"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-261D" title="point_up" data-aliases="" data-emoji="point_up" data-unicode-name="261D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F446" title="point_up_2" data-aliases="" data-emoji="point_up_2" data-unicode-name="1F446"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A9" title="poop" data-aliases=":shit: :hankey: :poo:" data-emoji="poop" data-unicode-name="1F4A9"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45D" title="pouch" data-aliases="" data-emoji="pouch" data-unicode-name="1F45D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63E" title="pouting_cat" data-aliases="" data-emoji="pouting_cat" data-unicode-name="1F63E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64F" title="pray" data-aliases="" data-emoji="pray" data-unicode-name="1F64F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F478" title="princess" data-aliases="" data-emoji="princess" data-unicode-name="1F478"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44A" title="punch" data-aliases="" data-emoji="punch" data-unicode-name="1F44A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F49C" title="purple_heart" data-aliases="" data-emoji="purple_heart" data-unicode-name="1F49C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45B" title="purse" data-aliases="" data-emoji="purse" data-unicode-name="1F45B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F621" title="rage" data-aliases="" data-emoji="rage" data-unicode-name="1F621"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-270B" title="raised_hand" data-aliases="" data-emoji="raised_hand" data-unicode-name="270B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64C" title="raised_hands" data-aliases="" data-emoji="raised_hands" data-unicode-name="1F64C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64B" title="raising_hand" data-aliases="" data-emoji="raising_hand" data-unicode-name="1F64B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-263A" title="relaxed" data-aliases="" data-emoji="relaxed" data-unicode-name="263A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60C" title="relieved" data-aliases="" data-emoji="relieved" data-unicode-name="1F60C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F49E" title="revolving_hearts" data-aliases="" data-emoji="revolving_hearts" data-unicode-name="1F49E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F380" title="ribbon" data-aliases="" data-emoji="ribbon" data-unicode-name="1F380"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F48D" title="ring" data-aliases="" data-emoji="ring" data-unicode-name="1F48D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F3C3" title="runner" data-aliases="" data-emoji="runner" data-unicode-name="1F3C3"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F3BD" title="running_shirt_with_sash" data-aliases="" data-emoji="running_shirt_with_sash" data-unicode-name="1F3BD"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F461" title="sandal" data-aliases="" data-emoji="sandal" data-unicode-name="1F461"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F631" title="scream" data-aliases="" data-emoji="scream" data-unicode-name="1F631"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F640" title="scream_cat" data-aliases="" data-emoji="scream_cat" data-unicode-name="1F640"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F648" title="see_no_evil" data-aliases="" data-emoji="see_no_evil" data-unicode-name="1F648"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F455" title="shirt" data-aliases="" data-emoji="shirt" data-unicode-name="1F455"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F480" title="skull" data-aliases=":skeleton:" data-emoji="skull" data-unicode-name="1F480"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F634" title="sleeping" data-aliases="" data-emoji="sleeping" data-unicode-name="1F634"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62A" title="sleepy" data-aliases="" data-emoji="sleepy" data-unicode-name="1F62A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F604" title="smile" data-aliases="" data-emoji="smile" data-unicode-name="1F604"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F638" title="smile_cat" data-aliases="" data-emoji="smile_cat" data-unicode-name="1F638"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F603" title="smiley" data-aliases="" data-emoji="smiley" data-unicode-name="1F603"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63A" title="smiley_cat" data-aliases="" data-emoji="smiley_cat" data-unicode-name="1F63A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F608" title="smiling_imp" data-aliases="" data-emoji="smiling_imp" data-unicode-name="1F608"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60F" title="smirk" data-aliases="" data-emoji="smirk" data-unicode-name="1F60F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F63C" title="smirk_cat" data-aliases="" data-emoji="smirk_cat" data-unicode-name="1F63C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62D" title="sob" data-aliases="" data-emoji="sob" data-unicode-name="1F62D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-2728" title="sparkles" data-aliases="" data-emoji="sparkles" data-unicode-name="2728"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F496" title="sparkling_heart" data-aliases="" data-emoji="sparkling_heart" data-unicode-name="1F496"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F64A" title="speak_no_evil" data-aliases="" data-emoji="speak_no_evil" data-unicode-name="1F64A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4AC" title="speech_balloon" data-aliases="" data-emoji="speech_balloon" data-unicode-name="1F4AC"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F31F" title="star2" data-aliases="" data-emoji="star2" data-unicode-name="1F31F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61B" title="stuck_out_tongue" data-aliases="" data-emoji="stuck_out_tongue" data-unicode-name="1F61B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61D" title="stuck_out_tongue_closed_eyes" data-aliases="" data-emoji="stuck_out_tongue_closed_eyes" data-unicode-name="1F61D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61C" title="stuck_out_tongue_winking_eye" data-aliases="" data-emoji="stuck_out_tongue_winking_eye" data-unicode-name="1F61C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60E" title="sunglasses" data-aliases="" data-emoji="sunglasses" data-unicode-name="1F60E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F613" title="sweat" data-aliases="" data-emoji="sweat" data-unicode-name="1F613"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A6" title="sweat_drops" data-aliases="" data-emoji="sweat_drops" data-unicode-name="1F4A6"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F605" title="sweat_smile" data-aliases="" data-emoji="sweat_smile" data-unicode-name="1F605"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4AD" title="thought_balloon" data-aliases="" data-emoji="thought_balloon" data-unicode-name="1F4AD"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44E" title="thumbsdown" data-aliases=":-1:" data-emoji="thumbsdown" data-unicode-name="1F44E"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44D" title="thumbsup" data-aliases=":+1:" data-emoji="thumbsup" data-unicode-name="1F44D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F62B" title="tired_face" data-aliases="" data-emoji="tired_face" data-unicode-name="1F62B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F445" title="tongue" data-aliases="" data-emoji="tongue" data-unicode-name="1F445"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F3A9" title="tophat" data-aliases="" data-emoji="tophat" data-unicode-name="1F3A9"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F624" title="triumph" data-aliases="" data-emoji="triumph" data-unicode-name="1F624"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F495" title="two_hearts" data-aliases="" data-emoji="two_hearts" data-unicode-name="1F495"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46C" title="two_men_holding_hands" data-aliases="" data-emoji="two_men_holding_hands" data-unicode-name="1F46C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F46D" title="two_women_holding_hands" data-aliases="" data-emoji="two_women_holding_hands" data-unicode-name="1F46D"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F612" title="unamused" data-aliases="" data-emoji="unamused" data-unicode-name="1F612"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-270C" title="v" data-aliases="" data-emoji="v" data-unicode-name="270C"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F6B6" title="walking" data-aliases="" data-emoji="walking" data-unicode-name="1F6B6"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F44B" title="wave" data-aliases="" data-emoji="wave" data-unicode-name="1F44B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F629" title="weary" data-aliases="" data-emoji="weary" data-unicode-name="1F629"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F609" title="wink" data-aliases="" data-emoji="wink" data-unicode-name="1F609"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F469" title="woman" data-aliases="" data-emoji="woman" data-unicode-name="1F469"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F45A" title="womans_clothes" data-aliases="" data-emoji="womans_clothes" data-unicode-name="1F45A"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F452" title="womans_hat" data-aliases="" data-emoji="womans_hat" data-unicode-name="1F452"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F61F" title="worried" data-aliases="" data-emoji="worried" data-unicode-name="1F61F"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F49B" title="yellow_heart" data-aliases="" data-emoji="yellow_heart" data-unicode-name="1F49B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F60B" title="yum" data-aliases="" data-emoji="yum" data-unicode-name="1F60B"></div>
+ </button>
+ </li>
+ <li class='pull-left text-center emoji-menu-list-item'>
+ <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
+ <div class="icon emoji-icon emoji-1F4A4" title="zzz" data-aliases="" data-emoji="zzz" data-unicode-name="1F4A4"></div>
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+"""
diff --git a/spec/javascripts/fixtures/u2f/authenticate.html.haml b/spec/javascripts/fixtures/u2f/authenticate.html.haml
new file mode 100644
index 00000000000..859e79a6c9e
--- /dev/null
+++ b/spec/javascripts/fixtures/u2f/authenticate.html.haml
@@ -0,0 +1 @@
+= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in" }
diff --git a/spec/javascripts/fixtures/u2f/register.html.haml b/spec/javascripts/fixtures/u2f/register.html.haml
new file mode 100644
index 00000000000..393c0613fd3
--- /dev/null
+++ b/spec/javascripts/fixtures/u2f/register.html.haml
@@ -0,0 +1 @@
+= render partial: "u2f/register", locals: { create_u2f_profile_two_factor_auth_path: '/profile/two_factor_auth/create_u2f' }
diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index 5b992447473..56970e22e34 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -9,14 +9,14 @@ describe("ContributorsStatGraphUtil", function () {
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
-
+
var correct_parsed_log = {
total: [
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:
[
- {
+ {
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
@@ -132,8 +132,8 @@ describe("ContributorsStatGraphUtil", function () {
total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:[
- {
- author: "Karlo Soriano",
+ {
+ author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
@@ -161,11 +161,11 @@ describe("ContributorsStatGraphUtil", function () {
it("returns the log by author sorted by specified field", function () {
var fake_parsed_log = {
total: [
- {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
],
by_author: [
- {
+ {
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
index f2ce85efcdc..ce773793817 100644
--- a/spec/javascripts/new_branch_spec.js.coffee
+++ b/spec/javascripts/new_branch_spec.js.coffee
@@ -1,4 +1,4 @@
-#= require jquery-ui
+#= require jquery-ui/autocomplete
#= require new_branch_form
describe 'Branch', ->
diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee
new file mode 100644
index 00000000000..e8a2892d678
--- /dev/null
+++ b/spec/javascripts/u2f/authenticate_spec.coffee
@@ -0,0 +1,52 @@
+#= require u2f/authenticate
+#= require u2f/util
+#= require u2f/error
+#= require u2f
+#= require ./mock_u2f_device
+
+describe 'U2FAuthenticate', ->
+ U2FUtil.enableTestMode()
+ fixture.load('u2f/authenticate')
+
+ beforeEach ->
+ @u2fDevice = new MockU2FDevice
+ @container = $("#js-authenticate-u2f")
+ @component = new U2FAuthenticate(@container, {}, "token")
+ @component.start()
+
+ it 'allows authenticating via a U2F device', ->
+ setupButton = @container.find("#js-login-u2f-device")
+ setupMessage = @container.find("p")
+ expect(setupMessage.text()).toContain('Insert your security key')
+ expect(setupButton.text()).toBe('Login Via U2F Device')
+ setupButton.trigger('click')
+
+ inProgressMessage = @container.find("p")
+ expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
+
+ @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
+ authenticatedMessage = @container.find("p")
+ deviceResponse = @container.find('#js-device-response')
+ expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
+ expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
+
+ describe "errors", ->
+ it "displays an error message", ->
+ setupButton = @container.find("#js-login-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
+ errorMessage = @container.find("p")
+ expect(errorMessage.text()).toContain("There was a problem communicating with your device")
+
+ it "allows retrying authentication after an error", ->
+ setupButton = @container.find("#js-login-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
+ retryButton = @container.find("#js-u2f-try-again")
+ retryButton.trigger('click')
+
+ setupButton = @container.find("#js-login-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
+ authenticatedMessage = @container.find("p")
+ expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
diff --git a/spec/javascripts/u2f/mock_u2f_device.js.coffee b/spec/javascripts/u2f/mock_u2f_device.js.coffee
new file mode 100644
index 00000000000..97ed0e83a0e
--- /dev/null
+++ b/spec/javascripts/u2f/mock_u2f_device.js.coffee
@@ -0,0 +1,15 @@
+class @MockU2FDevice
+ constructor: () ->
+ window.u2f ||= {}
+
+ window.u2f.register = (appId, registerRequests, signRequests, callback) =>
+ @registerCallback = callback
+
+ window.u2f.sign = (appId, challenges, signRequests, callback) =>
+ @authenticateCallback = callback
+
+ respondToRegisterRequest: (params) =>
+ @registerCallback(params)
+
+ respondToAuthenticateRequest: (params) =>
+ @authenticateCallback(params)
diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee
new file mode 100644
index 00000000000..0858abeca1a
--- /dev/null
+++ b/spec/javascripts/u2f/register_spec.js.coffee
@@ -0,0 +1,57 @@
+#= require u2f/register
+#= require u2f/util
+#= require u2f/error
+#= require u2f
+#= require ./mock_u2f_device
+
+describe 'U2FRegister', ->
+ U2FUtil.enableTestMode()
+ fixture.load('u2f/register')
+
+ beforeEach ->
+ @u2fDevice = new MockU2FDevice
+ @container = $("#js-register-u2f")
+ @component = new U2FRegister(@container, $("#js-register-u2f-templates"), {}, "token")
+ @component.start()
+
+ it 'allows registering a U2F device', ->
+ setupButton = @container.find("#js-setup-u2f-device")
+ expect(setupButton.text()).toBe('Setup New U2F Device')
+ setupButton.trigger('click')
+
+ inProgressMessage = @container.children("p")
+ expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
+
+ @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
+ registeredMessage = @container.find('p')
+ deviceResponse = @container.find('#js-device-response')
+ expect(registeredMessage.text()).toContain("Your device was successfully set up!")
+ expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
+
+ describe "errors", ->
+ it "doesn't allow the same device to be registered twice (for the same user", ->
+ setupButton = @container.find("#js-setup-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToRegisterRequest({errorCode: 4})
+ errorMessage = @container.find("p")
+ expect(errorMessage.text()).toContain("already been registered with us")
+
+ it "displays an error message for other errors", ->
+ setupButton = @container.find("#js-setup-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
+ errorMessage = @container.find("p")
+ expect(errorMessage.text()).toContain("There was a problem communicating with your device")
+
+ it "allows retrying registration after an error", ->
+ setupButton = @container.find("#js-setup-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
+ retryButton = @container.find("#U2FTryAgain")
+ retryButton.trigger('click')
+
+ setupButton = @container.find("#js-setup-u2f-device")
+ setupButton.trigger('click')
+ @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
+ registeredMessage = @container.find("p")
+ expect(registeredMessage.text()).toContain("Your device was successfully set up!")
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
new file mode 100644
index 00000000000..55e681f6faf
--- /dev/null
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ReferenceFilter, lib: true do
+ let(:project) { build(:project) }
+
+ describe '#each_node' do
+ it 'iterates over the nodes in a document' do
+ document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ filter = described_class.new(document, project: project)
+
+ expect { |b| filter.each_node(&b) }.
+ to yield_with_args(an_instance_of(Nokogiri::XML::Element))
+ end
+
+ it 'returns an Enumerator when no block is given' do
+ document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ filter = described_class.new(document, project: project)
+
+ expect(filter.each_node).to be_an_instance_of(Enumerator)
+ end
+
+ it 'skips links with a "gfm" class' do
+ document = Nokogiri::HTML.fragment('<a href="foo" class="gfm">foo</a>')
+ filter = described_class.new(document, project: project)
+
+ expect { |b| filter.each_node(&b) }.not_to yield_control
+ end
+
+ it 'skips text nodes in pre elements' do
+ document = Nokogiri::HTML.fragment('<pre>foo</pre>')
+ filter = described_class.new(document, project: project)
+
+ expect { |b| filter.each_node(&b) }.not_to yield_control
+ end
+ end
+
+ describe '#nodes' do
+ it 'returns an Array of the HTML nodes' do
+ document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ filter = described_class.new(document, project: project)
+
+ expect(filter.nodes).to eq([document.children[0]])
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index d7dfd6699ef..108b36a97cc 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -136,4 +136,23 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
end
+
+ describe '#namespaces' do
+ it 'returns a Hash containing all Namespaces' do
+ document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ filter = described_class.new(document, project: project)
+ ns = user.namespace
+
+ expect(filter.namespaces).to eq({ ns.path => ns })
+ end
+ end
+
+ describe '#usernames' do
+ it 'returns the usernames mentioned in a document' do
+ document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.usernames).to eq([user.username])
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
deleted file mode 100644
index 185abbb2108..00000000000
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::WikiLinkFilter, lib: true do
- include FilterSpecHelper
-
- let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
- let(:project) { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
- let(:user) { double }
- let(:project_wiki) { ProjectWiki.new(project, user) }
-
- describe "links within the wiki (relative)" do
- describe "hierarchical links to the current directory" do
- it "doesn't rewrite non-file links" do
- link = "<a href='./page'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('./page')
- end
-
- it "doesn't rewrite file links" do
- link = "<a href='./page.md'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('./page.md')
- end
- end
-
- describe "hierarchical links to the parent directory" do
- it "doesn't rewrite non-file links" do
- link = "<a href='../page'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('../page')
- end
-
- it "doesn't rewrite file links" do
- link = "<a href='../page.md'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('../page.md')
- end
- end
-
- describe "hierarchical links to a sub-directory" do
- it "doesn't rewrite non-file links" do
- link = "<a href='./subdirectory/page'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('./subdirectory/page')
- end
-
- it "doesn't rewrite file links" do
- link = "<a href='./subdirectory/page.md'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md')
- end
- end
-
- describe "non-hierarchical links" do
- it 'rewrites non-file links to be at the scope of the wiki root' do
- link = "<a href='page'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to match('/wiki_link_ns/wiki_link_project/wikis/page')
- end
-
- it "doesn't rewrite file links" do
- link = "<a href='page.md'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('page.md')
- end
- end
- end
-
- describe "links outside the wiki (absolute)" do
- it "doesn't rewrite links" do
- link = "<a href='http://example.com/page'>Link to Page</a>"
- filtered_link = filter(link, project_wiki: project_wiki).children[0]
-
- expect(filtered_link.attribute('href').value).to eq('http://example.com/page')
- end
- end
-end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 7aa1b4a3bf6..72bc6a0b704 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -50,4 +50,112 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe "Links" do
+ let(:namespace) { create(:namespace, name: "wiki_link_ns") }
+ let(:project) { create(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
+ let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
+ let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
+
+ { "when GitLab is hosted at a root URL" => '/',
+ "when GitLab is hosted at a relative URL" => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
+
+ context test_name do
+ before do
+ allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root)
+ end
+
+ describe "linking to pages within the wiki" do
+ context "when creating hierarchical links to the current directory" do
+ it "rewrites non-file links to be at the scope of the current directory" do
+ markdown = "[Page](./page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page\"")
+ end
+
+ it "rewrites file links to be at the scope of the current directory" do
+ markdown = "[Link to Page](./page.md)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"")
+ end
+ end
+
+ context "when creating hierarchical links to the parent directory" do
+ it "rewrites non-file links to be at the scope of the parent directory" do
+ markdown = "[Link to Page](../page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/page\"")
+ end
+
+ it "rewrites file links to be at the scope of the parent directory" do
+ markdown = "[Link to Page](../page.md)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/page.md\"")
+ end
+ end
+
+ context "when creating hierarchical links to a sub-directory" do
+ it "rewrites non-file links to be at the scope of the sub-directory" do
+ markdown = "[Link to Page](./subdirectory/page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/subdirectory/page\"")
+ end
+
+ it "rewrites file links to be at the scope of the sub-directory" do
+ markdown = "[Link to Page](./subdirectory/page.md)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/subdirectory/page.md\"")
+ end
+ end
+
+ describe "when creating non-hierarchical links" do
+ it 'rewrites non-file links to be at the scope of the wiki root' do
+ markdown = "[Link to Page](page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page\"")
+ end
+
+ it "rewrites file links to be at the scope of the current directory" do
+ markdown = "[Link to Page](page.md)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"")
+ end
+ end
+
+ describe "when creating root links" do
+ it 'rewrites non-file links to be at the scope of the wiki root' do
+ markdown = "[Link to Page](/page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page\"")
+ end
+
+ it 'rewrites file links to be at the scope of the wiki root' do
+ markdown = "[Link to Page](/page.md)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/page.md\"")
+ end
+ end
+ end
+
+ describe "linking to pages outside the wiki (absolute)" do
+ it "doesn't rewrite links" do
+ markdown = "[Link to Page](http://example.com/page)"
+ output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug)
+
+ expect(output).to include('href="http://example.com/page"')
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 9d1215a5760..9c6b4ea5086 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -4,19 +4,19 @@ describe Ci::Charts, lib: true do
context "build_times" do
before do
- @commit = FactoryGirl.create(:ci_commit)
- FactoryGirl.create(:ci_build, commit: @commit)
+ @pipeline = FactoryGirl.create(:ci_pipeline)
+ FactoryGirl.create(:ci_build, pipeline: @pipeline)
end
it 'should return build times in minutes' do
- chart = Ci::Charts::BuildTime.new(@commit.project)
+ chart = Ci::Charts::BuildTime.new(@pipeline.project)
expect(chart.build_times).to eq([2])
end
it 'should handle nil build times' do
- create(:ci_commit, duration: nil, project: @commit.project)
+ create(:ci_pipeline, duration: nil, project: @pipeline.project)
- chart = Ci::Charts::BuildTime.new(@commit.project)
+ chart = Ci::Charts::BuildTime.new(@pipeline.project)
expect(chart.build_times).to eq([2, 0])
end
end
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
index c2a7b20b84d..309a88151cf 100644
--- a/spec/lib/disable_email_interceptor_spec.rb
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe DisableEmailInterceptor, lib: true do
before do
- ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
+ Mail.register_interceptor(DisableEmailInterceptor)
end
it 'should not send emails' do
@@ -14,7 +14,7 @@ describe DisableEmailInterceptor, lib: true do
# Removing interceptor from the list because unregister_interceptor is
# implemented in later version of mail gem
# See: https://github.com/mikel/mail/pull/705
- Mail.class_variable_set(:@@delivery_interceptors, [])
+ Mail.unregister_interceptor(DisableEmailInterceptor)
end
def deliver_mail
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index aad291c03cd..a814ad2a4e7 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -1,9 +1,47 @@
require 'spec_helper'
describe Gitlab::Auth, lib: true do
- let(:gl_auth) { Gitlab::Auth.new }
+ let(:gl_auth) { described_class }
- describe :find do
+ describe 'find' do
+ it 'recognizes CI' do
+ token = '123'
+ project = create(:empty_project)
+ project.update_attributes(runners_token: token, builds_enabled: true)
+ ip = 'ip'
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
+ expect(gl_auth.find('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci))
+ end
+
+ it 'recognizes master passwords' do
+ user = create(:user, password: 'password')
+ ip = 'ip'
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
+ expect(gl_auth.find(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap))
+ end
+
+ it 'recognizes OAuth tokens' do
+ user = create(:user)
+ application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
+ token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
+ ip = 'ip'
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2')
+ expect(gl_auth.find("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth))
+ end
+
+ it 'returns double nil for invalid credentials' do
+ login = 'foo'
+ ip = 'ip'
+
+ expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login)
+ expect(gl_auth.find(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new)
+ end
+ end
+
+ describe 'find_in_gitlab_or_ldap' do
let!(:user) do
create(:user,
username: username,
@@ -14,25 +52,25 @@ describe Gitlab::Auth, lib: true do
let(:password) { 'my-secret' }
it "should find user by valid login/password" do
- expect( gl_auth.find(username, password) ).to eql user
+ expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).to eql user
end
it 'should find user by valid email/password with case-insensitive email' do
- expect(gl_auth.find(user.email.upcase, password)).to eql user
+ expect(gl_auth.find_in_gitlab_or_ldap(user.email.upcase, password)).to eql user
end
it 'should find user by valid username/password with case-insensitive username' do
- expect(gl_auth.find(username.upcase, password)).to eql user
+ expect(gl_auth.find_in_gitlab_or_ldap(username.upcase, password)).to eql user
end
it "should not find user with invalid password" do
password = 'wrong'
- expect( gl_auth.find(username, password) ).not_to eql user
+ expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user
end
it "should not find user with invalid login" do
user = 'wrong'
- expect( gl_auth.find(username, password) ).not_to eql user
+ expect( gl_auth.find_in_gitlab_or_ldap(username, password) ).not_to eql user
end
context "with ldap enabled" do
@@ -43,13 +81,13 @@ describe Gitlab::Auth, lib: true do
it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::Authentication).not_to receive(:login)
- gl_auth.find(username, password)
+ gl_auth.find_in_gitlab_or_ldap(username, password)
end
it "uses ldap as fallback to for authentication" do
expect(Gitlab::LDAP::Authentication).to receive(:login)
- gl_auth.find('ldap_user', 'password')
+ gl_auth.find_in_gitlab_or_ldap('ldap_user', 'password')
end
end
end
diff --git a/spec/lib/award_emoji_spec.rb b/spec/lib/gitlab/award_emoji_spec.rb
index c3098574292..0f3852b1729 100644
--- a/spec/lib/award_emoji_spec.rb
+++ b/spec/lib/gitlab/award_emoji_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe AwardEmoji do
+describe Gitlab::AwardEmoji do
describe '.urls' do
- subject { AwardEmoji.urls }
+ subject { Gitlab::AwardEmoji.urls }
it { is_expected.to be_an_instance_of(Array) }
it { is_expected.not_to be_empty }
@@ -19,7 +19,7 @@ describe AwardEmoji do
describe '.emoji_by_category' do
it "only contains known categories" do
- undefined_categories = AwardEmoji.emoji_by_category.keys - AwardEmoji::CATEGORIES.keys
+ undefined_categories = Gitlab::AwardEmoji.emoji_by_category.keys - Gitlab::AwardEmoji::CATEGORIES.keys
expect(undefined_categories).to be_empty
end
end
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
deleted file mode 100644
index cd26dca0998..00000000000
--- a/spec/lib/gitlab/backend/grack_auth_spec.rb
+++ /dev/null
@@ -1,209 +0,0 @@
-require "spec_helper"
-
-describe Grack::Auth, lib: true do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- let(:app) { lambda { |env| [200, {}, "Success!"] } }
- let!(:auth) { Grack::Auth.new(app) }
- let(:env) do
- {
- 'rack.input' => '',
- 'REQUEST_METHOD' => 'GET',
- 'QUERY_STRING' => 'service=git-upload-pack'
- }
- end
- let(:status) { auth.call(env).first }
-
- describe "#call" do
- context "when the project doesn't exist" do
- before do
- env["PATH_INFO"] = "doesnt/exist.git"
- end
-
- context "when no authentication is provided" do
- it "responds with status 401" do
- expect(status).to eq(401)
- end
- end
-
- context "when username and password are provided" do
- context "when authentication fails" do
- before do
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope")
- end
-
- it "responds with status 401" do
- expect(status).to eq(401)
- end
- end
-
- context "when authentication succeeds" do
- before do
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
- end
-
- it "responds with status 404" do
- expect(status).to eq(404)
- end
- end
- end
- end
-
- context "when the Wiki for a project exists" do
- before do
- @wiki = ProjectWiki.new(project)
- env["PATH_INFO"] = "#{@wiki.repository.path_with_namespace}.git/info/refs"
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
-
- it "responds with the right project" do
- response = auth.call(env)
- json_body = ActiveSupport::JSON.decode(response[2][0])
-
- expect(response.first).to eq(200)
- expect(json_body['RepoPath']).to include(@wiki.repository.path_with_namespace)
- end
- end
-
- context "when the project exists" do
- before do
- env["PATH_INFO"] = project.path_with_namespace + ".git"
- end
-
- context "when the project is public" do
- before do
- project.update_attribute(:visibility_level, Project::PUBLIC)
- end
-
- it "responds with status 200" do
- expect(status).to eq(200)
- end
- end
-
- context "when the project is private" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
-
- context "when no authentication is provided" do
- it "responds with status 401" do
- expect(status).to eq(401)
- end
- end
-
- context "when username and password are provided" do
- context "when authentication fails" do
- before do
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope")
- end
-
- it "responds with status 401" do
- expect(status).to eq(401)
- end
-
- context "when the user is IP banned" do
- before do
- expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
- end
-
- it "responds with status 401" do
- expect(status).to eq(401)
- end
- end
- end
-
- context "when authentication succeeds" do
- before do
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
- end
-
- context "when the user has access to the project" do
- before do
- project.team << [user, :master]
- end
-
- context "when the user is blocked" do
- before do
- user.block
- project.team << [user, :master]
- end
-
- it "responds with status 404" do
- expect(status).to eq(404)
- end
- end
-
- context "when the user isn't blocked" do
- before do
- expect(Rack::Attack::Allow2Ban).to receive(:reset)
- end
-
- it "responds with status 200" do
- expect(status).to eq(200)
- end
- end
-
- context "when blank password attempts follow a valid login" do
- let(:options) { Gitlab.config.rack_attack.git_basic_auth }
- let(:maxretry) { options[:maxretry] - 1 }
- let(:ip) { '1.2.3.4' }
-
- before do
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
- Rack::Attack::Allow2Ban.reset(ip, options)
- end
-
- after do
- Rack::Attack::Allow2Ban.reset(ip, options)
- end
-
- def attempt_login(include_password)
- password = include_password ? user.password : ""
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, password)
- Grack::Auth.new(app)
- auth.call(env).first
- end
-
- it "repeated attempts followed by successful attempt" do
- maxretry.times.each do
- expect(attempt_login(false)).to eq(401)
- end
-
- expect(attempt_login(true)).to eq(200)
- expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
-
- maxretry.times.each do
- expect(attempt_login(false)).to eq(401)
- end
- end
- end
- end
-
- context "when the user doesn't have access to the project" do
- it "responds with status 404" do
- expect(status).to eq(404)
- end
- end
- end
- end
-
- context "when a gitlab ci token is provided" do
- let(:token) { "123" }
- let(:project) { FactoryGirl.create :empty_project }
-
- before do
- project.update_attributes(runners_token: token, builds_enabled: true)
-
- env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token)
- end
-
- it "responds with status 200" do
- expect(status).to eq(200)
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index b6f7a2e7ec4..2034445a197 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -42,9 +42,7 @@ describe Gitlab::Badge::Build do
end
context 'build exists' do
- let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) }
- let!(:build) { create(:ci_build, commit: ci_commit) }
-
+ let!(:build) { create_build(project, sha, branch) }
context 'build success' do
before { build.success! }
@@ -96,6 +94,28 @@ describe Gitlab::Badge::Build do
end
end
+ context 'when outdated pipeline for given ref exists' do
+ before do
+ build = create_build(project, sha, branch)
+ build.success!
+
+ old_build = create_build(project, '11eeffdd', branch)
+ old_build.drop!
+ end
+
+ it 'does not take outdated pipeline into account' do
+ expect(badge.to_s).to eq 'build-success'
+ end
+ end
+
+ def create_build(project, sha, branch)
+ pipeline = create(:ci_pipeline, project: project,
+ sha: sha,
+ ref: branch)
+
+ create(:ci_build, pipeline: pipeline)
+ end
+
def status_node(data, status)
xml = Nokogiri::XML.parse(data)
xml.at(%Q{text:contains("#{status}")})
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index 7718689e6d4..760d66a1488 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -1,12 +1,14 @@
require 'spec_helper'
describe Gitlab::BitbucketImport::Client, lib: true do
+ include ImportSpecHelper
+
let(:token) { '123456' }
let(:secret) { 'secret' }
let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) }
before do
- Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+ stub_omniauth_provider('bitbucket')
end
it 'all OAuth client options are symbols' do
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 1a833f255a5..aa00f32becb 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
describe Gitlab::BitbucketImport::Importer, lib: true do
+ include ImportSpecHelper
+
before do
- Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+ stub_omniauth_provider('bitbucket')
end
let(:statuses) do
diff --git a/spec/lib/gitlab/ci/config/loader_spec.rb b/spec/lib/gitlab/ci/config/loader_spec.rb
new file mode 100644
index 00000000000..2d44b1f60f1
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/loader_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Loader do
+ let(:loader) { described_class.new(yml) }
+
+ context 'when yaml syntax is correct' do
+ let(:yml) { 'image: ruby:2.2' }
+
+ describe '#valid?' do
+ it 'returns true' do
+ expect(loader.valid?).to be true
+ end
+ end
+
+ describe '#load!' do
+ it 'returns a valid hash' do
+ expect(loader.load!).to eq(image: 'ruby:2.2')
+ end
+ end
+ end
+
+ context 'when yaml syntax is incorrect' do
+ let(:yml) { '// incorrect' }
+
+ describe '#valid?' do
+ it 'returns false' do
+ expect(loader.valid?).to be false
+ end
+ end
+
+ describe '#load!' do
+ it 'raises error' do
+ expect { loader.load! }.to raise_error(
+ Gitlab::Ci::Config::Loader::FormatError,
+ 'Invalid configuration format'
+ )
+ end
+ end
+ end
+
+ context 'when yaml config is empty' do
+ let(:yml) { '' }
+
+ describe '#valid?' do
+ it 'returns false' do
+ expect(loader.valid?).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
new file mode 100644
index 00000000000..4d46abe520f
--- /dev/null
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config do
+ let(:config) do
+ described_class.new(yml)
+ end
+
+ context 'when config is valid' do
+ let(:yml) do
+ <<-EOS
+ image: ruby:2.2
+
+ rspec:
+ script:
+ - gem install rspec
+ - rspec
+ EOS
+ end
+
+ describe '#to_hash' do
+ it 'returns hash created from string' do
+ hash = {
+ image: 'ruby:2.2',
+ rspec: {
+ script: ['gem install rspec',
+ 'rspec']
+ }
+ }
+
+ expect(config.to_hash).to eq hash
+ end
+ end
+
+ context 'when config is invalid' do
+ let(:yml) { '// invalid' }
+
+ describe '.new' do
+ it 'raises error' do
+ expect { config }.to raise_error(
+ Gitlab::Ci::Config::Loader::FormatError,
+ /Invalid configuration format/
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 35ade7a2be0..83ddabe6b0b 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -16,14 +16,21 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
context 'using PostgreSQL' do
- it 'creates the index concurrently' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ before { expect(Gitlab::Database).to receive(:postgresql?).and_return(true) }
+ it 'creates the index concurrently' do
expect(model).to receive(:add_index).
with(:users, :foo, algorithm: :concurrently)
model.add_concurrent_index(:users, :foo)
end
+
+ it 'creates unique index concurrently' do
+ expect(model).to receive(:add_index).
+ with(:users, :foo, { algorithm: :concurrently, unique: true })
+
+ model.add_concurrent_index(:users, :foo, unique: true)
+ end
end
context 'using MySQL' do
@@ -31,7 +38,7 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
expect(model).to receive(:add_index).
- with(:users, :foo)
+ with(:users, :foo, {})
model.add_concurrent_index(:users, :foo)
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index d0a447753b7..3031559c613 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -39,6 +39,22 @@ describe Gitlab::Database, lib: true do
end
end
+ describe '.nulls_last_order' do
+ context 'when using PostgreSQL' do
+ before { expect(described_class).to receive(:postgresql?).and_return(true) }
+
+ it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
+ it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
+ end
+
+ context 'when using MySQL' do
+ before { expect(described_class).to receive(:postgresql?).and_return(false) }
+
+ it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column IS NULL, column ASC'}
+ it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC'}
+ end
+ end
+
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index 55e86d4ceac..9ae02a6c45f 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -29,6 +29,7 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
commit_id: nil,
line_code: nil,
author_id: project.creator_id,
+ type: nil,
created_at: created_at,
updated_at: updated_at
}
@@ -56,6 +57,7 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do
commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_4_3',
author_id: project.creator_id,
+ type: 'LegacyDiffNote',
created_at: created_at,
updated_at: updated_at
}
diff --git a/spec/lib/gitlab/github_import/hook_formatter_spec.rb b/spec/lib/gitlab/github_import/hook_formatter_spec.rb
new file mode 100644
index 00000000000..110ba428258
--- /dev/null
+++ b/spec/lib/gitlab/github_import/hook_formatter_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::HookFormatter, lib: true do
+ describe '#id' do
+ it 'returns raw id' do
+ raw = double(id: 100000)
+ formatter = described_class.new(raw)
+ expect(formatter.id).to eq 100000
+ end
+ end
+
+ describe '#name' do
+ it 'returns raw id' do
+ raw = double(name: 'web')
+ formatter = described_class.new(raw)
+ expect(formatter.name).to eq 'web'
+ end
+ end
+
+ describe '#config' do
+ it 'returns raw config.attrs' do
+ raw = double(config: double(attrs: { url: 'http://something.com/webhook' }))
+ formatter = described_class.new(raw)
+ expect(formatter.config).to eq({ url: 'http://something.com/webhook' })
+ end
+ end
+
+ describe '#valid?' do
+ it 'returns true when events contains the wildcard event' do
+ raw = double(events: ['*', 'commit_comment'], active: true)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq true
+ end
+
+ it 'returns true when events contains the create event' do
+ raw = double(events: ['create', 'commit_comment'], active: true)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq true
+ end
+
+ it 'returns true when events contains delete event' do
+ raw = double(events: ['delete', 'commit_comment'], active: true)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq true
+ end
+
+ it 'returns true when events contains pull_request event' do
+ raw = double(events: ['pull_request', 'commit_comment'], active: true)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq true
+ end
+
+ it 'returns false when events does not contains branch related events' do
+ raw = double(events: ['member', 'commit_comment'], active: true)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq false
+ end
+
+ it 'returns false when hook is not active' do
+ raw = double(events: ['pull_request', 'commit_comment'], active: false)
+ formatter = described_class.new(raw)
+ expect(formatter.valid?).to eq false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index e6831e7c383..cd8e805466a 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -1,11 +1,13 @@
require 'spec_helper'
describe Gitlab::GitlabImport::Client, lib: true do
+ include ImportSpecHelper
+
let(:token) { '123456' }
let(:client) { Gitlab::GitlabImport::Client.new(token) }
before do
- Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab")
+ stub_omniauth_provider('gitlab')
end
it 'all OAuth2 client options are symbols' do
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
index c2a51d9249c..84c21ceefd9 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -145,6 +145,7 @@ describe Gitlab::Saml::User, lib: true do
allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) }
allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
end
context 'and no account for the LDAP user' do
@@ -177,6 +178,23 @@ describe Gitlab::Saml::User, lib: true do
])
end
end
+
+ context 'user has SAML user, and wants to add their LDAP identity' do
+ it 'adds the LDAP identity to the existing SAML user' do
+ create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'saml', username: 'john')
+ local_hash = OmniAuth::AuthHash.new(uid: 'uid=user1,ou=People,dc=example', provider: provider, info: info_hash)
+ local_saml_user = described_class.new(local_hash)
+ local_saml_user.save
+ local_gl_user = local_saml_user.gl_user
+
+ expect(local_gl_user).to be_valid
+ expect(local_gl_user.identities.length).to eql 2
+ identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: 'uid=user1,ou=People,dc=example' }
+ ])
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index d940bf05061..c5c1402e8fc 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Workhorse, lib: true do
end
it "raises an error" do
- expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError)
+ expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
new file mode 100644
index 00000000000..cb3c592f8cd
--- /dev/null
+++ b/spec/models/award_emoji_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe AwardEmoji, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to(:awardable) }
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'modules' do
+ it { is_expected.to include_module(Participable) }
+ end
+
+ describe "validations" do
+ it { is_expected.to validate_presence_of(:awardable) }
+ it { is_expected.to validate_presence_of(:user) }
+ it { is_expected.to validate_presence_of(:name) }
+
+ # To circumvent a bug in the shoulda matchers
+ describe "scoped uniqueness validation" do
+ it "rejects duplicate award emoji" do
+ user = create(:user)
+ issue = create(:issue)
+ create(:award_emoji, user: user, awardable: issue)
+ new_award = build(:award_emoji, user: user, awardable: issue)
+
+ expect(new_award).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 5c6c30c20ea..2beb6cc598d 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -2,16 +2,16 @@ require 'spec_helper'
describe Ci::Build, models: true do
let(:project) { create(:project) }
- let(:commit) { create(:ci_commit, project: project) }
- let(:build) { create(:ci_build, commit: commit) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :trace_html }
describe '#first_pending' do
- let!(:first) { create(:ci_build, commit: commit, status: 'pending', created_at: Date.yesterday) }
- let!(:second) { create(:ci_build, commit: commit, status: 'pending') }
+ let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
+ let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
subject { Ci::Build.first_pending }
it { is_expected.to be_a(Ci::Build) }
@@ -97,7 +97,7 @@ describe Ci::Build, models: true do
# describe :timeout do
# subject { build.timeout }
#
- # it { is_expected.to eq(commit.project.timeout) }
+ # it { is_expected.to eq(pipeline.project.timeout) }
# end
describe '#options' do
@@ -124,13 +124,13 @@ describe Ci::Build, models: true do
describe '#project' do
subject { build.project }
- it { is_expected.to eq(commit.project) }
+ it { is_expected.to eq(pipeline.project) }
end
describe '#project_id' do
subject { build.project_id }
- it { is_expected.to eq(commit.project_id) }
+ it { is_expected.to eq(pipeline.project_id) }
end
describe '#project_name' do
@@ -219,7 +219,7 @@ describe Ci::Build, models: true do
context 'and trigger variables' do
let(:trigger) { create(:ci_trigger, project: project) }
- let(:trigger_request) { create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) }
+ let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:trigger_variables) do
[
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
@@ -428,10 +428,10 @@ describe Ci::Build, models: true do
end
describe '#depends_on_builds' do
- let!(:build) { create(:ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build') }
- let!(:rspec_test) { create(:ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test') }
- let!(:rubocop_test) { create(:ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test') }
- let!(:staging) { create(:ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy') }
+ let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
+ let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
+ let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
+ let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
it 'to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
@@ -451,19 +451,19 @@ describe Ci::Build, models: true do
end
end
- def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
- create(factory, source_project_id: commit.gl_project_id,
- target_project_id: commit.gl_project_id,
+ def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
+ create(factory, source_project_id: pipeline.gl_project_id,
+ target_project_id: pipeline.gl_project_id,
source_branch: build.ref,
created_at: created_at)
end
describe '#merge_request' do
- context 'when a MR has a reference to the commit' do
+ context 'when a MR has a reference to the pipeline' do
before do
- @merge_request = create_mr(build, commit, factory: :merge_request)
+ @merge_request = create_mr(build, pipeline, factory: :merge_request)
- commits = [double(id: commit.sha)]
+ commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
@@ -473,19 +473,19 @@ describe Ci::Build, models: true do
end
end
- context 'when there is not a MR referencing the commit' do
+ context 'when there is not a MR referencing the pipeline' do
it 'returns nil' do
expect(build.merge_request).to be_nil
end
end
- context 'when more than one MR have a reference to the commit' do
+ context 'when more than one MR have a reference to the pipeline' do
before do
- @merge_request = create_mr(build, commit, factory: :merge_request)
+ @merge_request = create_mr(build, pipeline, factory: :merge_request)
@merge_request.close!
- @merge_request2 = create_mr(build, commit, factory: :merge_request)
+ @merge_request2 = create_mr(build, pipeline, factory: :merge_request)
- commits = [double(id: commit.sha)]
+ commits = [double(id: pipeline.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(@merge_request2).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
@@ -498,11 +498,11 @@ describe Ci::Build, models: true do
context 'when a Build is created after the MR' do
before do
- @merge_request = create_mr(build, commit, factory: :merge_request_with_diffs)
- commit2 = create(:ci_commit, project: project)
- @build2 = create(:ci_build, commit: commit2)
+ @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
+ pipeline2 = create(:ci_pipeline, project: project)
+ @build2 = create(:ci_build, pipeline: pipeline2)
- commits = [double(id: commit.sha), double(id: commit2.sha)]
+ commits = [double(id: pipeline.sha), double(id: pipeline2.sha)]
allow(@merge_request).to receive(:commits).and_return(commits)
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
deleted file mode 100644
index 22f8639e5ab..00000000000
--- a/spec/models/ci/commit_spec.rb
+++ /dev/null
@@ -1,403 +0,0 @@
-require 'spec_helper'
-
-describe Ci::Commit, models: true do
- let(:project) { FactoryGirl.create :empty_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
-
- it { is_expected.to belong_to(:project) }
- it { is_expected.to have_many(:statuses) }
- 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 respond_to :git_author_name }
- it { is_expected.to respond_to :git_author_email }
- it { is_expected.to respond_to :short_sha }
-
- describe :valid_commit_sha do
- context 'commit.sha can not start with 00000000' do
- before do
- commit.sha = '0' * 40
- commit.valid_commit_sha
- end
-
- it('commit errors should not be empty') { expect(commit.errors).not_to be_empty }
- end
- end
-
- describe :short_sha do
- subject { commit.short_sha }
-
- it 'has 8 items' do
- expect(subject.size).to eq(8)
- end
- it { expect(commit.sha).to start_with(subject) }
- end
-
- describe :create_next_builds do
- end
-
- describe :retried do
- subject { commit.retried }
-
- before do
- @commit1 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
- @commit2 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
- end
-
- it 'returns old builds' do
- is_expected.to contain_exactly(@commit1)
- end
- end
-
- describe :create_builds do
- let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false }
-
- def create_builds(trigger_request = nil)
- commit.create_builds(nil, trigger_request)
- end
-
- def create_next_builds
- commit.create_next_builds(commit.builds.order(:id).last)
- 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_next_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(4)
-
- expect(create_next_builds).to be_truthy
- commit.builds.update_all(status: "success")
- expect(commit.builds.count(:all)).to eq(5)
-
- expect(create_next_builds).to be_falsey
- end
-
- context 'custom stage with first job allowed to fail' do
- let(:yaml) do
- {
- stages: ['clean', 'test'],
- clean_job: {
- stage: 'clean',
- allow_failure: true,
- script: 'BUILD',
- },
- test_job: {
- stage: 'test',
- script: 'TEST',
- },
- }
- end
-
- before do
- stub_ci_commit_yaml_file(YAML.dump(yaml))
- create_builds
- end
-
- it 'properly schedules builds' do
- expect(commit.builds.pluck(:status)).to contain_exactly('pending')
- commit.builds.running_or_pending.each(&:drop)
- expect(commit.builds.pluck(:status)).to contain_exactly('pending', 'failed')
- end
- end
-
- context 'properly creates builds when "when" is defined' do
- let(:yaml) do
- {
- stages: ["build", "test", "test_failure", "deploy", "cleanup"],
- build: {
- stage: "build",
- script: "BUILD",
- },
- test: {
- stage: "test",
- script: "TEST",
- },
- test_failure: {
- stage: "test_failure",
- script: "ON test failure",
- when: "on_failure",
- },
- deploy: {
- stage: "deploy",
- script: "PUBLISH",
- },
- cleanup: {
- stage: "cleanup",
- script: "TIDY UP",
- when: "always",
- }
- }
- end
-
- before do
- stub_ci_commit_yaml_file(YAML.dump(yaml))
- end
-
- 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', '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(:status)).to contain_exactly('success', 'success', 'success', 'success')
- commit.reload
- expect(commit.status).to eq('success')
- end
- end
-
- 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', '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(:status)).to contain_exactly('success', 'failed', 'success', 'success')
- commit.reload
- expect(commit.status).to eq('failed')
- end
- end
-
- 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
-
- 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')
- 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', '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', '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(:status)).to contain_exactly('success', 'success', 'failed', 'success')
- commit.reload
- expect(commit.status).to eq('failed')
- end
- end
-
- 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.running_or_pending).not_to be_empty
-
- 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.running_or_pending).to be_empty
- expect(commit.reload.status).to eq('canceled')
- end
- end
- end
- end
-
- describe "#finished_at" do
- let(:commit) { FactoryGirl.create :ci_commit }
-
- it "returns finished_at of latest build" do
- build = FactoryGirl.create :ci_build, commit: commit, finished_at: Time.now - 60
- FactoryGirl.create :ci_build, commit: commit, finished_at: Time.now - 120
-
- expect(commit.finished_at.to_i).to eq(build.finished_at.to_i)
- end
-
- it "returns nil if there is no finished build" do
- FactoryGirl.create :ci_not_started_build, commit: commit
-
- expect(commit.finished_at).to be_nil
- end
- end
-
- describe "coverage" do
- let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
-
- it "calculates average when there are two builds with coverage" do
- FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
- FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, commit: commit
- expect(commit.coverage).to eq("35.00")
- end
-
- it "calculates average when there are two builds with coverage and one with nil" do
- FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
- FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, commit: commit
- FactoryGirl.create :ci_build, commit: commit
- expect(commit.coverage).to eq("35.00")
- end
-
- it "calculates average when there are two builds with coverage and one is retried" do
- FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
- FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, commit: commit
- FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, commit: commit
- expect(commit.coverage).to eq("35.00")
- end
-
- it "calculates average when there is one build without coverage" do
- FactoryGirl.create :ci_build, commit: commit
- 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/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
new file mode 100644
index 00000000000..0d769ed7324
--- /dev/null
+++ b/spec/models/ci/pipeline_spec.rb
@@ -0,0 +1,403 @@
+require 'spec_helper'
+
+describe Ci::Pipeline, models: true do
+ let(:project) { FactoryGirl.create :empty_project }
+ let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:statuses) }
+ 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 respond_to :git_author_name }
+ it { is_expected.to respond_to :git_author_email }
+ it { is_expected.to respond_to :short_sha }
+
+ describe :valid_commit_sha do
+ context 'commit.sha can not start with 00000000' do
+ before do
+ pipeline.sha = '0' * 40
+ pipeline.valid_commit_sha
+ end
+
+ it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
+ end
+ end
+
+ describe :short_sha do
+ subject { pipeline.short_sha }
+
+ it 'has 8 items' do
+ expect(subject.size).to eq(8)
+ end
+ it { expect(pipeline.sha).to start_with(subject) }
+ end
+
+ describe :create_next_builds do
+ end
+
+ describe :retried do
+ subject { pipeline.retried }
+
+ before do
+ @build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
+ @build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
+ end
+
+ it 'returns old builds' do
+ is_expected.to contain_exactly(@build1)
+ end
+ end
+
+ describe :create_builds do
+ let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project, ref: 'master', tag: false }
+
+ def create_builds(trigger_request = nil)
+ pipeline.create_builds(nil, trigger_request)
+ end
+
+ def create_next_builds
+ pipeline.create_next_builds(pipeline.builds.order(:id).last)
+ end
+
+ it 'creates builds' do
+ expect(create_builds).to be_truthy
+ pipeline.builds.update_all(status: "success")
+ expect(pipeline.builds.count(:all)).to eq(2)
+
+ expect(create_next_builds).to be_truthy
+ pipeline.builds.update_all(status: "success")
+ expect(pipeline.builds.count(:all)).to eq(4)
+
+ expect(create_next_builds).to be_truthy
+ pipeline.builds.update_all(status: "success")
+ expect(pipeline.builds.count(:all)).to eq(5)
+
+ expect(create_next_builds).to be_falsey
+ end
+
+ context 'custom stage with first job allowed to fail' do
+ let(:yaml) do
+ {
+ stages: ['clean', 'test'],
+ clean_job: {
+ stage: 'clean',
+ allow_failure: true,
+ script: 'BUILD',
+ },
+ test_job: {
+ stage: 'test',
+ script: 'TEST',
+ },
+ }
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(yaml))
+ create_builds
+ end
+
+ it 'properly schedules builds' do
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending', 'failed')
+ end
+ end
+
+ context 'properly creates builds when "when" is defined' do
+ let(:yaml) do
+ {
+ stages: ["build", "test", "test_failure", "deploy", "cleanup"],
+ build: {
+ stage: "build",
+ script: "BUILD",
+ },
+ test: {
+ stage: "test",
+ script: "TEST",
+ },
+ test_failure: {
+ stage: "test_failure",
+ script: "ON test failure",
+ when: "on_failure",
+ },
+ deploy: {
+ stage: "deploy",
+ script: "PUBLISH",
+ },
+ cleanup: {
+ stage: "cleanup",
+ script: "TIDY UP",
+ when: "always",
+ }
+ }
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(yaml))
+ end
+
+ context 'when builds are successful' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('success')
+ end
+ end
+
+ context 'when test job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ context 'when test and test_failure jobs fail' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ context 'when deploy job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ 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(pipeline.builds.pluck(:name)).to contain_exactly('build')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(pipeline.builds.running_or_pending).not_to be_empty
+
+ expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:cancel)
+
+ expect(pipeline.builds.running_or_pending).to be_empty
+ expect(pipeline.reload.status).to eq('canceled')
+ end
+ end
+ end
+ end
+
+ describe "#finished_at" do
+ let(:pipeline) { FactoryGirl.create :ci_pipeline }
+
+ it "returns finished_at of latest build" do
+ build = FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 60
+ FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 120
+
+ expect(pipeline.finished_at.to_i).to eq(build.finished_at.to_i)
+ end
+
+ it "returns nil if there is no finished build" do
+ FactoryGirl.create :ci_not_started_build, pipeline: pipeline
+
+ expect(pipeline.finished_at).to be_nil
+ end
+ end
+
+ describe "coverage" do
+ let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
+ let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
+
+ it "calculates average when there are two builds with coverage" do
+ FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
+ FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
+ expect(pipeline.coverage).to eq("35.00")
+ end
+
+ it "calculates average when there are two builds with coverage and one with nil" do
+ FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
+ FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
+ FactoryGirl.create :ci_build, pipeline: pipeline
+ expect(pipeline.coverage).to eq("35.00")
+ end
+
+ it "calculates average when there are two builds with coverage and one is retried" do
+ FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
+ FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline
+ FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
+ expect(pipeline.coverage).to eq("35.00")
+ end
+
+ it "calculates average when there is one build without coverage" do
+ FactoryGirl.create :ci_build, pipeline: pipeline
+ expect(pipeline.coverage).to be_nil
+ end
+ end
+
+ describe '#retryable?' do
+ subject { pipeline.retryable? }
+
+ context 'no failed builds' do
+ before do
+ FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, 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", pipeline: pipeline, status: 'running'
+ FactoryGirl.create :ci_build, name: "rubocop", pipeline: pipeline, status: 'failed'
+ end
+
+ it 'be retryable' do
+ is_expected.to be_truthy
+ end
+ end
+ end
+
+ describe '#stages' do
+ let(:pipeline2) { FactoryGirl.create :ci_pipeline, project: project }
+ subject { CommitStatus.where(pipeline: [pipeline, pipeline2]).stages }
+
+ before do
+ FactoryGirl.create :ci_build, pipeline: pipeline2, stage: 'test', stage_idx: 1
+ FactoryGirl.create :ci_build, pipeline: pipeline, 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(pipeline).to receive(:update_state).and_return(true)
+ pipeline.touch
+ end
+
+ context 'dependent objects' do
+ let(:commit_status) { build :commit_status, pipeline: pipeline }
+
+ it 'execute update_state after saving dependent object' do
+ expect(pipeline).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, pipeline: pipeline, 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(pipeline.send(param)).to eq(build.send(param))
+ end
+ end
+ end
+ end
+
+ describe '#branch?' do
+ subject { pipeline.branch? }
+
+ context 'is not a tag' do
+ before do
+ pipeline.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
+ pipeline.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/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 434e58cfd06..8fb605fff8a 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,18 +1,18 @@
require 'spec_helper'
describe CommitStatus, models: true do
- let(:commit) { FactoryGirl.create :ci_commit }
- let(:commit_status) { FactoryGirl.create :commit_status, commit: commit }
+ let(:pipeline) { FactoryGirl.create :ci_pipeline }
+ let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline }
- it { is_expected.to belong_to(:commit) }
+ it { is_expected.to belong_to(:pipeline) }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:project) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }
- it { is_expected.to delegate_method(:sha).to(:commit) }
- it { is_expected.to delegate_method(:short_sha).to(:commit) }
+ it { is_expected.to delegate_method(:sha).to(:pipeline) }
+ it { is_expected.to delegate_method(:short_sha).to(:pipeline) }
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
@@ -121,11 +121,11 @@ describe CommitStatus, models: true do
subject { CommitStatus.latest.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: 'cc', status: 'success'
- @commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'bb', status: 'success'
- @commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'success'
+ @commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running'
+ @commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending'
+ @commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'cc', status: 'success'
+ @commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'bb', status: 'success'
+ @commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'success'
end
it 'return unique statuses' do
@@ -137,11 +137,11 @@ describe CommitStatus, models: true 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'
+ @commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running'
+ @commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending'
+ @commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: nil, status: 'success'
+ @commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'dd', ref: nil, status: 'failed'
+ @commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'ee', ref: nil, status: 'canceled'
end
it 'return statuses that are running or pending' do
@@ -152,17 +152,17 @@ describe CommitStatus, models: true do
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 }
+ context 'when no before_sha is set for pipeline' do
+ before { pipeline.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
+ context 'for before_sha set for pipeline' do
let(:value) { '1234' }
- before { commit.before_sha = value }
+ before { pipeline.before_sha = value }
it 'return the set value' do
is_expected.to eq(value)
@@ -172,14 +172,14 @@ describe CommitStatus, models: true do
describe '#stages' do
before do
- 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'
+ FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success'
+ FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed'
+ FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running'
+ FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success'
end
context 'stages list' do
- subject { CommitStatus.where(commit: commit).stages }
+ subject { CommitStatus.where(pipeline: pipeline).stages }
it 'return ordered list of stages' do
is_expected.to eq(%w(build test deploy))
@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
end
context 'stages with statuses' do
- subject { CommitStatus.where(commit: commit).stages_status }
+ subject { CommitStatus.where(pipeline: pipeline).stages_status }
it 'return list of stages with statuses' do
is_expected.to eq({
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
new file mode 100644
index 00000000000..a371c4a18a9
--- /dev/null
+++ b/spec/models/concerns/awardable_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Issue, "Awardable" do
+ let!(:issue) { create(:issue) }
+ let!(:award_emoji) { create(:award_emoji, :downvote, awardable: issue) }
+
+ describe "Associations" do
+ it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
+ end
+
+ describe "ClassMethods" do
+ let!(:issue2) { create(:issue) }
+
+ before do
+ create(:award_emoji, awardable: issue2)
+ end
+
+ it "orders on upvotes" do
+ expect(Issue.order_upvotes_desc.to_a).to eq [issue2, issue]
+ end
+
+ it "orders on downvotes" do
+ expect(Issue.order_downvotes_desc.to_a).to eq [issue, issue2]
+ end
+ end
+
+ describe "#upvotes" do
+ it "counts the number of upvotes" do
+ expect(issue.upvotes).to be 0
+ end
+ end
+
+ describe "#downvotes" do
+ it "counts the number of downvotes" do
+ expect(issue.downvotes).to be 1
+ end
+ end
+
+ describe "#toggle_award_emoji" do
+ it "adds an emoji if it isn't awarded yet" do
+ expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)
+ end
+
+ it "toggles already awarded emoji" do
+ expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1)
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index fb20578d8d3..efbcbf72f76 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -10,6 +10,20 @@ describe Issue, "Issuable" do
it { is_expected.to belong_to(:assignee) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
+
+ context 'Notes' do
+ let!(:note) { create(:note, noteable: issue, project: issue.project) }
+ let(:scoped_issue) { Issue.includes(notes: :author).find(issue.id) }
+
+ it 'indicates if the notes have their authors loaded' do
+ expect(issue.notes).not_to be_authors_loaded
+ expect(scoped_issue.notes).to be_authors_loaded
+ end
+ end
+ end
+
+ describe 'Included modules' do
+ it { is_expected.to include_module(Awardable) }
end
describe "Validation" do
@@ -227,12 +241,42 @@ describe Issue, "Issuable" do
end
end
+ describe '#labels_array' do
+ let(:project) { create(:project) }
+ let(:bug) { create(:label, project: project, title: 'bug') }
+ let(:issue) { create(:issue, project: project) }
+
+ before(:each) do
+ issue.labels << bug
+ end
+
+ it 'loads the association and returns it as an array' do
+ expect(issue.reload.labels_array).to eq([bug])
+ end
+ end
+
+ describe '#user_notes_count' do
+ let(:project) { create(:project) }
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+
+ before do
+ create_list(:note, 3, noteable: issue1, project: project)
+ create_list(:note, 6, noteable: issue2, project: project)
+ end
+
+ it 'counts the user notes' do
+ expect(issue1.user_notes_count).to be(3)
+ expect(issue2.user_notes_count).to be(6)
+ end
+ end
+
describe "votes" do
let(:project) { issue.project }
before do
- issue.notes.awards.create!(note: "thumbsup", author: user, project: project)
- issue.notes.awards.create!(note: "thumbsdown", author: user, project: project)
+ create(:award_emoji, :upvote, awardable: issue)
+ create(:award_emoji, :downvote, awardable: issue)
end
it "returns correct values" do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index d0e02618b6b..c4e781dd1dc 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe GenericCommitStatus, models: true do
- let(:commit) { FactoryGirl.create :ci_commit }
- let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit }
+ let(:pipeline) { FactoryGirl.create :ci_pipeline }
+ let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
describe :context do
subject { generic_commit_status.context }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 87b3d8d650a..b87d68283e6 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -269,4 +269,21 @@ describe Issue, models: true do
end
end
end
+
+ describe 'cached counts' do
+ it 'updates when assignees change' do
+ user1 = create(:user)
+ user2 = create(:user)
+ issue = create(:issue, assignee: user1)
+
+ expect(user1.assigned_open_issues_count).to eq(1)
+ expect(user2.assigned_open_issues_count).to eq(0)
+
+ issue.assignee = user2
+ issue.save
+
+ expect(user1.assigned_open_issues_count).to eq(0)
+ expect(user2.assigned_open_issues_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 118e1e22a78..3b199f4d98d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -390,19 +390,19 @@ describe MergeRequest, models: true do
subject { create :merge_request, :simple }
end
- describe '#ci_commit' do
+ describe '#pipeline' do
describe 'when the source project exists' do
it 'returns the latest commit' do
- commit = double(:commit, id: '123abc')
- ci_commit = double(:ci_commit, ref: 'master')
+ commit = double(:commit, id: '123abc')
+ pipeline = double(:ci_pipeline, ref: 'master')
allow(subject).to receive(:last_commit).and_return(commit)
- expect(subject.source_project).to receive(:ci_commit).
+ expect(subject.source_project).to receive(:pipeline).
with('123abc', 'master').
- and_return(ci_commit)
+ and_return(pipeline)
- expect(subject.ci_commit).to eq(ci_commit)
+ expect(subject.pipeline).to eq(pipeline)
end
end
@@ -410,7 +410,7 @@ describe MergeRequest, models: true do
it 'returns nil' do
allow(subject).to receive(:source_project).and_return(nil)
- expect(subject.ci_commit).to be_nil
+ expect(subject.pipeline).to be_nil
end
end
end
@@ -438,4 +438,174 @@ describe MergeRequest, models: true do
expect(mr.participants).to include(note1.author, note2.author)
end
end
+
+ describe 'cached counts' do
+ it 'updates when assignees change' do
+ user1 = create(:user)
+ user2 = create(:user)
+ mr = create(:merge_request, assignee: user1)
+
+ expect(user1.assigned_open_merge_request_count).to eq(1)
+ expect(user2.assigned_open_merge_request_count).to eq(0)
+
+ mr.assignee = user2
+ mr.save
+
+ expect(user1.assigned_open_merge_request_count).to eq(0)
+ expect(user2.assigned_open_merge_request_count).to eq(1)
+ end
+ end
+
+ describe '#check_if_can_be_merged' do
+ let(:project) { create(:project, only_allow_merge_if_build_succeeds: true) }
+
+ subject { create(:merge_request, source_project: project, merge_status: :unchecked) }
+
+ context 'when it is not broken and has no conflicts' do
+ it 'is marked as mergeable' do
+ allow(subject).to receive(:broken?) { false }
+ allow(project).to receive_message_chain(:repository, :can_be_merged?) { true }
+
+ expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
+ end
+ end
+
+ context 'when broken' do
+ before { allow(subject).to receive(:broken?) { true } }
+
+ it 'becomes unmergeable' do
+ expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
+ end
+ end
+
+ context 'when it has conflicts' do
+ before do
+ allow(subject).to receive(:broken?) { false }
+ allow(project).to receive_message_chain(:repository, :can_be_merged?) { false }
+ end
+
+ it 'becomes unmergeable' do
+ expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
+ end
+ end
+ end
+
+ describe '#mergeable?' do
+ let(:project) { create(:project) }
+
+ subject { create(:merge_request, source_project: project) }
+
+ it 'returns false if #mergeable_state? is false' do
+ expect(subject).to receive(:mergeable_state?) { false }
+
+ expect(subject.mergeable?).to be_falsey
+ end
+
+ it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
+ allow(subject).to receive(:mergeable_state?) { true }
+ expect(subject).to receive(:check_if_can_be_merged)
+ expect(subject).to receive(:can_be_merged?) { true }
+
+ expect(subject.mergeable?).to be_truthy
+ end
+ end
+
+ describe '#mergeable_state?' do
+ let(:project) { create(:project) }
+
+ subject { create(:merge_request, source_project: project) }
+
+ it 'checks if merge request can be merged' do
+ allow(subject).to receive(:mergeable_ci_state?) { true }
+ expect(subject).to receive(:check_if_can_be_merged)
+
+ subject.mergeable?
+ end
+
+ context 'when not open' do
+ before { subject.close }
+
+ it 'returns false' do
+ expect(subject.mergeable_state?).to be_falsey
+ end
+ end
+
+ context 'when working in progress' do
+ before { subject.title = 'WIP MR' }
+
+ it 'returns false' do
+ expect(subject.mergeable_state?).to be_falsey
+ end
+ end
+
+ context 'when broken' do
+ before { allow(subject).to receive(:broken?) { true } }
+
+ it 'returns false' do
+ expect(subject.mergeable_state?).to be_falsey
+ end
+ end
+
+ context 'when failed' do
+ before { allow(subject).to receive(:broken?) { false } }
+
+ context 'when project settings restrict to merge only if build succeeds and build failed' do
+ before do
+ project.only_allow_merge_if_build_succeeds = true
+ allow(subject).to receive(:mergeable_ci_state?) { false }
+ end
+
+ it 'returns false' do
+ expect(subject.mergeable_state?).to be_falsey
+ end
+ end
+ end
+ end
+
+ describe '#mergeable_ci_state?' do
+ let(:project) { create(:empty_project, only_allow_merge_if_build_succeeds: true) }
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ subject { build(:merge_request, target_project: project) }
+
+ context 'when it is only allowed to merge when build is green' do
+ context 'and a failed pipeline is associated' do
+ before do
+ pipeline.statuses << create(:commit_status, status: 'failed', project: project)
+ allow(subject).to receive(:pipeline) { pipeline }
+ end
+
+ it { expect(subject.mergeable_ci_state?).to be_falsey }
+ end
+
+ context 'when no pipeline is associated' do
+ before do
+ allow(subject).to receive(:pipeline) { nil }
+ end
+
+ it { expect(subject.mergeable_ci_state?).to be_truthy }
+ end
+ end
+
+ context 'when merges are not restricted to green builds' do
+ subject { build(:merge_request, target_project: build(:empty_project, only_allow_merge_if_build_succeeds: false)) }
+
+ context 'and a failed pipeline is associated' do
+ before do
+ pipeline.statuses << create(:commit_status, status: 'failed', project: project)
+ allow(subject).to receive(:pipeline) { pipeline }
+ end
+
+ it { expect(subject.mergeable_ci_state?).to be_truthy }
+ end
+
+ context 'when no pipeline is associated' do
+ before do
+ allow(subject).to receive(:pipeline) { nil }
+ end
+
+ it { expect(subject.mergeable_ci_state?).to be_truthy }
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index e9d89c9a847..f15e96714b2 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -9,6 +9,16 @@ describe Note, models: true do
it { is_expected.to have_many(:todos).dependent(:destroy) }
end
+ describe 'modules' do
+ subject { described_class }
+
+ it { is_expected.to include_module(Participable) }
+ it { is_expected.to include_module(Mentionable) }
+ it { is_expected.to include_module(Awardable) }
+
+ it { is_expected.to include_module(Gitlab::CurrentSettings) }
+ end
+
describe 'validation' do
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
@@ -171,23 +181,6 @@ describe Note, models: true do
end
end
- describe '.grouped_awards' do
- before do
- create :note, note: "smile", is_award: true
- create :note, note: "smile", is_award: true
- end
-
- it "returns grouped hash of notes" do
- expect(Note.grouped_awards.keys.size).to eq(3)
- expect(Note.grouped_awards["smile"]).to match_array(Note.all)
- end
-
- it "returns thumbsup and thumbsdown always" do
- expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none)
- expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none)
- end
- end
-
describe "editable?" do
it "returns true" do
note = build(:note)
@@ -198,11 +191,6 @@ describe Note, models: true do
note = build(:note, system: true)
expect(note.editable?).to be_falsy
end
-
- it "returns false" do
- note = build(:note, is_award: true, note: "smiley")
- expect(note.editable?).to be_falsy
- end
end
describe "cross_reference_not_visible_for?" do
@@ -229,29 +217,6 @@ describe Note, models: true do
end
end
- describe "set_award!" do
- let(:merge_request) { create :merge_request }
-
- it "converts aliases to actual name" do
- note = create(:note, note: ":+1:",
- noteable: merge_request,
- project: merge_request.project)
-
- expect(note.reload.note).to eq("thumbsup")
- end
-
- it "is not an award emoji when comment is on a diff" do
- note = create(:note_on_merge_request_diff, note: ":blowfish:",
- noteable: merge_request,
- project: merge_request.project,
- line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
- note = note.reload
-
- expect(note.note).to eq(":blowfish:")
- expect(note.is_award?).to be_falsy
- end
- end
-
describe 'clear_blank_line_code!' do
it 'clears a blank line code before validation' do
note = build(:note, line_code: ' ')
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index e771f35811e..ec81f05fc7a 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -194,7 +194,7 @@ describe BambooService, models: true do
def service(bamboo_url: 'http://gitlab.com')
described_class.create(
- project: build_stubbed(:empty_project),
+ project: create(:empty_project),
properties: {
bamboo_url: bamboo_url,
username: 'mic',
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index ad24b895170..24a708ca849 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -182,7 +182,7 @@ describe TeamcityService, models: true do
def service(teamcity_url: 'http://gitlab.com')
described_class.create(
- project: build_stubbed(:empty_project),
+ project: create(:empty_project),
properties: {
teamcity_url: teamcity_url,
username: 'mic',
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 338a4c3d3f0..f3590f72cfe 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -22,7 +22,7 @@ describe Project, models: true do
it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:commit_statuses) }
- it { is_expected.to have_many(:ci_commits) }
+ it { is_expected.to have_many(:pipelines) }
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) }
@@ -258,6 +258,69 @@ describe Project, models: true do
end
end
+ describe :external_issue_tracker do
+ let(:project) { create(:project) }
+ let(:ext_project) { create(:redmine_project) }
+
+ context 'on existing projects with no value for has_external_issue_tracker' do
+ before(:each) do
+ project.update_column(:has_external_issue_tracker, nil)
+ ext_project.update_column(:has_external_issue_tracker, nil)
+ end
+
+ it 'updates the has_external_issue_tracker boolean' do
+ expect do
+ project.external_issue_tracker
+ end.to change { project.reload.has_external_issue_tracker }.to(false)
+
+ expect do
+ ext_project.external_issue_tracker
+ end.to change { ext_project.reload.has_external_issue_tracker }.to(true)
+ end
+ end
+
+ it 'returns nil and does not query services when there is no external issue tracker' do
+ project.build_missing_services
+ project.reload
+
+ expect(project).not_to receive(:services)
+
+ expect(project.external_issue_tracker).to eq(nil)
+ end
+
+ it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do
+ ext_project.reload # Factory returns a project with changed attributes
+ ext_project.build_missing_services
+ ext_project.reload
+
+ expect(ext_project).to receive(:services).once.and_call_original
+
+ 2.times { expect(ext_project.external_issue_tracker).to be_a_kind_of(RedmineService) }
+ end
+ end
+
+ describe :cache_has_external_issue_tracker do
+ let(:project) { create(:project) }
+
+ it 'stores true if there is any external_issue_tracker' do
+ services = double(:service, external_issue_trackers: [RedmineService.new])
+ expect(project).to receive(:services).and_return(services)
+
+ expect do
+ project.cache_has_external_issue_tracker
+ end.to change { project.has_external_issue_tracker}.to(true)
+ end
+
+ it 'stores false if there is no external_issue_tracker' do
+ services = double(:service, external_issue_trackers: [])
+ expect(project).to receive(:services).and_return(services)
+
+ expect do
+ project.cache_has_external_issue_tracker
+ end.to change { project.has_external_issue_tracker}.to(false)
+ end
+ end
+
describe :can_have_issues_tracker_id? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
@@ -399,23 +462,23 @@ describe Project, models: true do
end
end
- describe :ci_commit do
+ describe :pipeline do
let(:project) { create :project }
- let(:commit) { create :ci_commit, project: project, ref: 'master' }
+ let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
- subject { project.ci_commit(commit.sha, 'master') }
+ subject { project.pipeline(pipeline.sha, 'master') }
- it { is_expected.to eq(commit) }
+ it { is_expected.to eq(pipeline) }
context 'return latest' do
- let(:commit2) { create :ci_commit, project: project, ref: 'master' }
+ let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
before do
- commit
- commit2
+ pipeline
+ pipeline2
end
- it { is_expected.to eq(commit2) }
+ it { is_expected.to eq(pipeline2) }
end
end
@@ -859,4 +922,37 @@ describe Project, models: true do
it { is_expected.to be_falsey }
end
end
+
+ describe '.where_paths_in' do
+ context 'without any paths' do
+ it 'returns an empty relation' do
+ expect(Project.where_paths_in([])).to eq([])
+ end
+ end
+
+ context 'without any valid paths' do
+ it 'returns an empty relation' do
+ expect(Project.where_paths_in(%w[foo])).to eq([])
+ end
+ end
+
+ context 'with valid paths' do
+ let!(:project1) { create(:project) }
+ let!(:project2) { create(:project) }
+
+ it 'returns the projects matching the paths' do
+ projects = Project.where_paths_in([project1.path_with_namespace,
+ project2.path_with_namespace])
+
+ expect(projects).to contain_exactly(project1, project2)
+ end
+
+ it 'returns projects regardless of the casing of paths' do
+ projects = Project.where_paths_in([project1.path_with_namespace.upcase,
+ project2.path_with_namespace.upcase])
+
+ expect(projects).to contain_exactly(project1, project2)
+ end
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 8592e112c50..2f000dbc01a 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -204,4 +204,37 @@ describe Service, models: true do
expect(service.bamboo_url_was).to be_nil
end
end
+
+ describe "callbacks" do
+ let(:project) { create(:project) }
+ let!(:service) do
+ RedmineService.new(
+ project: project,
+ active: true,
+ properties: {
+ project_url: 'http://redmine/projects/project_name_in_redmine',
+ issues_url: "http://redmine/#{project.id}/project_name_in_redmine/:id",
+ new_issue_url: 'http://redmine/projects/project_name_in_redmine/issues/new'
+ }
+ )
+ end
+
+ describe "on create" do
+ it "updates the has_external_issue_tracker boolean" do
+ expect do
+ service.save!
+ end.to change { service.project.has_external_issue_tracker }.from(nil).to(true)
+ end
+ end
+
+ describe "on update" do
+ it "updates the has_external_issue_tracker boolean" do
+ service.save!
+
+ expect do
+ service.update_attributes(active: false)
+ end.to change { service.project.has_external_issue_tracker }.from(true).to(false)
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 548bec364f8..73bee535fe3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -30,6 +30,7 @@ describe User, models: true do
it { is_expected.to have_one(:abuse_report) }
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
+ it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
end
describe 'validations' do
@@ -67,7 +68,10 @@ describe User, models: true do
describe 'email' do
context 'when no signup domains listed' do
- before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return([]) }
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return([])
+ end
+
it 'accepts any email' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
@@ -75,7 +79,10 @@ describe User, models: true do
end
context 'when a signup domain is listed and subdomains are allowed' do
- before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com']) }
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com'])
+ end
+
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
@@ -93,7 +100,9 @@ describe User, models: true do
end
context 'when a signup domain is listed and subdomains are not allowed' do
- before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com']) }
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com'])
+ end
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
@@ -120,6 +129,66 @@ describe User, models: true do
end
end
+ describe "scopes" do
+ describe ".with_two_factor" do
+ it "returns users with 2fa enabled via OTP" do
+ user_with_2fa = create(:user, :two_factor_via_otp)
+ user_without_2fa = create(:user)
+ users_with_two_factor = User.with_two_factor.pluck(:id)
+
+ expect(users_with_two_factor).to include(user_with_2fa.id)
+ expect(users_with_two_factor).not_to include(user_without_2fa.id)
+ end
+
+ it "returns users with 2fa enabled via U2F" do
+ user_with_2fa = create(:user, :two_factor_via_u2f)
+ user_without_2fa = create(:user)
+ users_with_two_factor = User.with_two_factor.pluck(:id)
+
+ expect(users_with_two_factor).to include(user_with_2fa.id)
+ expect(users_with_two_factor).not_to include(user_without_2fa.id)
+ end
+
+ it "returns users with 2fa enabled via OTP and U2F" do
+ user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
+ user_without_2fa = create(:user)
+ users_with_two_factor = User.with_two_factor.pluck(:id)
+
+ expect(users_with_two_factor).to eq([user_with_2fa.id])
+ expect(users_with_two_factor).not_to include(user_without_2fa.id)
+ end
+ end
+
+ describe ".without_two_factor" do
+ it "excludes users with 2fa enabled via OTP" do
+ user_with_2fa = create(:user, :two_factor_via_otp)
+ user_without_2fa = create(:user)
+ users_without_two_factor = User.without_two_factor.pluck(:id)
+
+ expect(users_without_two_factor).to include(user_without_2fa.id)
+ expect(users_without_two_factor).not_to include(user_with_2fa.id)
+ end
+
+ it "excludes users with 2fa enabled via U2F" do
+ user_with_2fa = create(:user, :two_factor_via_u2f)
+ user_without_2fa = create(:user)
+ users_without_two_factor = User.without_two_factor.pluck(:id)
+
+ expect(users_without_two_factor).to include(user_without_2fa.id)
+ expect(users_without_two_factor).not_to include(user_with_2fa.id)
+ end
+
+ it "excludes users with 2fa enabled via OTP and U2F" do
+ user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
+ user_without_2fa = create(:user)
+ users_without_two_factor = User.without_two_factor.pluck(:id)
+
+ expect(users_without_two_factor).to include(user_without_2fa.id)
+ expect(users_without_two_factor).not_to include(user_with_2fa.id)
+ end
+ end
+ end
+
describe "Respond to" do
it { is_expected.to respond_to(:is_admin?) }
it { is_expected.to respond_to(:name) }
@@ -141,7 +210,10 @@ describe User, models: true do
end
describe '#confirm' do
- before { allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) }
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
+ end
+
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do
@@ -784,6 +856,75 @@ describe User, models: true do
it { is_expected.to eq([private_project]) }
end
+ describe '#ci_authorized_runners' do
+ let(:user) { create(:user) }
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ project.runners << runner
+ end
+
+ context 'without any projects' do
+ let(:project) { create(:project) }
+
+ it 'does not load' do
+ expect(user.ci_authorized_runners).to be_empty
+ end
+ end
+
+ context 'with personal projects runners' do
+ let(:namespace) { create(:namespace, owner: user) }
+ let(:project) { create(:project, namespace: namespace) }
+
+ it 'loads' do
+ expect(user.ci_authorized_runners).to contain_exactly(runner)
+ end
+ end
+
+ shared_examples :member do
+ context 'when the user is a master' do
+ before do
+ add_user(Gitlab::Access::MASTER)
+ end
+
+ it 'loads' do
+ expect(user.ci_authorized_runners).to contain_exactly(runner)
+ end
+ end
+
+ context 'when the user is a developer' do
+ before do
+ add_user(Gitlab::Access::DEVELOPER)
+ end
+
+ it 'does not load' do
+ expect(user.ci_authorized_runners).to be_empty
+ end
+ end
+ end
+
+ context 'with groups projects runners' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ def add_user(access)
+ group.add_user(user, access)
+ end
+
+ it_behaves_like :member
+ end
+
+ context 'with other projects runners' do
+ let(:project) { create(:project) }
+
+ def add_user(access)
+ project.team << [user, access]
+ end
+
+ it_behaves_like :member
+ end
+ end
+
describe '#viewable_starred_projects' do
let(:user) { create(:user) }
let(:public_project) { create(:empty_project, :public) }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 0fbc984c061..6cb7be188ef 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -9,8 +9,8 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) }
let!(:developer) { create(:project_member, :developer, user: user, project: project) }
let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
- let(:commit) { create(:ci_commit, project: project)}
- let(:build) { create(:ci_build, commit: commit) }
+ let(:pipeline) { create(:ci_pipeline, project: project)}
+ let(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
@@ -59,8 +59,8 @@ describe API::API, api: true do
describe 'GET /projects/:id/repository/commits/:sha/builds' do
before do
- project.ensure_ci_commit(commit.sha, 'master')
- get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
+ project.ensure_pipeline(pipeline.sha, 'master')
+ get api("/projects/#{project.id}/repository/commits/#{pipeline.sha}/builds", api_user)
end
context 'authorized user' do
@@ -102,7 +102,7 @@ describe API::API, api: true do
before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
context 'build with artifacts' do
- let(:build) { create(:ci_build, :artifacts, commit: commit) }
+ let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
context 'authorized user' do
let(:download_headers) do
@@ -131,7 +131,7 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/builds/:build_id/trace' do
- let(:build) { create(:ci_build, :trace, commit: commit) }
+ let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
@@ -181,7 +181,7 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/builds/:build_id/retry' do
- let(:build) { create(:ci_build, :canceled, commit: commit) }
+ let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
@@ -218,7 +218,7 @@ describe API::API, api: true do
end
context 'build is erasable' do
- let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, commit: commit) }
+ let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
it 'should erase build content' do
expect(response.status).to eq 201
@@ -234,7 +234,7 @@ describe API::API, api: true do
end
context 'build is not erasable' do
- let(:build) { create(:ci_build, :trace, project: project, commit: commit) }
+ let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
it 'should respond with forbidden' do
expect(response.status).to eq 403
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 633927c8c3e..298cdbad329 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -5,7 +5,7 @@ describe API::CommitStatuses, api: true do
let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
- let(:commit_status) { create(:commit_status, commit: ci_commit) }
+ let(:commit_status) { create(:commit_status, pipeline: pipeline) }
let(:guest) { create_user(:guest) }
let(:reporter) { create_user(:reporter) }
let(:developer) { create_user(:developer) }
@@ -16,8 +16,8 @@ describe API::CommitStatuses, api: true do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') }
- let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') }
+ let!(:master) { project.pipelines.create(sha: commit.id, ref: 'master') }
+ let!(:develop) { project.pipelines.create(sha: commit.id, ref: 'develop') }
it_behaves_like 'a paginated resources' do
let(:request) { get api(get_url, reporter) }
@@ -27,7 +27,7 @@ describe API::CommitStatuses, api: true do
let(:statuses_id) { json_response.map { |status| status['id'] } }
def create_status(commit, opts = {})
- create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts))
+ create(:commit_status, { pipeline: commit, ref: commit.ref }.merge(opts))
end
let!(:status1) { create_status(master, status: 'running') }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index cb82ca7802d..6fc38f537d3 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -90,10 +90,10 @@ describe API::API, api: true do
end
it "should return status for CI" do
- ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master')
+ pipeline = project.ensure_pipeline(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)
+ expect(json_response['status']).to eq(pipeline.status)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 37ab9cc8cfe..bb926172593 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -249,7 +249,6 @@ describe API::API, api: true do
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
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4b0111df149..1356f87b0e9 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -138,7 +138,6 @@ describe API::API, api: true do
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
@@ -388,7 +387,7 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
- let(:ci_commit) { create(:ci_commit_without_jobs) }
+ let(:pipeline) { create(:ci_pipeline_without_jobs) }
it "should return merge_request in case of success" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
@@ -420,6 +419,15 @@ describe API::API, api: true do
expect(json_response['message']).to eq('405 Method Not Allowed')
end
+ it 'returns 405 if the build failed for a merge request that requires success' do
+ allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false)
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+
+ expect(response.status).to eq(405)
+ expect(json_response['message']).to eq('405 Method Not Allowed')
+ end
+
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
@@ -428,9 +436,22 @@ describe API::API, api: true do
expect(json_response['message']).to eq('401 Unauthorized')
end
+ it "returns 409 if the SHA parameter doesn't match" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha.succ
+
+ expect(response.status).to eq(409)
+ expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
+ end
+
+ it "succeeds if the SHA parameter matches" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.source_sha
+
+ expect(response.status).to eq(200)
+ end
+
it "enables merge when build succeeds if the ci is active" do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:active?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+ allow(pipeline).to receive(:active?).and_return(true)
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 0510b77a39b..fdd4ec6d761 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -23,7 +23,7 @@ describe API::API do
end
before do
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
context 'Handles errors' do
@@ -44,13 +44,13 @@ describe API::API do
end
context 'Have a commit' do
- let(:commit) { project.ci_commits.last }
+ let(:pipeline) { project.pipelines.last }
it 'should create builds' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
expect(response.status).to eq(201)
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ pipeline.builds.reload
+ expect(pipeline.builds.size).to eq(2)
end
it 'should return bad request with no builds created if there\'s no commit for that ref' do
@@ -79,8 +79,8 @@ describe API::API do
it 'create trigger request with variables' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response.status).to eq(201)
- commit.builds.reload
- expect(commit.builds.first.trigger_request.variables).to eq(variables)
+ pipeline.builds.reload
+ expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
end
end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index e5124ea5ea7..e8508f8f950 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -7,7 +7,7 @@ describe Ci::API::API do
let(:project) { FactoryGirl.create(:empty_project) }
before do
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
describe "Builds API for runners" do
@@ -20,9 +20,9 @@ describe Ci::API::API do
describe "POST /builds/register" do
it "should start a build" do
- commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
- commit.create_builds(nil)
- build = commit.builds.first
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
+ pipeline.create_builds(nil)
+ build = pipeline.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -38,8 +38,8 @@ describe Ci::API::API do
end
it "should return 404 error if no builds for specific runner" do
- commit = FactoryGirl.create(:ci_commit, project: shared_project)
- FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
+ pipeline = FactoryGirl.create(:ci_pipeline, project: shared_project)
+ FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending')
post ci_api("/builds/register"), token: runner.token
@@ -47,8 +47,8 @@ describe Ci::API::API do
end
it "should return 404 error if no builds for shared runner" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project)
+ FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending')
post ci_api("/builds/register"), token: shared_runner.token
@@ -56,8 +56,8 @@ describe Ci::API::API do
end
it "returns options" do
- commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
- commit.create_builds(nil)
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
+ pipeline.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, ref: 'master')
- commit.create_builds(nil)
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
+ pipeline.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, ref: 'master')
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
- trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
- commit.create_builds(nil, trigger_request)
+ trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger)
+ pipeline.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,9 +103,9 @@ describe Ci::API::API do
end
it "returns dependent builds" do
- commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master')
- commit.create_builds(nil, nil)
- commit.builds.where(stage: 'test').each(&:success)
+ pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
+ pipeline.create_builds(nil, nil)
+ pipeline.builds.where(stage: 'test').each(&:success)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -131,8 +131,8 @@ describe Ci::API::API do
context 'when build has no tags' do
before do
- commit = create(:ci_commit, project: project)
- create(:ci_build, commit: commit, tags: [])
+ pipeline = create(:ci_pipeline, project: project)
+ create(:ci_build, pipeline: pipeline, tags: [])
end
context 'when runner is allowed to pick untagged builds' do
@@ -163,8 +163,8 @@ describe Ci::API::API do
end
describe "PUT /builds/:id" do
- let(:commit) {create(:ci_commit, project: project)}
- let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) }
+ let(:pipeline) {create(:ci_pipeline, project: project)}
+ let(:build) { create(:ci_build, :trace, pipeline: pipeline, runner_id: runner.id) }
before do
build.run!
@@ -237,8 +237,8 @@ describe Ci::API::API do
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') }
- let(:commit) { create(:ci_commit, project: project) }
- let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index 0ef03f9371b..72f6a3c981d 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -15,7 +15,7 @@ describe Ci::API::API do
end
before do
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
context 'Handles errors' do
@@ -36,13 +36,13 @@ describe Ci::API::API do
end
context 'Have a commit' do
- let(:commit) { project.ci_commits.last }
+ let(:pipeline) { project.pipelines.last }
it 'should create builds' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options
expect(response.status).to eq(201)
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ pipeline.builds.reload
+ expect(pipeline.builds.size).to eq(2)
end
it 'should return bad request with no builds created if there\'s no commit for that ref' do
@@ -71,8 +71,8 @@ describe Ci::API::API do
it 'create trigger request with variables' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables)
expect(response.status).to eq(201)
- commit.builds.reload
- expect(commit.builds.first.trigger_request.variables).to eq(variables)
+ pipeline.builds.reload
+ expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
end
end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
new file mode 100644
index 00000000000..c44a4a7a1fc
--- /dev/null
+++ b/spec/requests/git_http_spec.rb
@@ -0,0 +1,395 @@
+require "spec_helper"
+
+describe 'Git HTTP requests', lib: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, path: 'project.git-project') }
+
+ it "gives WWW-Authenticate hints" do
+ clone_get('doesnt/exist.git')
+
+ expect(response.header['WWW-Authenticate']).to start_with('Basic ')
+ end
+
+ context "when the project doesn't exist" do
+ context "when no authentication is provided" do
+ it "responds with status 401 (no project existence information leak)" do
+ download('doesnt/exist.git') do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ it "responds with status 401" do
+ download('doesnt/exist.git', user: user.username, password: "nope") do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ context "when authentication succeeds" do
+ it "responds with status 404" do
+ download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+
+ context "when the Wiki for a project exists" do
+ it "responds with the right project" do
+ wiki = ProjectWiki.new(project)
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+
+ download("/#{wiki.repository.path_with_namespace}.git") do |response|
+ json_body = ActiveSupport::JSON.decode(response.body)
+
+ expect(response.status).to eq(200)
+ expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
+ end
+ end
+ end
+
+ context "when the project exists" do
+ let(:path) { "#{project.path_with_namespace}.git" }
+
+ context "when the project is public" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ it "downloads get status 200" do
+ download(path, {}) do |response|
+ expect(response.status).to eq(200)
+ end
+ end
+
+ it "uploads get status 401" do
+ upload(path, {}) do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context "with correct credentials" do
+ let(:env) { { user: user.username, password: user.password } }
+
+ it "uploads get status 200 (because Git hooks do the real check)" do
+ upload(path, env) do |response|
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'but git-receive-pack is disabled' do
+ it "responds with status 404" do
+ allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
+
+ upload(path, env) do |response|
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+
+ context 'but git-upload-pack is disabled' do
+ it "responds with status 404" do
+ allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
+
+ download(path, {}) do |response|
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+
+ context "when the project is private" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ context "when no authentication is provided" do
+ it "responds with status 401 to downloads" do
+ download(path, {}) do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+
+ it "responds with status 401 to uploads" do
+ upload(path, {}) do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ context "when username and password are provided" do
+ let(:env) { { user: user.username, password: 'nope' } }
+
+ context "when authentication fails" do
+ it "responds with status 401" do
+ download(path, env) do |response|
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context "when the user is IP banned" do
+ it "responds with status 401" do
+ expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
+
+ clone_get(path, env)
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ context "when authentication succeeds" do
+ let(:env) { { user: user.username, password: user.password } }
+
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
+
+ context "when the user is blocked" do
+ it "responds with status 404" do
+ user.block
+ project.team << [user, :master]
+
+ download(path, env) do |response|
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "downloads get status 200" do
+ expect(Rack::Attack::Allow2Ban).to receive(:reset)
+
+ clone_get(path, env)
+
+ expect(response.status).to eq(200)
+ end
+
+ it "uploads get status 200" do
+ upload(path, env) do |response|
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when an oauth token is provided" do
+ before do
+ application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
+ @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
+ end
+
+ it "downloads get status 200" do
+ clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+
+ expect(response.status).to eq(200)
+ end
+
+ it "uploads get status 401 (no project existence information leak)" do
+ push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context "when blank password attempts follow a valid login" do
+ def attempt_login(include_password)
+ password = include_password ? user.password : ""
+ clone_get path, user: user.username, password: password
+ response.status
+ end
+
+ it "repeated attempts followed by successful attempt" do
+ options = Gitlab.config.rack_attack.git_basic_auth
+ maxretry = options[:maxretry] - 1
+ ip = '1.2.3.4'
+
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ Rack::Attack::Allow2Ban.reset(ip, options)
+
+ maxretry.times.each do
+ expect(attempt_login(false)).to eq(401)
+ end
+
+ expect(attempt_login(true)).to eq(200)
+ expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
+
+ maxretry.times.each do
+ expect(attempt_login(false)).to eq(401)
+ end
+
+ Rack::Attack::Allow2Ban.reset(ip, options)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ it "downloads get status 404" do
+ download(path, user: user.username, password: user.password) do |response|
+ expect(response.status).to eq(404)
+ end
+ end
+
+ it "uploads get status 200 (because Git hooks do the real check)" do
+ upload(path, user: user.username, password: user.password) do |response|
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+ end
+ end
+
+ context "when a gitlab ci token is provided" do
+ let(:token) { 123 }
+ let(:project) { FactoryGirl.create :empty_project }
+
+ before do
+ project.update_attributes(runners_token: token, builds_enabled: true)
+ end
+
+ it "downloads get status 200" do
+ clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+
+ expect(response.status).to eq(200)
+ end
+
+ it "uploads get status 401 (no project existence information leak)" do
+ push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+ end
+
+ context "when the project path doesn't end in .git" do
+ context "GET info/refs" do
+ let(:path) { "/#{project.path_with_namespace}/info/refs" }
+
+ context "when no params are added" do
+ before { get path }
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
+ end
+ end
+
+ context "when the upload-pack service is requested" do
+ let(:params) { { service: 'git-upload-pack' } }
+ before { get path, params }
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
+ end
+
+ context "when the receive-pack service is requested" do
+ let(:params) { { service: 'git-receive-pack' } }
+ before { get path, params }
+
+ it "redirects to the .git suffix version" do
+ expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
+ end
+ end
+
+ context "when the params are anything else" do
+ let(:params) { { service: 'git-implode-pack' } }
+ before { get path, params }
+
+ it "redirects to the sign-in page" do
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context "POST git-upload-pack" do
+ it "fails to find a route" do
+ expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
+ end
+
+ context "POST git-receive-pack" do
+ it "failes to find a route" do
+ expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
+ end
+ end
+ end
+
+ context "retrieving an info/refs file" do
+ before { project.update_attribute(:visibility_level, Project::PUBLIC) }
+
+ context "when the file exists" do
+ before do
+ # Provide a dummy file in its place
+ allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
+ allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do
+ Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore')
+ end
+
+ get "/#{project.path_with_namespace}/blob/master/info/refs"
+ end
+
+ it "returns the file" do
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when the file exists" do
+ before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
+
+ it "returns not found" do
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
+ def clone_get(project, options={})
+ get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password))
+ end
+
+ def clone_post(project, options={})
+ post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password))
+ end
+
+ def push_get(project, options={})
+ get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password))
+ end
+
+ def push_post(project, options={})
+ post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password))
+ end
+
+ def download(project, user: nil, password: nil)
+ args = [project, { user: user, password: password }]
+
+ clone_get(*args)
+ yield response
+
+ clone_post(*args)
+ yield response
+ end
+
+ def upload(project, user: nil, password: nil)
+ args = [project, { user: user, password: password }]
+
+ push_get(*args)
+ yield response
+
+ push_post(*args)
+ yield response
+ end
+
+ def auth_env(user, password)
+ if user && password
+ { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
+ else
+ {}
+ end
+ end
+end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index d006ff195cf..c995993a853 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -44,7 +44,7 @@ describe JwtController do
let(:user) { create(:user) }
let(:headers) { { authorization: credentials('user', 'password') } }
- before { expect_any_instance_of(Gitlab::Auth).to receive(:find).with('user', 'password').and_return(user) }
+ before { expect(Gitlab::Auth).to receive(:find_in_gitlab_or_ldap).with('user', 'password').and_return(user) }
subject! { get '/jwt/auth', parameters, headers }
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
index ecc3a88a262..984b78487d4 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, ref: 'master') }
+ let(:pipeline) { create(:ci_pipeline, ref: 'master') }
let(:user) { create(:user) }
describe '#execute' do
@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
#
subject do
- described_class.new(commit).execute(commit, nil, user, status)
+ described_class.new(pipeline).execute('test', nil, user, status)
end
context 'next builds available' do
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index dbdc5370bd8..ae4b7aca820 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -6,7 +6,7 @@ describe Ci::CreateTriggerRequestService, services: true do
let(:trigger) { create(:ci_trigger, project: project) }
before do
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
describe :execute do
@@ -27,8 +27,8 @@ describe Ci::CreateTriggerRequestService, services: true do
subject { service.execute(project, trigger, 'master') }
before do
- stub_ci_commit_yaml_file('{}')
- FactoryGirl.create :ci_commit, project: project
+ stub_ci_pipeline_yaml_file('{}')
+ FactoryGirl.create :ci_pipeline, project: project
end
it { expect(subject).to be_nil }
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 4cc4b3870d1..476a888e394 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,8 +5,8 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
- let(:commit) { project.ensure_ci_commit(commit_sha, 'master') }
- let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
+ let(:commit) { project.ensure_pipeline(commit_sha, 'master') }
+ let(:build) { FactoryGirl.create(:ci_build, pipeline: commit) }
describe :execute do
before { build }
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index e81f9e757ac..d91fc574299 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -4,8 +4,8 @@ module Ci
describe RegisterBuildService, services: true do
let!(:service) { RegisterBuildService.new }
let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
- let!(:commit) { FactoryGirl.create :ci_commit, project: project }
- let!(:pending_build) { FactoryGirl.create :ci_build, commit: commit }
+ let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
+ let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
index 9ae8f31b372..a5b4d9f05de 100644
--- a/spec/services/create_commit_builds_service_spec.rb
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -6,12 +6,12 @@ describe CreateCommitBuildsService, services: true do
let(:user) { nil }
before do
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
describe :execute do
context 'valid params' do
- let(:commit) do
+ let(:pipeline) do
service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
@@ -20,11 +20,11 @@ describe CreateCommitBuildsService, services: true do
)
end
- it { expect(commit).to be_kind_of(Ci::Commit) }
- it { expect(commit).to be_valid }
- it { expect(commit).to be_persisted }
- it { expect(commit).to eq(project.ci_commits.last) }
- it { expect(commit.builds.first).to be_kind_of(Ci::Build) }
+ it { expect(pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(pipeline).to be_valid }
+ it { expect(pipeline).to be_persisted }
+ it { expect(pipeline).to eq(project.pipelines.last) }
+ it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
end
context "skip tag if there is no build for it" do
@@ -40,7 +40,7 @@ describe CreateCommitBuildsService, services: true do
it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
- stub_ci_commit_yaml_file(config)
+ stub_ci_pipeline_yaml_file(config)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
@@ -52,8 +52,8 @@ describe CreateCommitBuildsService, services: true do
end
end
- it 'skips creating ci_commit for refs without .gitlab-ci.yml' do
- stub_ci_commit_yaml_file(nil)
+ it 'skips creating pipeline for refs without .gitlab-ci.yml' do
+ stub_ci_pipeline_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
@@ -61,115 +61,115 @@ describe CreateCommitBuildsService, services: true do
commits: [{ message: 'Message' }]
)
expect(result).to be_falsey
- expect(Ci::Commit.count).to eq(0)
+ expect(Ci::Pipeline.count).to eq(0)
end
it 'fails commits if yaml is invalid' do
message = 'message'
- allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
- stub_ci_commit_yaml_file('invalid: file: file')
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
+ stub_ci_pipeline_yaml_file('invalid: file: file')
commits = [{ message: message }]
- commit = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(commit).to be_persisted
- expect(commit.builds.any?).to be false
- expect(commit.status).to eq('failed')
- expect(commit.yaml_errors).not_to be_nil
+ pipeline = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq('failed')
+ expect(pipeline.yaml_errors).not_to be_nil
end
describe :ci_skip? do
let(:message) { "some message[ci skip]" }
before do
- allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
end
it "skips builds creation if there is [ci skip] tag in commit message" do
commits = [{ message: message }]
- commit = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(commit).to be_persisted
- expect(commit.builds.any?).to be false
- expect(commit.status).to eq("skipped")
+ pipeline = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
- allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
commits = [{ message: "some message" }]
- commit = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(commit).to be_persisted
- expect(commit.builds.first.name).to eq("staging")
+ pipeline = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.first.name).to eq("staging")
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
- stub_ci_commit_yaml_file('invalid: file: fiile')
+ stub_ci_pipeline_yaml_file('invalid: file: fiile')
commits = [{ message: message }]
- commit = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(commit).to be_persisted
- expect(commit.builds.any?).to be false
- expect(commit.status).to eq("skipped")
- expect(commit.yaml_errors).to be_nil
+ pipeline = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ expect(pipeline.yaml_errors).to be_nil
end
end
it "skips build creation if there are already builds" do
- allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { gitlab_ci_yaml }
commits = [{ message: "message" }]
- commit = service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(commit).to be_persisted
- expect(commit.builds.count(:all)).to eq(2)
+ pipeline = service.execute(project, user,
+ ref: 'refs/heads/master',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.count(:all)).to eq(2)
- commit = service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(commit).to be_persisted
- expect(commit.builds.count(:all)).to eq(2)
+ pipeline = service.execute(project, user,
+ ref: 'refs/heads/master',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.count(:all)).to eq(2)
end
it "creates commit with failed status if yaml is invalid" do
- stub_ci_commit_yaml_file('invalid: file')
+ stub_ci_pipeline_yaml_file('invalid: file')
commits = [{ message: "some message" }]
- commit = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
+ pipeline = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
- expect(commit).to be_persisted
- expect(commit.status).to eq("failed")
- expect(commit.builds.any?).to be false
+ expect(pipeline).to be_persisted
+ expect(pipeline.status).to eq("failed")
+ expect(pipeline.builds.any?).to be false
end
end
end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 96f050bbd9b..4a689e64dc5 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -1,114 +1,265 @@
require 'spec_helper'
describe Issues::BulkUpdateService, services: true do
- let(:issue) { create(:issue, project: @project) }
-
- before do
- @user = create :user
- opts = {
- name: "GitLab",
- namespace: @user.namespace
- }
- @project = Projects::CreateService.new(@user, opts).execute
- end
+ let(:user) { create(:user) }
+ let(:project) { Projects::CreateService.new(user, namespace: user.namespace, name: 'test').execute }
- describe :close_issue do
+ let!(:result) { Issues::BulkUpdateService.new(project, user, params).execute }
- before do
- @issues = create_list(:issue, 5, project: @project)
- @params = {
+ describe :close_issue do
+ let(:issues) { create_list(:issue, 5, project: project) }
+ let(:params) do
+ {
state_event: 'close',
- issues_ids: @issues.map(&:id)
+ issues_ids: issues.map(&:id).join(',')
}
end
- it do
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ it 'succeeds and returns the correct number of issues updated' do
expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(@issues.count)
-
- expect(@project.issues.opened).to be_empty
- expect(@project.issues.closed).not_to be_empty
+ expect(result[:count]).to eq(issues.count)
end
+ it 'closes all the issues passed' do
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ end
end
describe :reopen_issues do
- before do
- @issues = create_list(:closed_issue, 5, project: @project)
- @params = {
+ let(:issues) { create_list(:closed_issue, 5, project: project) }
+ let(:params) do
+ {
state_event: 'reopen',
- issues_ids: @issues.map(&:id)
+ issues_ids: issues.map(&:id).join(',')
}
end
- it do
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ it 'succeeds and returns the correct number of issues updated' do
expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(@issues.count)
-
- expect(@project.issues.closed).to be_empty
- expect(@project.issues.opened).not_to be_empty
+ expect(result[:count]).to eq(issues.count)
end
+ it 'reopens all the issues passed' do
+ expect(project.issues.closed).to be_empty
+ expect(project.issues.opened).not_to be_empty
+ end
end
- describe :update_assignee do
+ describe 'updating assignee' do
+ let(:issue) do
+ create(:issue, project: project) { |issue| issue.update_attributes(assignee: user) }
+ end
- before do
- @new_assignee = create :user
- @params = {
- issues_ids: [issue.id],
- assignee_id: @new_assignee.id
+ let(:params) do
+ {
+ assignee_id: assignee_id,
+ issues_ids: issue.id.to_s
}
end
- it do
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ context 'when the new assignee ID is a valid user' do
+ let(:new_assignee) { create(:user) }
+ let(:assignee_id) { new_assignee.id }
- expect(@project.issues.first.assignee).to eq(@new_assignee)
- end
+ it 'succeeds' do
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
- it 'allows mass-unassigning' do
- @project.issues.first.update_attribute(:assignee, @new_assignee)
- expect(@project.issues.first.assignee).not_to be_nil
+ it 'updates the assignee to the use ID passed' do
+ expect(issue.reload.assignee).to eq(new_assignee)
+ end
+ end
- @params[:assignee_id] = -1
+ context 'when the new assignee ID is -1' do
+ let(:assignee_id) { -1 }
- Issues::BulkUpdateService.new(@project, @user, @params).execute
- expect(@project.issues.first.assignee).to be_nil
+ it 'unassigns the issues' do
+ expect(issue.reload.assignee).to be_nil
+ end
end
- it 'does not unassign when assignee_id is not present' do
- @project.issues.first.update_attribute(:assignee, @new_assignee)
- expect(@project.issues.first.assignee).not_to be_nil
+ context 'when the new assignee ID is not present' do
+ let(:assignee_id) { nil }
- @params[:assignee_id] = ''
-
- Issues::BulkUpdateService.new(@project, @user, @params).execute
- expect(@project.issues.first.assignee).not_to be_nil
+ it 'does not unassign' do
+ expect(issue.reload.assignee).to eq(user)
+ end
end
end
- describe :update_milestone do
+ describe 'updating milestones' do
+ let(:issue) { create(:issue, project: project) }
+ let(:milestone) { create(:milestone, project: project) }
- before do
- @milestone = create(:milestone, project: @project)
- @params = {
- issues_ids: [issue.id],
- milestone_id: @milestone.id
+ let(:params) do
+ {
+ issues_ids: issue.id.to_s,
+ milestone_id: milestone.id
}
end
- it do
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ it 'succeeds' do
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
+ end
- expect(@project.issues.first.milestone).to eq(@milestone)
+ it 'updates the issue milestone' do
+ expect(project.issues.first.milestone).to eq(milestone)
end
end
+ describe 'updating labels' do
+ def create_issue_with_labels(labels)
+ create(:issue, project: project) { |issue| issue.update_attributes(labels: labels) }
+ end
+
+ let(:bug) { create(:label, project: project) }
+ let(:regression) { create(:label, project: project) }
+ let(:merge_requests) { create(:label, project: project) }
+
+ let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
+ let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
+ let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
+ let(:issue_no_labels) { create(:issue, project: project) }
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
+
+ let(:labels) { [] }
+ let(:add_labels) { [] }
+ let(:remove_labels) { [] }
+
+ let(:params) do
+ {
+ label_ids: labels.map(&:id),
+ add_label_ids: add_labels.map(&:id),
+ remove_label_ids: remove_labels.map(&:id),
+ issues_ids: issues.map(&:id).join(',')
+ }
+ end
+
+ context 'when label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_no_labels] }
+ let(:labels) { [bug, regression] }
+
+ it 'updates the labels of all issues passed to the labels passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id)))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+
+ context 'when those label IDs are empty' do
+ let(:labels) { [] }
+
+ it 'updates the issues passed to have no labels' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+ end
+ end
+
+ context 'when add_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug, regression, merge_requests] }
+
+ it 'adds those label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:remove_labels) { [bug, regression, merge_requests] }
+
+ it 'removes those label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when add_label_ids and label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
+ let(:labels) { [merge_requests] }
+ let(:add_labels) { [regression] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_no_labels.label_ids).to be_empty
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:issues) { [issue_no_labels, issue_bug_and_regression] }
+ let(:labels) { [merge_requests] }
+ let(:remove_labels) { [regression] }
+
+ it 'remove the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
+ end
+ end
+
+ context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
+ let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
+ let(:labels) { [regression] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+ end
end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index 95fe6c2400a..93bf0f64963 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -39,6 +39,7 @@ describe Issues::MoveService, services: true do
let!(:milestone2) do
create(:milestone, project_id: new_project.id, title: 'v9.0')
end
+ let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }
let!(:new_issue) { move_service.execute(old_issue, new_project) }
end
@@ -115,6 +116,10 @@ describe Issues::MoveService, services: true do
it 'preserves create time' do
expect(old_issue.created_at).to eq new_issue.created_at
end
+
+ it 'moves the award emoji' do
+ expect(old_issue.award_emoji.first.name).to eq new_issue.reload.award_emoji.first.name
+ end
end
context 'issue with notes' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index be19be17151..dacbcd8fb46 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require 'spec_helper'
describe Issues::UpdateService, services: true do
@@ -273,5 +274,50 @@ describe Issues::UpdateService, services: true do
end
end
end
+
+ context 'updating labels' do
+ let(:label3) { create(:label, project: project) }
+ let(:result) { Issues::UpdateService.new(project, user, params).execute(issue).reload }
+
+ context 'when add_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } }
+
+ it 'ignores the label_ids parameter' do
+ expect(result.label_ids).not_to include(label.id)
+ end
+
+ it 'adds the passed labels' do
+ expect(result.label_ids).to include(label3.id)
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:params) { { label_ids: [], remove_label_ids: [label.id] } }
+
+ before { issue.update_attributes(labels: [label, label3]) }
+
+ it 'ignores the label_ids parameter' do
+ expect(result.label_ids).not_to be_empty
+ end
+
+ it 'removes the passed labels' do
+ expect(result.label_ids).not_to include(label.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:params) { { add_label_ids: [label3.id], remove_label_ids: [label.id] } }
+
+ before { issue.update_attributes(labels: [label]) }
+
+ it 'adds the passed labels' do
+ expect(result.label_ids).to include(label3.id)
+ end
+
+ it 'removes the passed labels' do
+ expect(result.label_ids).not_to include(label.id)
+ end
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
index f70716c9d19..dd656c3bbb7 100644
--- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
+++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb
@@ -6,7 +6,7 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
let(:merge_request) { create(:merge_request) }
let(:project) { create(:project) }
let(:sha) { '1234567890abcdef1234567890abcdef12345678' }
- let(:ci_commit) { create(:ci_commit_with_one_job, ref: merge_request.source_branch, project: project, sha: sha) }
+ let(:pipeline) { create(:ci_pipeline_with_one_job, ref: merge_request.source_branch, project: project, sha: sha) }
let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user, commit_message: 'Awesome message') }
let(:todo_service) { TodoService.new }
@@ -17,13 +17,13 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
end
before do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
allow(service).to receive(:todo_service).and_return(todo_service)
end
describe '#execute' do
context 'commit status with ref' do
- let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) }
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, pipeline: pipeline) }
it 'notifies the todo service' do
expect(todo_service).to receive(:merge_request_build_failed).with(merge_request)
@@ -52,7 +52,7 @@ describe MergeRequests::AddTodoWhenBuildFailsService do
describe '#close' do
context 'commit status with ref' do
- let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, commit: ci_commit) }
+ let(:commit_status) { create(:generic_commit_status, ref: merge_request.source_branch, pipeline: pipeline) }
it 'notifies the todo service' do
expect(todo_service).to receive(:merge_request_build_retried).with(merge_request)
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 0861d74aede..4da8146e3d6 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -10,7 +10,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
source_project: project, target_project: project, state: "opened")
end
- let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) }
+ let(:pipeline) { create(:ci_pipeline_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) }
let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') }
describe "#execute" do
@@ -21,7 +21,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
context 'first time enabling' do
before do
- allow(merge_request).to receive(:ci_commit).and_return(ci_commit)
+ allow(merge_request).to receive(:pipeline).and_return(pipeline)
service.execute(merge_request)
end
@@ -43,9 +43,9 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
before do
- allow(mr_merge_if_green_enabled).to receive(:ci_commit).and_return(ci_commit)
+ allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline)
allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true)
- allow(ci_commit).to receive(:success?).and_return(true)
+ allow(pipeline).to receive(:success?).and_return(true)
end
it 'updates the merge params' do
@@ -62,8 +62,8 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
it "merges all merge requests with merge when build succeeds enabled" do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:success?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+ allow(pipeline).to receive(:success?).and_return(true)
expect(MergeWorker).to receive(:perform_async)
service.trigger(build)
@@ -75,8 +75,8 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
it "merges all merge requests with merge when build succeeds enabled" do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:success?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+ allow(pipeline).to receive(:success?).and_return(true)
allow(old_build).to receive(:sha).and_return('1234abcdef')
expect(MergeWorker).not_to receive(:perform_async)
@@ -99,9 +99,9 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
it 'discovers branches and merges all merge requests when status is success' do
allow(project.repository).to receive(:branch_names_contains).
with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch])
- allow(ci_commit).to receive(:success?).and_return(true)
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:success?).and_return(true)
+ allow(pipeline).to receive(:success?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline)
+ allow(pipeline).to receive(:success?).and_return(true)
expect(MergeWorker).to receive(:perform_async)
service.trigger(commit_status)
@@ -110,17 +110,17 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
context 'properly handles multiple stages' do
let(:ref) { mr_merge_if_green_enabled.source_branch }
- let(:build) { create(:ci_build, commit: ci_commit, ref: ref, name: 'build', stage: 'build') }
- let(:test) { create(:ci_build, commit: ci_commit, ref: ref, name: 'test', stage: 'test') }
+ let(:build) { create(:ci_build, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') }
+ let(:test) { create(:ci_build, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') }
before do
# This behavior of MergeRequest: we instantiate a new object
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_wrap_original do
- Ci::Commit.find(ci_commit.id)
+ allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do
+ Ci::Pipeline.find(pipeline.id)
end
# We create test after the build
- allow(ci_commit).to receive(:create_next_builds).and_wrap_original do
+ allow(pipeline).to receive(:create_next_builds).and_wrap_original do
test
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index ff23f13e1cb..35f576874b8 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -14,7 +14,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue',
noteable_id: issue.id
}
-
+
@note = Notes::CreateService.new(project, user, opts).execute
end
@@ -28,18 +28,16 @@ describe Notes::CreateService, services: true do
project.team << [user, :master]
end
- it "creates emoji note" do
+ it "creates an award emoji" do
opts = {
note: ':smile: ',
noteable_type: 'Issue',
noteable_id: issue.id
}
+ note = Notes::CreateService.new(project, user, opts).execute
- @note = Notes::CreateService.new(project, user, opts).execute
-
- expect(@note).to be_valid
- expect(@note.note).to eq('smile')
- expect(@note.is_award).to be_truthy
+ expect(note).to be_valid
+ expect(note.name).to eq('smile')
end
it "creates regular note if emoji name is invalid" do
@@ -48,12 +46,22 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue',
noteable_id: issue.id
}
+ note = Notes::CreateService.new(project, user, opts).execute
+
+ expect(note).to be_valid
+ expect(note.note).to eq(opts[:note])
+ end
+
+ it "normalizes the emoji name" do
+ opts = {
+ note: ':+1:',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
- @note = Notes::CreateService.new(project, user, opts).execute
+ expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user)
- expect(@note).to be_valid
- expect(@note.note).to eq(opts[:note])
- expect(@note.is_award).to be_falsy
+ Notes::CreateService.new(project, user, opts).execute
end
end
end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 7f2dcdab960..068c9a1219c 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -49,7 +49,7 @@ describe Projects::ImportService, services: true do
result = subject.execute
expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'Failed to import the repository'
+ expect(result[:message]).to eq "Error importing repository #{project.import_url} into #{project.path_with_namespace} - Failed to import the repository"
end
end
@@ -124,7 +124,7 @@ describe Projects::ImportService, services: true do
}
)
- Gitlab.config.omniauth.providers << provider
+ allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 29e0a63d8ce..09f0ee3871d 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -208,7 +208,7 @@ describe SystemNoteService, services: true do
end
describe '.merge_when_build_succeeds' do
- let(:ci_commit) { build(:ci_commit_without_jobs )}
+ let(:pipeline) { build(:ci_pipeline_without_jobs )}
let(:noteable) do
create(:merge_request, source_project: project, target_project: project)
end
@@ -223,7 +223,6 @@ describe SystemNoteService, services: true do
end
describe '.cancel_merge_when_build_succeeds' do
- let(:ci_commit) { build(:ci_commit_without_jobs) }
let(:noteable) do
create(:merge_request, source_project: project, target_project: project)
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 42147736532..489c920f19f 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -18,7 +18,7 @@ describe TodoService, services: true do
end
describe 'Issues' do
- let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) }
+ let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
@@ -101,6 +101,19 @@ 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
+
+ it 'does not create todo when when tasks are marked as completed' do
+ issue.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
+
+ service.update_issue(issue, author)
+
+ should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: assignee, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: member, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
+ end
end
describe '#close_issue' do
@@ -156,7 +169,6 @@ describe TodoService, services: true do
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
- let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
let(:system_note) { create(:system_note, project: project, noteable: issue) }
it 'mark related pending todos to the noteable for the note author as done' do
@@ -169,13 +181,6 @@ describe TodoService, services: true do
expect(second_todo.reload).to be_done
end
- it 'mark related pending todos to the noteable for the award note author as done' do
- service.new_note(award_note, john_doe)
-
- expect(first_todo.reload).to be_done
- expect(second_todo.reload).to be_done
- end
-
it 'does not mark related pending todos it is a system note' do
service.new_note(system_note, john_doe)
@@ -218,7 +223,7 @@ describe TodoService, services: true do
end
describe 'Merge Requests' do
- let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: mentions) }
+ let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") }
let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
describe '#new_merge_request' do
@@ -261,6 +266,19 @@ describe TodoService, services: true do
expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end
+
+ it 'does not create todo when when tasks are marked as completed' do
+ mr_assigned.update(description: "- [x] Task 1\n- [X] Task 2 #{mentions}")
+
+ service.update_merge_request(mr_assigned, author)
+
+ should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: assignee, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
+ end
end
describe '#close_merge_request' do
@@ -306,6 +324,15 @@ describe TodoService, services: true do
end
end
+ describe '#new_award_emoji' do
+ it 'marks related pending todos to the target for the user as done' do
+ todo = create(:todo, user: john_doe, project: project, target: mr_assigned, author: author)
+ service.new_award_emoji(mr_assigned, john_doe)
+
+ expect(todo.reload).to be_done
+ end
+ end
+
describe '#merge_request_build_failed' do
it 'creates a pending todo for the merge request author' do
service.merge_request_build_failed(mr_unassigned)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 576d16e7ea3..b43f38ef202 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -16,6 +16,11 @@ require 'shoulda/matchers'
require 'sidekiq/testing/inline'
require 'rspec/retry'
+if ENV['CI']
+ require 'knapsack'
+ Knapsack::Adapters::RSpecAdapter.bind
+end
+
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb
new file mode 100644
index 00000000000..553fe9f1fbc
--- /dev/null
+++ b/spec/support/fake_u2f_device.rb
@@ -0,0 +1,36 @@
+class FakeU2fDevice
+ def initialize(page)
+ @page = page
+ end
+
+ def respond_to_u2f_registration
+ app_id = @page.evaluate_script('gon.u2f.app_id')
+ challenges = @page.evaluate_script('gon.u2f.challenges')
+
+ json_response = u2f_device(app_id).register_response(challenges[0])
+
+ @page.execute_script("
+ u2f.register = function(appId, registerRequests, signRequests, callback) {
+ callback(#{json_response});
+ };
+ ")
+ end
+
+ def respond_to_u2f_authentication
+ app_id = @page.evaluate_script('gon.u2f.app_id')
+ challenges = @page.evaluate_script('gon.u2f.challenges')
+ json_response = u2f_device(app_id).sign_response(challenges[0])
+
+ @page.execute_script("
+ u2f.sign = function(appId, challenges, signRequests, callback) {
+ callback(#{json_response});
+ };
+ ")
+ end
+
+ private
+
+ def u2f_device(app_id)
+ @u2f_device ||= U2F::FakeU2F.new(app_id)
+ end
+end
diff --git a/spec/controllers/import/import_spec_helper.rb b/spec/support/import_spec_helper.rb
index 9d7648e25a7..6710962f082 100644
--- a/spec/controllers/import/import_spec_helper.rb
+++ b/spec/support/import_spec_helper.rb
@@ -28,6 +28,6 @@ module ImportSpecHelper
app_id: 'asd123',
app_secret: 'asd123'
)
- Gitlab.config.omniauth.providers << provider
+ allow(Gitlab.config.omniauth).to receive(:providers).and_return([provider])
end
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 7fc6d6fcc5e..a79386b5db9 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -32,6 +32,10 @@ class MarkdownFeature
@project_wiki ||= ProjectWiki.new(project, user)
end
+ def project_wiki_page
+ @project_wiki_page ||= build(:wiki_page, wiki: project_wiki)
+ end
+
def issue
@issue ||= create(:issue, project: project)
end
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index f73416a3d0f..93f96cacc00 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -13,12 +13,12 @@ module StubGitlabCalls
allow_any_instance_of(Network).to receive(:projects) { project_hash_array }
end
- def stub_ci_commit_to_return_yaml_file
- stub_ci_commit_yaml_file(gitlab_ci_yaml)
+ def stub_ci_pipeline_to_return_yaml_file
+ stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
end
- def stub_ci_commit_yaml_file(ci_yaml)
- allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
+ def stub_ci_pipeline_yaml_file(ci_yaml)
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
end
def stub_ci_builds_disabled
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 20d3dfb42b3..b8e73682c91 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -52,16 +52,16 @@ describe PostReceive do
context "gitlab-ci.yml" do
subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) }
- context "creates a Ci::Commit for every change" do
- before { stub_ci_commit_to_return_yaml_file }
+ context "creates a Ci::Pipeline for every change" do
+ before { stub_ci_pipeline_to_return_yaml_file }
- it { expect{ subject }.to change{ Ci::Commit.count }.by(2) }
+ it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) }
end
- context "does not create a Ci::Commit" do
- before { stub_ci_commit_yaml_file(nil) }
+ context "does not create a Ci::Pipeline" do
+ before { stub_ci_pipeline_yaml_file(nil) }
- it { expect{ subject }.not_to change{ Ci::Commit.count } }
+ it { expect{ subject }.not_to change{ Ci::Pipeline.count } }
end
end
end
diff --git a/vendor/assets/javascripts/task_list.js.coffee b/vendor/assets/javascripts/task_list.js.coffee
new file mode 100644
index 00000000000..584751af8ea
--- /dev/null
+++ b/vendor/assets/javascripts/task_list.js.coffee
@@ -0,0 +1,258 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2014 GitHub, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# TaskList Behavior
+#
+#= provides tasklist:enabled
+#= provides tasklist:disabled
+#= provides tasklist:change
+#= provides tasklist:changed
+#
+#
+# Enables Task List update behavior.
+#
+# ### Example Markup
+#
+# <div class="js-task-list-container">
+# <ul class="task-list">
+# <li class="task-list-item">
+# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
+# text
+# </li>
+# </ul>
+# <form>
+# <textarea class="js-task-list-field">- [ ] text</textarea>
+# </form>
+# </div>
+#
+# ### Specification
+#
+# TaskLists MUST be contained in a `(div).js-task-list-container`.
+#
+# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
+#
+# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
+# `disabled` by default.
+#
+# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
+# `value` attribute is the source (Markdown) to be udpated. The source MUST
+# follow the syntax guidelines.
+#
+# TaskList updates trigger `tasklist:change` events. If the change is
+# successful, `tasklist:changed` is fired. The change can be canceled.
+#
+# jQuery is required.
+#
+# ### Methods
+#
+# `.taskList('enable')` or `.taskList()`
+#
+# Enables TaskList updates for the container.
+#
+# `.taskList('disable')`
+#
+# Disables TaskList updates for the container.
+#
+## ### Events
+#
+# `tasklist:enabled`
+#
+# Fired when the TaskList is enabled.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-container`
+#
+# `tasklist:disabled`
+#
+# Fired when the TaskList is disabled.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-container`
+#
+# `tasklist:change`
+#
+# Fired before the TaskList item change takes affect.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** Yes
+# * **Target** `.js-task-list-field`
+#
+# `tasklist:changed`
+#
+# Fired once the TaskList item change has taken affect.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-field`
+#
+# ### NOTE
+#
+# Task list checkboxes are rendered as disabled by default because rendered
+# user content is cached without regard for the viewer.
+
+incomplete = "[ ]"
+complete = "[x]"
+
+# Escapes the String for regular expression matching.
+escapePattern = (str) ->
+ str.
+ replace(/([\[\]])/g, "\\$1"). # escape square brackets
+ replace(/\s/, "\\s"). # match all white space
+ replace("x", "[xX]") # match all cases
+
+incompletePattern = ///
+ #{escapePattern(incomplete)}
+///
+completePattern = ///
+ #{escapePattern(complete)}
+///
+
+# Pattern used to identify all task list items.
+# Useful when you need iterate over all items.
+itemPattern = ///
+ ^
+ (?: # prefix, consisting of
+ \s* # optional leading whitespace
+ (?:>\s*)* # zero or more blockquotes
+ (?:[-+*]|(?:\d+\.)) # list item indicator
+ )
+ \s* # optional whitespace prefix
+ ( # checkbox
+ #{escapePattern(complete)}|
+ #{escapePattern(incomplete)}
+ )
+ \s+ # is followed by whitespace
+ (?!
+ \(.*?\) # is not part of a [foo](url) link
+ )
+ (?= # and is followed by zero or more links
+ (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
+ (?:[^\[]|$) # and either a non-link or the end of the string
+ )
+///
+
+# Used to filter out code fences from the source for comparison only.
+# http://rubular.com/r/x5EwZVrloI
+# Modified slightly due to issues with JS
+codeFencesPattern = ///
+ ^`{3} # ```
+ (?:\s*\w+)? # followed by optional language
+ [\S\s] # whitespace
+ .* # code
+ [\S\s] # whitespace
+ ^`{3}$ # ```
+///mg
+
+# Used to filter out potential mismatches (items not in lists).
+# http://rubular.com/r/OInl6CiePy
+itemsInParasPattern = ///
+ ^
+ (
+ #{escapePattern(complete)}|
+ #{escapePattern(incomplete)}
+ )
+ .+
+ $
+///g
+
+# Given the source text, updates the appropriate task list item to match the
+# given checked value.
+#
+# Returns the updated String text.
+updateTaskListItem = (source, itemIndex, checked) ->
+ clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
+ replace(itemsInParasPattern, '').split("\n")
+ index = 0
+ result = for line in source.split("\n")
+ if line in clean && line.match(itemPattern)
+ index += 1
+ if index == itemIndex
+ line =
+ if checked
+ line.replace(incompletePattern, complete)
+ else
+ line.replace(completePattern, incomplete)
+ line
+ result.join("\n")
+
+# Updates the $field value to reflect the state of $item.
+# Triggers the `tasklist:change` event before the value has changed, and fires
+# a `tasklist:changed` event once the value has changed.
+updateTaskList = ($item) ->
+ $container = $item.closest '.js-task-list-container'
+ $field = $container.find '.js-task-list-field'
+ index = 1 + $container.find('.task-list-item-checkbox').index($item)
+ checked = $item.prop 'checked'
+
+ event = $.Event 'tasklist:change'
+ $field.trigger event, [index, checked]
+
+ unless event.isDefaultPrevented()
+ $field.val updateTaskListItem($field.val(), index, checked)
+ $field.trigger 'change'
+ $field.trigger 'tasklist:changed', [index, checked]
+
+# When the task list item checkbox is updated, submit the change
+$(document).on 'change', '.task-list-item-checkbox', ->
+ updateTaskList $(this)
+
+# Enables TaskList item changes.
+enableTaskList = ($container) ->
+ if $container.find('.js-task-list-field').length > 0
+ $container.
+ find('.task-list-item').addClass('enabled').
+ find('.task-list-item-checkbox').attr('disabled', null)
+ $container.addClass('is-task-list-enabled').
+ trigger 'tasklist:enabled'
+
+# Enables a collection of TaskList containers.
+enableTaskLists = ($containers) ->
+ for container in $containers
+ enableTaskList $(container)
+
+# Disable TaskList item changes.
+disableTaskList = ($container) ->
+ $container.
+ find('.task-list-item').removeClass('enabled').
+ find('.task-list-item-checkbox').attr('disabled', 'disabled')
+ $container.removeClass('is-task-list-enabled').
+ trigger 'tasklist:disabled'
+
+# Disables a collection of TaskList containers.
+disableTaskLists = ($containers) ->
+ for container in $containers
+ disableTaskList $(container)
+
+$.fn.taskList = (method) ->
+ $container = $(this).closest('.js-task-list-container')
+
+ methods =
+ enable: enableTaskLists
+ disable: disableTaskLists
+
+ methods[method || 'enable']($container)
diff --git a/vendor/assets/javascripts/u2f.js b/vendor/assets/javascripts/u2f.js
new file mode 100644
index 00000000000..e666b136051
--- /dev/null
+++ b/vendor/assets/javascripts/u2f.js
@@ -0,0 +1,748 @@
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+'use strict';
+
+
+/**
+ * Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * FIDO U2F Javascript API Version
+ * @number
+ */
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+ 'U2F_REGISTER_REQUEST': 'u2f_register_request',
+ 'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+ 'U2F_SIGN_REQUEST': 'u2f_sign_request',
+ 'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+ 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+ 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
+};
+
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+ 'OK': 0,
+ 'OTHER_ERROR': 1,
+ 'BAD_REQUEST': 2,
+ 'CONFIGURATION_UNSUPPORTED': 3,
+ 'DEVICE_INELIGIBLE': 4,
+ 'TIMEOUT': 5
+};
+
+
+/**
+ * A message for registration requests
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * appId: ?string,
+ * timeoutSeconds: ?number,
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fRequest;
+
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ * requestId: ?number
+ * }}
+ */
+u2f.U2fResponse;
+
+
+/**
+ * An error object for responses
+ * @typedef {{
+ * errorCode: u2f.ErrorCodes,
+ * errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ * version: string,
+ * challenge: string,
+ * keyHandle: string,
+ * appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ * keyHandle: string,
+ * signatureData: string,
+ * clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ * version: string,
+ * challenge: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: Transports,
+ * appId: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ * version: string,
+ * keyHandle: string,
+ * transports: ?Transports,
+ * appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ * js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+ if (typeof chrome != 'undefined' && chrome.runtime) {
+ // The actual message here does not matter, but we need to get a reply
+ // for the callback to run. Thus, send an empty signature request
+ // in order to get a failure response.
+ var msg = {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: []
+ };
+ chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+ if (!chrome.runtime.lastError) {
+ // We are on a whitelisted origin and can talk directly
+ // with the extension.
+ u2f.getChromeRuntimePort_(callback);
+ } else {
+ // chrome.runtime was available, but we couldn't message
+ // the extension directly, use iframe
+ u2f.getIframePort_(callback);
+ }
+ });
+ } else if (u2f.isAndroidChrome_()) {
+ u2f.getAuthenticatorPort_(callback);
+ } else if (u2f.isIosChrome_()) {
+ u2f.getIosPort_(callback);
+ } else {
+ // chrome.runtime was not available at all, which is normal
+ // when this origin doesn't have access to any extensions.
+ u2f.getIframePort_(callback);
+ }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+ var userAgent = navigator.userAgent;
+ return userAgent.indexOf('Chrome') != -1 &&
+ userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+ return $.inArray(navigator.platform, ["iPhone", "iPad", "iPod"]) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+ var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+ {'includeTlsChannelId': true});
+ setTimeout(function() {
+ callback(new u2f.WrappedChromeRuntimePort_(port));
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedAuthenticatorPort_());
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedIosPort_());
+ }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+ this.port_ = port;
+};
+
+/**
+ * Format and return a sign request compliant with the JS API version supported by the extension.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatSignRequest_ =
+ function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: challenge,
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: signRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ appId: appId,
+ challenge: challenge,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ };
+
+/**
+ * Format and return a register request compliant with the JS API version supported by the extension..
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatRegisterRequest_ =
+ function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+ if (js_api_version === undefined || js_api_version < 1.1) {
+ // Adapt request to the 1.0 JS API
+ for (var i = 0; i < registerRequests.length; i++) {
+ registerRequests[i].appId = appId;
+ }
+ var signRequests = [];
+ for (var i = 0; i < registeredKeys.length; i++) {
+ signRequests[i] = {
+ version: registeredKeys[i].version,
+ challenge: registerRequests[0],
+ keyHandle: registeredKeys[i].keyHandle,
+ appId: appId
+ };
+ }
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ signRequests: signRequests,
+ registerRequests: registerRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ }
+ // JS 1.1 API
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ appId: appId,
+ registerRequests: registerRequests,
+ registeredKeys: registeredKeys,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ };
+
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+ this.port_.postMessage(message);
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+ function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message' || name == 'onmessage') {
+ this.port_.onMessage.addListener(function(message) {
+ // Emulate a minimal MessageEvent object
+ handler({'data': message});
+ });
+ } else {
+ console.error('WrappedChromeRuntimePort only supports onMessage');
+ }
+ };
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+ this.requestId_ = -1;
+ this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+ var intentUrl =
+ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+ ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+ ';end';
+ document.location = intentUrl;
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+ return "WrappedAuthenticatorPort_";
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message') {
+ var self = this;
+ /* Register a callback to that executes when
+ * chrome injects the response. */
+ window.addEventListener(
+ 'message', self.onRequestUpdate_.bind(self, handler), false);
+ } else {
+ console.error('WrappedAuthenticatorPort only supports message');
+ }
+};
+
+/**
+ * Callback invoked when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+ function(callback, message) {
+ var messageObject = JSON.parse(message.data);
+ var intentUrl = messageObject['intentURL'];
+
+ var errorCode = messageObject['errorCode'];
+ var responseObject = null;
+ if (messageObject.hasOwnProperty('data')) {
+ responseObject = /** @type {Object} */ (
+ JSON.parse(messageObject['data']));
+ }
+
+ callback({'data': responseObject});
+ };
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+ 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+
+/**
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+ var str = JSON.stringify(message);
+ var url = "u2f://auth?" + encodeURI(str);
+ location.replace(url);
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+ return "WrappedIosPort_";
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name !== 'message') {
+ console.error('WrappedIosPort only supports message');
+ }
+};
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+ // Create the iframe
+ var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+ var iframe = document.createElement('iframe');
+ iframe.src = iframeOrigin + '/u2f-comms.html';
+ iframe.setAttribute('style', 'display:none');
+ document.body.appendChild(iframe);
+
+ var channel = new MessageChannel();
+ var ready = function(message) {
+ if (message.data == 'ready') {
+ channel.port1.removeEventListener('message', ready);
+ callback(channel.port1);
+ } else {
+ console.error('First event on iframe port was not "ready"');
+ }
+ };
+ channel.port1.addEventListener('message', ready);
+ channel.port1.start();
+
+ iframe.addEventListener('load', function() {
+ // Deliver the port to the iframe and initialize
+ iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+ });
+};
+
+
+//High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ * |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+ if (u2f.port_) {
+ callback(u2f.port_);
+ } else {
+ if (u2f.waitingForPort_.length == 0) {
+ u2f.getMessagePort(function(port) {
+ u2f.port_ = port;
+ u2f.port_.addEventListener('message',
+ /** @type {function(Event)} */ (u2f.responseHandler_));
+
+ // Careful, here be async callbacks. Maybe.
+ while (u2f.waitingForPort_.length)
+ u2f.waitingForPort_.shift()(u2f.port_);
+ });
+ }
+ u2f.waitingForPort_.push(callback);
+ }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+ var response = message.data;
+ var reqId = response['requestId'];
+ if (!reqId || !u2f.callbackMap_[reqId]) {
+ console.error('Unknown or missing requestId in response.');
+ return;
+ }
+ var cb = u2f.callbackMap_[reqId];
+ delete u2f.callbackMap_[reqId];
+ cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual sign request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+ console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual sign request in the supported API version.
+ u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ if (js_api_version === undefined) {
+ // Send a message to get the extension to JS API version, then send the actual register request.
+ u2f.getApiVersion(
+ function (response) {
+ js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+ console.log("Extension JS API Version: ", js_api_version);
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ });
+ } else {
+ // We know the JS API version. Send the actual register request in the supported API version.
+ u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+ callback, opt_timeoutSeconds);
+ }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = u2f.formatRegisterRequest_(
+ appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ // If we are using Android Google Authenticator or iOS client app,
+ // do not fire an intent to ask which JS API version to use.
+ if (port.getPortType) {
+ var apiVersion;
+ switch (port.getPortType()) {
+ case 'WrappedIosPort_':
+ case 'WrappedAuthenticatorPort_':
+ apiVersion = 1.1;
+ break;
+
+ default:
+ apiVersion = 0;
+ break;
+ }
+ callback({ 'js_api_version': apiVersion });
+ return;
+ }
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var req = {
+ type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+ timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+ requestId: reqId
+ };
+ port.postMessage(req);
+ });
+}; \ No newline at end of file