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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml352
-rw-r--r--.rubocop.yml3
-rw-r--r--CHANGELOG25
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock51
-rwxr-xr-xRakefile2
-rw-r--r--app/assets/javascripts/LabelManager.js.coffee84
-rw-r--r--app/assets/javascripts/awards_handler.coffee272
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee6
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee78
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee8
-rw-r--r--app/assets/javascripts/issuable.js.coffee25
-rw-r--r--app/assets/javascripts/lib/emoji_aliases.js.coffee.erb2
-rw-r--r--app/assets/javascripts/notes.js.coffee20
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee18
-rw-r--r--app/assets/javascripts/subscription.js.coffee5
-rw-r--r--app/assets/stylesheets/framework/blocks.scss5
-rw-r--r--app/assets/stylesheets/framework/buttons.scss15
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss29
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss34
-rw-r--r--app/assets/stylesheets/framework/header.scss35
-rw-r--r--app/assets/stylesheets/framework/lists.scss12
-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/sidebar.scss130
-rw-r--r--app/assets/stylesheets/framework/timeline.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss7
-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.scss4
-rw-r--r--app/assets/stylesheets/pages/confirmation.scss10
-rw-r--r--app/assets/stylesheets/pages/labels.scss138
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss5
-rw-r--r--app/assets/stylesheets/pages/note_form.scss33
-rw-r--r--app/assets/stylesheets/pages/notes.scss58
-rw-r--r--app/assets/stylesheets/pages/projects.scss18
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb11
-rw-r--r--app/controllers/jwt_controller.rb42
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb4
-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/labels_controller.rb29
-rw-r--r--app/controllers/projects/merge_requests_controller.rb35
-rw-r--r--app/controllers/projects/notes_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb1
-rw-r--r--app/finders/issuable_finder.rb8
-rw-r--r--app/helpers/appearances_helper.rb4
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/ci_status_helper.rb6
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb12
-rw-r--r--app/helpers/labels_helper.rb6
-rw-r--r--app/helpers/notifications_helper.rb22
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/ci/build.rb24
-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/issuable.rb47
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/label.rb14
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/note.rb1
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/snippet.rb2
-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/merge_requests/base_service.rb8
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb4
-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.haml2
-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.haml3
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/events/event/_common.html.haml10
-rw-r--r--app/views/groups/group_members/_group_member.html.haml4
-rw-r--r--app/views/groups/show.html.haml3
-rw-r--r--app/views/import/github/status.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml16
-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.haml18
-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/projects/_home_panel.html.haml10
-rw-r--r--app/views/projects/_md_preview.html.haml6
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml61
-rw-r--r--app/views/projects/builds/index.html.haml120
-rw-r--r--app/views/projects/builds/show.html.haml12
-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.haml42
-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/graphs/ci/_overall.haml2
-rw-r--r--app/views/projects/hooks/index.html.haml92
-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.haml3
-rw-r--r--app/views/projects/issues/show.html.haml16
-rw-r--r--app/views/projects/labels/_label.html.haml63
-rw-r--r--app/views/projects/labels/index.html.haml37
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml4
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/index.html.haml1
-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/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.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.haml112
-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.haml4
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tags/index.html.haml44
-rw-r--r--app/views/projects/tree/show.html.haml19
-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/icons/_activity.svg15
-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/_sidebar.html.haml17
-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--config/dependency_decisions.yml183
-rw-r--r--config/initializers/doorkeeper.rb2
-rw-r--r--config/initializers/metrics.rb7
-rw-r--r--config/license_finder.yml2
-rw-r--r--config/routes.rb33
-rw-r--r--db/fixtures/development/14_builds.rb2
-rw-r--r--db/fixtures/production/001_admin.rb12
-rw-r--r--db/migrate/20160314094147_add_priority_to_label.rb6
-rw-r--r--db/migrate/20160603180330_remove_duplicated_notification_settings.rb32
-rw-r--r--db/migrate/20160603182247_add_index_to_notification_settings.rb9
-rw-r--r--db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb5
-rw-r--r--db/schema.rb7
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/licensing.md93
-rw-r--r--doc/development/ui_guide.md2
-rw-r--r--doc/install/installation.md6
-rw-r--r--doc/update/8.8-to-8.9.md162
-rw-r--r--features/project/issues/issues.feature7
-rw-r--r--features/project/merge_requests.feature14
-rw-r--r--features/steps/dashboard/todos.rb2
-rw-r--r--features/steps/project/commits/commits.rb6
-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.rb4
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/wiki.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/support/env.rb5
-rw-r--r--lib/api/builds.rb2
-rw-r--r--lib/api/commit_statuses.rb12
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/session.rb3
-rw-r--r--lib/banzai/filter/external_link_filter.rb1
-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/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/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/comment_formatter.rb5
-rw-r--r--lib/gitlab/github_import/hook_formatter.rb23
-rw-r--r--lib/gitlab/github_import/importer.rb139
-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/o_auth/user.rb17
-rw-r--r--lib/gitlab/saml/user.rb4
-rw-r--r--lib/gitlab/workhorse.rb24
-rw-r--r--lib/tasks/gitlab/setup.rake2
-rwxr-xr-xscripts/merge-reports29
-rwxr-xr-xscripts/prepare_build.sh10
-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/projects/labels_controller_spec.rb53
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb11
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb36
-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/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.rb8
-rw-r--r--spec/features/builds_spec.rb4
-rw-r--r--spec/features/commits_spec.rb56
-rw-r--r--spec/features/issues/filter_by_labels_spec.rb35
-rw-r--r--spec/features/markdown_spec.rb17
-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/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/security/project/public_access_spec.rb4
-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/merge_requests_helper_spec.rb2
-rw-r--r--spec/javascripts/awards_handler_spec.js.coffee202
-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/new_branch_spec.js.coffee2
-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/gitlab/auth_spec.rb56
-rw-r--r--spec/lib/gitlab/backend/grack_auth_spec.rb209
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb8
-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/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/issuable_spec.rb26
-rw-r--r--spec/models/generic_commit_status_spec.rb4
-rw-r--r--spec/models/merge_request_spec.rb14
-rw-r--r--spec/models/note_spec.rb10
-rw-r--r--spec/models/project_spec.rb18
-rw-r--r--spec/models/user_spec.rb88
-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/merge_requests_spec.rb6
-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/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/projects/import_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb3
-rw-r--r--spec/spec_helper.rb5
-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
323 files changed, 6787 insertions, 2770 deletions
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 eb51a04c0ec..678f7db025b 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1088,6 +1088,9 @@ Rails/TimeZone:
Rails/Validation:
Enabled: false
+Rails/UniqBeforePluck:
+ Enabled: false
+
##################### RSpec ##################################
# Check that instances are not being stubbed globally.
diff --git a/CHANGELOG b/CHANGELOG
index fe9b9bec868..17fc4801a6e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,10 +2,15 @@ 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
- 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
- Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies
@@ -14,12 +19,16 @@ v 8.9.0 (unreleased)
- 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
+ - 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
- 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
@@ -30,14 +39,23 @@ v 8.9.0 (unreleased)
- Use downcased path to container repository as this is expected path by Docker
- 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
+ - 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.
+ - 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
v 8.8.4 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
@@ -45,6 +63,11 @@ v 8.8.4 (unreleased)
- Fix todos page throwing errors when you have a project pending deletion
- Reduce number of SQL queries when rendering user references
- Upgrade to jQuery 2
+ - Remove prev/next buttons on issues and merge requests
+ - Import GitHub repositories respecting the API rate limit
+ - Fix importer for GitHub comments on diff
+ - Disable Webhooks before proceeding with the GitHub import
+ - Added descriptions to notification settings dropdown
v 8.8.3
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
@@ -156,6 +179,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
@@ -165,6 +189,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 18270d9598f..f4472214778 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
@@ -531,3 +532,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`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 38ff536fd71..b2660144f2b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,7 +38,7 @@ 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
@@ -86,6 +86,7 @@ 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'
@@ -111,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
@@ -306,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 5f1dbd431e4..dfc15700494 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,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)
@@ -213,6 +228,11 @@ GEM
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
+ fog-azure (0.0.2)
+ azure (~> 0.6)
+ fog-core (~> 1.27)
+ fog-json (~> 1.0)
+ fog-xml (~> 0.1)
fog-core (1.40.0)
builder
excon (~> 0.49)
@@ -358,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)
@@ -366,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)
@@ -381,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)
@@ -392,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)
@@ -465,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)
@@ -540,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)
@@ -569,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)
@@ -618,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)
@@ -728,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)
@@ -789,6 +822,7 @@ GEM
builder
expression_parser
rinku
+ xml-simple (1.1.5)
xpath (2.0.0)
nokogiri (~> 1.3)
@@ -842,6 +876,7 @@ DEPENDENCIES
flay
flog
fog-aws (~> 0.9)
+ fog-azure (~> 0.0)
fog-core (~> 1.40)
fog-google (~> 0.3)
fog-local (~> 0.3)
@@ -874,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)
@@ -918,7 +955,7 @@ DEPENDENCIES
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
- recaptcha
+ recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace
@@ -926,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
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/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 766c653111a..efa8f6cd010 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -2,39 +2,47 @@ class @AwardsHandler
constructor: ->
- @aliases = emojiAliases()
+ @aliases = gl.emojiAliases()
$(document)
.off 'click', '.js-add-award'
- .on 'click', '.js-add-award', (event) =>
- event.stopPropagation()
- event.preventDefault()
+ .on 'click', '.js-add-award', (e) =>
+ e.stopPropagation()
+ e.preventDefault()
- @showEmojiMenu $(event.currentTarget)
+ @showEmojiMenu $(e.currentTarget)
- $('html').on 'click', (event) ->
- unless $(event.target).closest('.emoji-menu').length
+ $('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'
$(document)
.off 'click', '.js-emoji-btn'
- .on 'click', '.js-emoji-btn', @handleClick
-
+ .on 'click', '.js-emoji-btn', (e) =>
+ e.preventDefault()
- handleClick: (e) =>
+ $target = $ e.currentTarget
+ emoji = $target.find('.icon').data 'emoji'
- e.preventDefault()
-
- emoji = $(e.currentTarget).find('.icon').data 'emoji'
- @getVotesBlock().addClass 'js-awards-block'
- @addAward @getAwardUrl(), emoji
+ $target.closest('.js-awards-block').addClass 'current'
+ @addAward @getVotesBlock(), @getAwardUrl(), emoji
showEmojiMenu: ($addBtn) ->
- $menu = $('.emoji-menu')
+ $menu = $ '.emoji-menu'
+
+ if $addBtn.hasClass 'js-note-emoji'
+ $addBtn.parents('.note').find('.js-awards-block').addClass 'current'
+ else
+ $addBtn.closest('.js-awards-block').addClass 'current'
if $menu.length
$holder = $addBtn.closest('.js-award-holder')
@@ -51,7 +59,7 @@ class @AwardsHandler
$('#emoji_search').focus()
else
$addBtn.addClass 'is-loading is-active'
- url = $addBtn.data 'award-menu-url'
+ url = @getAwardMenuUrl()
@createEmojiMenu url, =>
$addBtn.removeClass 'is-loading'
@@ -68,12 +76,13 @@ class @AwardsHandler
createEmojiMenu: (awardMenuUrl, callback) ->
- $.get awardMenuUrl, (response) =>
+ $.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
@@ -91,88 +100,114 @@ class @AwardsHandler
$menu.css(css)
- addAward: (awardUrl, emoji, checkMutuality = yes) ->
+ addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) ->
- emoji = @normilizeEmojiName(emoji)
- @postEmoji awardUrl, emoji, =>
- @addAwardToEmojiBar(emoji, checkMutuality)
+ emoji = @normilizeEmojiName emoji
- $('.js-awards-block-current').removeClass 'js-awards-block-current'
+ @postEmoji awardUrl, emoji, =>
+ @addAwardToEmojiBar votesBlock, emoji, checkMutuality
+ callback?()
$('.emoji-menu').removeClass 'is-visible'
- addAwardToEmojiBar: (emoji, checkForMutuality = yes) ->
+ addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) ->
- @checkMutuality emoji if checkForMutuality
- @addEmojiToFrequentlyUsedList(emoji)
+ @checkMutuality votesBlock, emoji if checkForMutuality
+ @addEmojiToFrequentlyUsedList emoji
- emoji = @normilizeEmojiName(emoji)
- $emojiBtn = @findEmojiIcon(emoji).parent()
+ emoji = @normilizeEmojiName emoji
+ $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
- if $emojiBtn.length > 0
- if @isActive($emojiBtn)
- @decrementCounter($emojiBtn, emoji)
+ if $emojiButton.length > 0
+ if @isActive $emojiButton
+ @decrementCounter $emojiButton, emoji
else
- counter = $emojiBtn.find('.js-counter')
- counter.text(parseInt(counter.text()) + 1)
- $emojiBtn.addClass('active')
- @addMeToUserList(emoji)
+ counter = $emojiButton.find '.js-counter'
+ counter.text parseInt(counter.text()) + 1
+ $emojiButton.addClass 'active'
+ @addMeToUserList votesBlock, emoji
+ @animateEmoji $emojiButton
else
- @createEmoji(emoji)
+ votesBlock.removeClass 'hidden'
+ @createEmoji votesBlock, emoji
- getVotesBlock: -> return $ '.awards.js-awards-block'
+ getVotesBlock: ->
+ currentBlock = $ '.js-awards-block.current'
+ return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
- getAwardUrl: -> @getVotesBlock().data 'award-url'
+ getAwardUrl: -> return @getVotesBlock().data 'award-url'
- checkMutuality: (emoji) ->
+
+ checkMutuality: (votesBlock, emoji) ->
awardUrl = @getAwardUrl()
if emoji in [ 'thumbsup', 'thumbsdown' ]
- mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
+ 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, no, ->
+ $emojiButton.removeClass 'is-loading'
- isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
- @addAward awardUrl, mutualVote, no if isAlreadyVoted
+ showEmojiLoader: ($emojiButton) ->
- isActive: ($emojiBtn) -> $emojiBtn.hasClass 'active'
+ $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>'
- decrementCounter: ($emojiBtn, emoji) ->
- isntNoteBody = $emojiBtn.closest('.note-body').length is 0
- counter = $('.js-counter', $emojiBtn)
- counterNumber = parseInt(counter.text())
+ $emojiButton.addClass 'is-loading'
- if !isntNoteBody
- # If this is a note body, we just hide the award emoji row like the initial state
- $emojiBtn.closest('.js-awards-block').addClass 'hidden'
+
+ 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($emojiBtn, emoji)
- else if (emoji == 'thumbsup' || emoji == 'thumbsdown') && isntNoteBody
- $emojiBtn.tooltip('destroy')
- counter.text('0')
- @removeMeFromUserList($emojiBtn, emoji)
+ 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
- $emojiBtn.tooltip('destroy')
- $emojiBtn.remove()
+ @removeEmoji $emojiButton
+
+ $emojiButton.removeClass 'active'
- $emojiBtn.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')
+ return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
- removeMeFromUserList: ($emojiBtn, emoji) ->
+ removeMeFromUserList: ($emojiButton, emoji) ->
- awardBlock = $emojiBtn
+ awardBlock = $emojiButton
originalTitle = @getAwardTooltip awardBlock
authors = originalTitle.split ', '
@@ -183,117 +218,134 @@ class @AwardsHandler
awardBlock
.closest '.js-emoji-btn'
.removeData 'original-title'
- .removeData 'title'
.attr 'data-original-title', newAuthors
- .attr 'data-title', newAuthors
- @resetTooltip(awardBlock)
+ @resetTooltip awardBlock
- addMeToUserList: (emoji) ->
+ addMeToUserList: (votesBlock, emoji) ->
- awardBlock = @findEmojiIcon(emoji).parent()
+ awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
origTitle = @getAwardTooltip awardBlock
users = []
if origTitle
- users = origTitle.trim().split(', ')
+ users = origTitle.trim().split ', '
- users.push('me')
- awardBlock.attr('title', users.join(', '))
+ users.push 'me'
+ awardBlock.attr 'title', users.join ', '
- @resetTooltip(awardBlock)
+ @resetTooltip awardBlock
resetTooltip: (award) ->
- award.tooltip('destroy')
+
+ award.tooltip 'destroy'
# 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
- setTimeout (->
- award.tooltip()
- ), 200
+ cb = -> award.tooltip()
+ setTimeout cb, 200
- createEmoji_: (emoji) ->
+ createEmoji_: (votesBlock, emoji) ->
emojiCssClass = @resolveNameToCssClass emoji
-
- buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
+ 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>"
- emoji_node = $(buttonHtml)
- .insertBefore '.js-awards-block .js-award-holder:not(.js-award-action-btn)'
+ $emojiButton = $ buttonHtml
+ $emojiButton
+ .insertBefore votesBlock.find '.js-award-holder'
.find '.emoji-icon'
.data 'emoji', emoji
+ @animateEmoji $emojiButton
$('.award-control').tooltip()
+ votesBlock.removeClass 'current'
+
+
+ animateEmoji: ($emoji) ->
- $currentBlock = $ '.js-awards-block'
+ className = 'pulse animated'
- if $currentBlock.is '.hidden'
- $currentBlock.removeClass 'hidden'
+ $emoji.addClass className
+ setTimeout (-> $emoji.removeClass className), 321
- createEmoji: (emoji) ->
+ createEmoji: (votesBlock, emoji) ->
- return @createEmoji_ emoji if $('.emoji-menu').length
+ if $('.emoji-menu').length
+ return @createEmoji_ votesBlock, emoji
- awardMenuUrl = gl.awardMenuUrl or '/emojis'
- @createEmojiMenu awardMenuUrl, => @createEmoji emoji
+ @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
+
+
+ getAwardMenuUrl: -> return gl.awardMenuUrl
resolveNameToCssClass: (emoji) ->
- emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+ emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
- if emoji_icon.length > 0
- unicodeName = emoji_icon.data('unicode-name')
+ if emojiIcon.length > 0
+ unicodeName = emojiIcon.data 'unicode-name'
else
# Find by alias
- unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
return "emoji-#{unicodeName}"
postEmoji: (awardUrl, emoji, callback) ->
+
$.post awardUrl, { name: emoji }, (data) ->
- if data.ok
- callback.call()
+ callback() if data.ok
+
+
+ findEmojiIcon: (votesBlock, emoji) ->
+
+ return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
- findEmojiIcon: (emoji) ->
- $(".js-awards-block.awards > .js-emoji-btn [data-emoji='#{emoji}']")
scrollToAwards: ->
- $('body, html').animate({
- scrollTop: $('.awards').offset().top - 80
- }, 200)
- normilizeEmojiName: (emoji) ->
- @aliases[emoji] || emoji
+ options = scrollTop: $('.awards').offset().top - 110
+ $('body, html').animate options, 200
+
+
+ normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
+
addEmojiToFrequentlyUsedList: (emoji) ->
- frequently_used_emojis = @getFrequentlyUsedEmojis()
- frequently_used_emojis.push(emoji)
- $.cookie('frequently_used_emojis', frequently_used_emojis.join(','), { expires: 365 })
+
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
+ frequentlyUsedEmojis.push emoji
+ $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
+
getFrequentlyUsedEmojis: ->
- frequently_used_emojis = ($.cookie('frequently_used_emojis') || '').split(',')
- _.compact(_.uniq(frequently_used_emojis))
+
+ frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
+ return _.compact _.uniq frequentlyUsedEmojis
+
renderFrequentlyUsedBlock: ->
- if $.cookie('frequently_used_emojis')
- frequently_used_emojis = @getFrequentlyUsedEmojis()
+
+ if $.cookie 'frequently_used_emojis'
+ frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>")
- for emoji in frequently_used_emojis
+ for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
+
setupSearch: ->
+
$('input.emoji-search').on 'keyup', (ev) =>
term = $(ev.target).val()
@@ -310,5 +362,7 @@ class @AwardsHandler
else
$('.emoji-menu-content').children().show()
- searchEmojis: (term)->
+
+ searchEmojis: (term) ->
+
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index bae67a2ebaf..5d6ac6e757e 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -23,7 +23,7 @@ class Dispatcher
new Issue()
shortcut_handler = new ShortcutsIssuable()
new ZenMode()
- window.awardsHandler = new AwardsHandler()
+ gl.awardsHandler = new AwardsHandler()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
when 'dashboard:todos:index'
@@ -54,7 +54,7 @@ class Dispatcher
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
- window.awardsHandler = new AwardsHandler()
+ gl.awardsHandler = new AwardsHandler()
when "projects:merge_requests:diffs"
new Diff()
new ZenMode()
@@ -100,6 +100,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/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 41dba342107..b13a431a52f 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -22,6 +22,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 value.indexOf('undefined')
+ @at
+ else
+ value
+
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
@@ -53,18 +71,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 +113,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 +136,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 +156,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}"
@@ -128,3 +192,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 7c7334e9e40..b49bd4565a7 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -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
@@ -453,7 +454,7 @@ 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)
@@ -480,7 +481,7 @@ 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
@addInput(fieldName, value)
@@ -579,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/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/lib/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
index 97be65116e2..80f9936b9c2 100644
--- a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
+++ b/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
@@ -1,2 +1,2 @@
-window.emojiAliases = ->
+gl.emojiAliases = ->
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7c3d57fc194..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.name)
- 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/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/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..625200cbcad 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -142,15 +142,26 @@
}
&.btn-grouped {
- margin-right: 7px;
+ margin-right: $btn-side-margin;
float: left;
+
+ &.inline {
+ float: none;
+ }
+
&:last-child {
margin-right: 0;
}
+
+ &.btn-sm {
+ margin-right: $btn-sm-side-margin;
+ }
+
&.btn-xs {
- margin-right: 3px;
+ margin-right: $btn-xs-side-margin;
}
}
+
&.disabled {
pointer-events: auto !important;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 28634d0c59f..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%;
}
@@ -236,8 +245,7 @@
&::before {
position: absolute;
left: 5px;
- top: 50%;
- margin-top: -7px;
+ top: 8px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
@@ -532,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/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..c46d6b14782 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) {
- margin-left: $sidebar_width;
- }
-
- @media (max-width: $screen-xs-min) {
- margin-left: 0;
+ .header-content {
+ padding-left: $sidebar_width;
+ transition-duration: .3s;
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index b17c8bcbb1e..96e7aa4fb15 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -141,6 +141,18 @@ ul.content-list {
padding: 10px 14px;
}
}
+
+ // 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/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/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 67f491b6d9c..94985413746 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) {
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/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f253da814bc..d8ea07559ab 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;
@@ -79,6 +79,9 @@ $provider-btn-not-active-color: #4688f1;
$link-underline-blue: #4a8bee;
$layout-link-gray: #7e7c7c;
$todo-alert-blue: #428bca;
+$btn-side-margin: 7px;
+$btn-sm-side-margin: 5px;
+$btn-xs-side-margin: 5px;
/*
* Color schema
@@ -121,7 +124,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;
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 07d40f40556..05d1ee5b998 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -95,6 +95,7 @@
.award-control {
margin-right: 5px;
+ margin-bottom: 5px;
padding-left: 5px;
padding-right: 5px;
line-height: 20px;
@@ -108,7 +109,8 @@
}
&.is-loading {
- .award-control-icon-normal {
+ .award-control-icon-normal,
+ .emoji-icon {
display: none;
}
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/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..bf7334a8942 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;
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 7fa13e66b43..a6765fbc7c7 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;
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/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/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 09ff44f291b..036777c80c1 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -9,13 +9,22 @@ module ToggleAwardEmoji
name = params.require(:name)
awardable.toggle_award_emoji(name, current_user)
- TodoService.new.new_award_emoji(awardable, 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
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/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index db3ae586059..9b80efa5f11 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
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/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 f78b429b3e7..06a114dcbe8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -58,9 +58,16 @@ 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
+ headers.store(*Gitlab::Workhorse.send_git_diff(@project.repository,
+ @merge_request.diff_base_commit.id,
+ @merge_request.last_commit.id))
+ headers['Content-Disposition'] = 'inline'
+
+ head :ok
+ end
end
end
@@ -120,8 +127,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
@@ -200,7 +207,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@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)
@status = :merge_when_build_succeeds
@@ -231,10 +238,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
@@ -317,8 +324,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
@@ -327,8 +334,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 c205474e999..836f79ff080 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,4 +1,6 @@
class Projects::NotesController < Projects::ApplicationController
+ include ToggleAwardEmoji
+
# Authorize
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
@@ -61,6 +63,7 @@ class Projects::NotesController < Projects::ApplicationController
def note
@note ||= @project.notes.find(params[:id])
end
+ alias_method :awardable, :note
def note_to_html(note)
render_to_string(
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/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/sessions_controller.rb b/app/controllers/sessions_controller.rb
index f6eedb1773c..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
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/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/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/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/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 37b93f63145..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"
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/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/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/ci/build.rb b/app/models/ci/build.rb
index 64723ab6b4b..b8ada6361ac 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
@@ -360,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
@@ -370,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..59fc9951d11 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 :commit, 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/issuable.rb b/app/models/concerns/issuable.rb
index 5d279ae602a..0ccd3474b81 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -17,7 +17,12 @@ module Issuable
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
@@ -44,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 }) }
@@ -105,17 +111,24 @@ 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_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)
if title.is_a?(Array) && title.size > 1
joins(:labels).where(labels: { title: title }).group(*grouping_columns(sort)).having("COUNT(DISTINCT labels.title) = #{title.size}")
@@ -139,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?
@@ -158,7 +185,13 @@ module Issuable
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)
@@ -218,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/merge_request.rb b/app/models/merge_request.rb
index 722c258244c..b0ed8182855 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -313,13 +313,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"
@@ -579,8 +572,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 46c3f6e24af..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
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 e4a9d17a20c..f47ef8a81de 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'
@@ -930,12 +930,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
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/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..c3194f45b10 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,
+ commit: 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/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/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
index e9302c39753..84fd146a26b 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -11,7 +11,7 @@
gl.awardMenuUrl = "#{emojis_path}"
.award-menu-holder.js-award-holder
- %button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } }
+ %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
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 9d04db2c45e..a373f61bd3c 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -6,7 +6,8 @@
- 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|
- = f.hidden_field :remember_me, value: params[resource_name][:remember_me]
+ - 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
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/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..271700e6db4 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -36,7 +36,7 @@
- if can?(current_user, :update_group_member, member)
= button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do
- %i.fa.fa-pencil-square-o
+ = 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/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/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/_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/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 306ebd5fcf7..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_open_issues_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_open_merge_request_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 03c9fa0a94d..2a58ef224b3 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -24,17 +24,19 @@
.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 commit commits compare repositories tags branches releases network)) do
- = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
+ = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
= icon('code fw')
%span
Code
@@ -42,7 +44,7 @@
- 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
@@ -63,14 +65,14 @@
- 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')
+ = navbar_icon('milestones')
%span
Milestones
- if project_nav_tab? :issues
= nav_link(controller: :issues) 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?
@@ -79,7 +81,7 @@
- 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)
@@ -94,7 +96,7 @@
- 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
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/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/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/index.html.haml b/app/views/projects/branches/index.html.haml
index 08148b1a18b..0d59c3884cd 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,32 +1,35 @@
+- @no_container = true
- page_title "Branches"
= render "projects/commits/head"
-.row-content-block
- .pull-right
- - if can? current_user, :push_code, @project
- = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
- = 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
+
+%div{ class: (container_class) }
+ .row-content-block.second-block.content-component-block
+ .pull-right
+ - if can? current_user, :push_code, @project
+ = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ = 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
- = 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'
+ %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'
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 818d5d28f04..55d2ac89ebc 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,62 +1,64 @@
+- @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
+ = 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'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 16017c994ba..5477fc65c2b 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -4,7 +4,7 @@
.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)
+ %strong.monospace= link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
- merge_request = @build.merge_request
@@ -13,7 +13,7 @@
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#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|
@@ -178,16 +178,16 @@
Commit
.pull-right
%small
- = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
+ = link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline), 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}
+ #{@build.pipeline.git_author_name}
%p
%span.attr-name Message:
- #{@build.commit.git_commit_message}
+ #{@build.pipeline.git_commit_message}
- if @build.tags.any?
.build-widget
@@ -201,7 +201,7 @@
.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"
+ = link_to @build.pipeline.short_sha, ci_status_path(@build.pipeline), class: "monospace"
%table.table.builds
- @builds.each_with_index do |build, i|
%tr.build
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 1c136133ab0..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: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project) do
- Files
+.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: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
- Commits
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ Commits
- = nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
- Network
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ Network
- = 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: :compare) do
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ Compare
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- Branches
+ = 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
+ = 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/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/_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..7c1457553d9 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -12,10 +12,9 @@
= icon('rss')
%span.icon-label
Subscribe
- = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :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
= render 'shared/issuable/filter', type: :issues
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index a35c13fbd40..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
@@ -68,9 +66,9 @@
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript.
- .content-block.content-block-small
- = render 'new_branch'
- = render 'award_emoji/awards_block', awardable: @issue, inline: true
+ .content-block.content-block-small
+ = render 'new_branch'
+ = 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 294fec422c5..73c6f2a046c 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,27 +1,50 @@
-%li{ id: dom_id(label), data: { id: label.id } }
+- 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'
- %span.append-right-20
- = link_to_label(label) do
- = pluralize label.open_issues_count(current_user), 'open issue'
+ .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?"}
- - 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)}}
+ .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'
- %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
- %span= label_subscription_toggle_button_text(label)
+ - if current_user
+ .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..93583c92609 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,22 +1,35 @@
- page_title "Labels"
+- hide_class = ''
.top-area
.nav-text
Labels can be applied to issues and merge requests.
.nav-controls
- - if can? current_user, :admin_label, @project
+ - 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
.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}.
- - else
- No labels created
+ - 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
+ .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/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 1ec180235ce..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
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 a73d0063be2..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
@@ -54,7 +52,7 @@
= 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
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index b517e874b0f..c8653cb0c30 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -10,7 +10,6 @@
- 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
= render 'shared/issuable/filter', type: :merge_requests
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/_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 0d49b6471a9..60d7d6ff1f5 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,4 +1,4 @@
-- 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
@@ -6,7 +6,7 @@
.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
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 6e757df5417..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.running_or_pending_build_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.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..a78450e09d4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,58 +1,60 @@
+- @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
+ = 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'
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..1e53b8e37da 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -34,7 +34,7 @@
- if can?(current_user, :update_project_member, member)
= button_tag class: "btn-xs 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/index.html.haml b/app/views/projects/tags/index.html.haml
index 8f381663e6e..9ff805a8989 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) }
+ .row-content-block.second-block.content-component-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
-.tags
- - unless @tags.empty?
- %ul.content-list
- - @tags.each do |tag|
- = render 'tag', tag: @repository.find_tag(tag)
+ .tags
+ - unless @tags.empty?
+ %ul.content-list
+ - @tags.each do |tag|
+ = render 'tag', tag: @repository.find_tag(tag)
- = paginate @tags, theme: 'gitlab'
+ = paginate @tags, theme: 'gitlab'
- - else
- .nothing-here-block
- Repository has no tags yet.
- %br
- %small
- Use git tag command to add a new one:
+ - 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 59f60c4687c..2abcfcdd7b2 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,3 +1,5 @@
+- @no_container = true
+
- page_title @path.presence || "Files", @ref
= content_for :meta_tags do
- if current_user
@@ -5,13 +7,14 @@
= 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/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/icons/_activity.svg b/app/views/shared/icons/_activity.svg
new file mode 100644
index 00000000000..c87794b9062
--- /dev/null
+++ b/app/views/shared/icons/_activity.svg
@@ -0,0 +1,15 @@
+<?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>
+ <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 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 Z M6,11 L10.001,11 L10.001,9 L6,9 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 Z" id="path-1"></path>
+ </defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <mask id="mask-2" fill="white">
+ <use xlink:href="#path-1"></use>
+ </mask>
+ <use id="path-1" fill="#D8D8D8" xlink:href="#path-1"></use>
+ </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/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index d6552ae7f18..c7991d53a09 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
@@ -148,7 +133,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/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/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/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/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/routes.rb b/config/routes.rb
index 27ab79d68f5..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'
@@ -441,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
@@ -454,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'
@@ -592,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
@@ -601,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
@@ -719,10 +743,12 @@ 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
@@ -758,6 +784,7 @@ 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
end
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/20160314094147_add_priority_to_label.rb b/db/migrate/20160314094147_add_priority_to_label.rb
new file mode 100644
index 00000000000..8ddf7782972
--- /dev/null
+++ b/db/migrate/20160314094147_add_priority_to_label.rb
@@ -0,0 +1,6 @@
+class AddPriorityToLabel < ActiveRecord::Migration
+ def change
+ add_column :labels, :priority, :integer
+ add_index :labels, :priority
+ 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..fe1c863b5b9
--- /dev/null
+++ b/db/migrate/20160603180330_remove_duplicated_notification_settings.rb
@@ -0,0 +1,32 @@
+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..06462042b09
--- /dev/null
+++ b/db/migrate/20160603182247_add_index_to_notification_settings.rb
@@ -0,0 +1,9 @@
+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..89826fb96cb
--- /dev/null
+++ b/db/migrate/20160608155312_add_after_sign_up_text_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddAfterSignUpTextToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :after_sign_up_text, :text
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9b991f347a9..b7adf48fdb4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,8 @@
#
# 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"
enable_extension "pg_trgm"
@@ -83,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|
@@ -496,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|
@@ -674,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|
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 23760a14b39..5893b7c219e 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -49,8 +49,8 @@ information from database or file system
## Buttons
* Button should contain icon or text. Exceptions should be approved by UX designer.
-* Use gray button on white background or white button on gray background.
* 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/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/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/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/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/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 1dd6cbef615..640f1720a6c 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -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/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/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/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 66c138eb902..50d69274b2e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -351,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/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 db304abe1c3..43221d5622a 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])
@@ -243,7 +243,7 @@ module API
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/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/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/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/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/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index cdcaae8094c..9e09d2e118d 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, @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/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 9d077e79c39..5ef9d66ba68 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -3,6 +3,9 @@ module Gitlab
class Importer
include Gitlab::ShellAdapter
+ GITHUB_SAFE_REMAINING_REQUESTS = 100
+ GITHUB_SAFE_SLEEP_TIME = 500
+
attr_reader :client, :project, :repo, :repo_url
def initialize(project)
@@ -25,14 +28,53 @@ module Gitlab
private
+ def turn_auto_pagination_off!
+ client.auto_paginate = false
+ end
+
+ def turn_auto_pagination_on!
+ client.auto_paginate = true
+ end
+
+ def rate_limit
+ client.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 paginate
+ turn_auto_pagination_off!
+
+ sleep rate_limit_sleep_time if rate_limit_exceed?
+
+ data = yield
+
+ last_response = client.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
+
+ turn_auto_pagination_on!
+
+ data
+ end
+
def credentials
@credentials ||= project.import_data.credentials if project.import_data
end
def import_labels
- client.labels(repo).each do |raw_data|
- Label.create!(LabelFormatter.new(project, raw_data).attributes)
- end
+ labels = paginate { client.labels(repo, per_page: 100) }
+ labels.each { |raw| LabelFormatter.new(project, raw).create! }
true
rescue ActiveRecord::RecordInvalid => e
@@ -40,9 +82,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 = paginate { client.milestones(repo, state: :all, per_page: 100) }
+ milestones.each { |raw| MilestoneFormatter.new(project, raw).create! }
true
rescue ActiveRecord::RecordInvalid => e
@@ -50,16 +91,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)
+ data = paginate { 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)
+ data.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,50 +109,69 @@ 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 = paginate { 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
true
rescue ActiveRecord::RecordInvalid => e
raise Projects::ImportService::Error, e.message
ensure
- delete_refs(branches_removed)
+ clean_up_restored_branches(branches_removed)
+ clean_up_disabled_webhooks(hooks)
end
- def create_refs(branches)
+ 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|
+ sleep rate_limit_sleep_time if rate_limit_exceed?
+ client.edit_hook(repo, hook.id, hook.name, hook.config, options)
+ end
+ end
+
+ def restore_branches(branches)
branches.each do |name, sha|
+ sleep rate_limit_sleep_time if rate_limit_exceed?
client.create_ref(repo, "refs/heads/#{name}", sha)
end
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, _|
+ sleep rate_limit_sleep_time if rate_limit_exceed?
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)
+ sleep rate_limit_sleep_time if rate_limit_exceed?
+
+ issue = client.issue(repo, issuable.iid)
if issue.labels.count > 0
label_ids = issue.labels.map do |raw|
@@ -123,20 +182,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 = paginate { 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 = paginate { 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/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/workhorse.rb b/lib/gitlab/workhorse.rb
index c3ddd4c2680..56af739b1ef 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,
@@ -29,9 +36,22 @@ module Gitlab
"git-archive:#{encode(params)}",
]
end
-
+
+ def send_git_diff(repository, from, to)
+ params = {
+ 'RepoPath' => repository.path_to_repo,
+ 'ShaFrom' => from,
+ 'ShaTo' => to
+ }
+
+ [
+ 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/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/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/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/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 8499bf07e9f..1301574f489 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']).to start_with("git-diff:")
end
end
@@ -250,7 +247,7 @@ describe Projects::MergeRequestsController do
end
before do
- create(:ci_empty_commit, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch)
+ create(:ci_empty_pipeline, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch)
end
it 'returns :merge_when_build_succeeds' do
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/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/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 b72ad405479..1cb709c1de3 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -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
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index e268d76755f..df221ab1f3b 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -5,8 +5,8 @@ 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]
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/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
index 7f654684143..0ec8b6b180a 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,29 @@ 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
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/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/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/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/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/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..0bd6d696387
--- /dev/null
+++ b/spec/javascripts/awards_handler_spec.js.coffee
@@ -0,0 +1,202 @@
+#= require awards_handler
+#= require jquery
+#= require jquery.cookie
+#= require ./fixtures/emoji_menu
+
+awardsHandler = null
+window.gl or= {}
+gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
+gl.awardMenuUrl = '/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) ->
+ expect(req).toBe '/emojis'
+ 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/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/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..ea4ab2c852e 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) { build_stubbed(:namespace, name: "wiki_link_ns") }
+ let(:project) { build_stubbed(: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/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/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 6b2b335d4fc..2034445a197 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -109,11 +109,11 @@ describe Gitlab::Badge::Build do
end
def create_build(project, sha, branch)
- ci_commit = create(:ci_commit, project: project,
- sha: sha,
- ref: branch)
+ pipeline = create(:ci_pipeline, project: project,
+ sha: sha,
+ ref: branch)
- create(:ci_build, commit: ci_commit)
+ create(:ci_build, pipeline: pipeline)
end
def status_node(data, 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/models/build_spec.rb b/spec/models/build_spec.rb
index 5c6c30c20ea..7660ea2659c 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, commit: 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/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index dd03d64f750..efbcbf72f76 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -10,6 +10,16 @@ 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
@@ -245,6 +255,22 @@ describe Issue, "Issuable" do
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 }
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/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index a4c55cc2fd0..1b7cbc3efda 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
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 139f7cb9783..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) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 338a4c3d3f0..553556ed326 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) }
@@ -399,23 +399,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
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6ea8bf9bbe1..73bee535fe3 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -68,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
@@ -76,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
@@ -94,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")
@@ -202,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
@@ -845,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/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 10d22189c8d..9da69a913a8 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -387,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)
@@ -441,8 +441,8 @@ describe API::API, api: true do
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..88271642532 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, commit: 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/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/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index 9d90bfceb73..068c9a1219c 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -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/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/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