summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2016-06-08 14:30:15 +0200
committerDouwe Maan <douwe@selenight.nl>2016-06-08 14:30:15 +0200
commita9857f8c2f37668853bcc44b89d4c5e34adcf9e4 (patch)
treeb5e98311ddf131b616340c00a9c88b3a4c423230
parent3855e33cf8fe95d6de83b18698a958095fa49df5 (diff)
parentc0f31845d34362b726506ecf08ceab06010dadd6 (diff)
downloadgitlab-ce-a9857f8c2f37668853bcc44b89d4c5e34adcf9e4.tar.gz
Add send_git_diff helper
-rw-r--r--.gitlab-ci.yml352
-rw-r--r--CHANGELOG16
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock30
-rwxr-xr-xRakefile2
-rw-r--r--app/assets/javascripts/LabelManager.js.coffee84
-rw-r--r--app/assets/javascripts/application.js.coffee2
-rw-r--r--app/assets/javascripts/awards_handler.coffee272
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee6
-rw-r--r--app/assets/javascripts/due_date_select.js.coffee5
-rw-r--r--app/assets/javascripts/lib/emoji_aliases.js.coffee.erb2
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee4
-rw-r--r--app/assets/javascripts/notes.js.coffee7
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee18
-rw-r--r--app/assets/javascripts/users_select.js.coffee2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss5
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss28
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss7
-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.scss52
-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.scss2
-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/labels.scss33
-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/concerns/toggle_award_emoji.rb11
-rw-r--r--app/controllers/projects/artifacts_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb10
-rw-r--r--app/controllers/projects/labels_controller.rb29
-rw-r--r--app/controllers/projects/merge_requests_controller.rb32
-rw-r--r--app/controllers/projects/notes_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb8
-rw-r--r--app/helpers/ci_status_helper.rb6
-rw-r--r--app/helpers/issuables_helper.rb12
-rw-r--r--app/helpers/notifications_helper.rb22
-rw-r--r--app/helpers/sorting_helper.rb11
-rw-r--r--app/helpers/workhorse_helper.rb9
-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.rb6
-rw-r--r--app/models/commit_status.rb12
-rw-r--r--app/models/concerns/issuable.rb23
-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/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/runners/show.html.haml4
-rw-r--r--app/views/award_emoji/_awards_block.html.haml2
-rw-r--r--app/views/events/event/_common.html.haml10
-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/_dashboard.html.haml24
-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.haml8
-rw-r--r--app/views/projects/_md_preview.html.haml6
-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/issues/_merge_requests.html.haml6
-rw-r--r--app/views/projects/issues/_related_branches.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml6
-rw-r--r--app/views/projects/labels/_label.html.haml15
-rw-r--r--app/views/projects/labels/index.html.haml36
-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.haml2
-rw-r--r--app/views/projects/merge_requests/show/_builds.html.haml2
-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.haml12
-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/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.haml9
-rw-r--r--app/views/shared/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml15
-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/metrics.rb7
-rw-r--r--config/license_finder.yml2
-rw-r--r--config/routes.rb3
-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.rb7
-rw-r--r--db/migrate/20160603182247_add_index_to_notification_settings.rb9
-rw-r--r--db/schema.rb5
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/licensing.md93
-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.rb10
-rw-r--r--features/steps/project/merge_requests.rb4
-rw-r--r--features/steps/project/project.rb2
-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/merge_requests.rb2
-rw-r--r--lib/ci/charts.rb2
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb10
-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/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.rb138
-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/workhorse.rb19
-rw-r--r--lib/tasks/gitlab/setup.rake2
-rwxr-xr-xscripts/merge-reports29
-rwxr-xr-xscripts/prepare_build.sh10
-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/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.rb172
-rw-r--r--spec/features/commits_spec.rb56
-rw-r--r--spec/features/issues_spec.rb8
-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/helpers/ci_status_helper_spec.rb4
-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/graphs/stat_graph_contributors_util_spec.js12
-rw-r--r--spec/javascripts/new_branch_spec.js.coffee2
-rw-r--r--spec/lib/ci/charts_spec.rb10
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb26
-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/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/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/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/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/system_note_service_spec.rb3
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--spec/support/stub_gitlab_calls.rb8
-rw-r--r--spec/workers/post_receive_spec.rb12
-rw-r--r--vendor/assets/javascripts/task_list.js.coffee258
231 files changed, 5316 insertions, 2052 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85730e1b687..5ef3081395a 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,199 @@ 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
-
-brakeman:
- stage: test
- script:
- - bundle exec rake brakeman
-
-flog:
- stage: test
- script:
- - bundle exec rake flog
-
-flay:
- stage: test
- script:
- - bundle exec rake flay
-
-bundler:audit:
- stage: test
- only:
- - master
- script:
- - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+# Prepare and merge knapsack tests
-db-migrate-reset:
- stage: test
- script:
- - RAILS_ENV=test bundle exec rake db:migrate:reset
-
-# Ruby 2.2 jobs
-
-spec:feature: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 spec:feature
+.knapsack-state: &knapsack-state
+ services: []
+ variables:
+ USE_DB: "false"
+ USE_BUNDLE_INSTALL: "false"
cache:
- key: "ruby22"
+ key: "knapsack"
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"
+ - knapsack/
+ artifacts:
paths:
- - vendor
+ - knapsack/
-spec:models:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
+knapsack:
+ <<: *knapsack-state
+ stage: prepare
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
- cache:
- key: "ruby22"
- paths:
- - vendor
+ - mkdir -p knapsack/
+ - '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json'
+ - '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json'
-spec:lib:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
+update-knapsack:
+ <<: *knapsack-state
+ stage: post-test
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
- cache:
- key: "ruby22"
- paths:
- - vendor
+ - 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
-spec:services:ruby22:
- stage: test
- image: ruby:2.2
- only:
- - master
- script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
- cache:
- key: "ruby22"
- paths:
- - vendor
+# Execute all testing suites
-spec:other:ruby22:
+.rspec-knapsack: &rspec-knapsack
stage: test
- image: ruby:2.2
- only:
- - master
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
- 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
-
-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"
+ - 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
-
-spinach:project:rest: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 bundle exec rake assets:precompile 2>/dev/null
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
+ - master
cache:
key: "ruby22"
paths:
- vendor
-spinach:other:ruby22:
+.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
+
+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:other
- cache:
- key: "ruby22"
- paths:
- - vendor
+ - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+
+# 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/CHANGELOG b/CHANGELOG
index 03e7e5b1e56..d2a36112d7b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,10 +2,13 @@ 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)
- 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)
+ - 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,7 +17,9 @@ 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
+ - 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
@@ -33,18 +38,28 @@ v 8.9.0 (unreleased)
- 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
- Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects
+ - Remove duplicated notification settings
- Put project Files and Commits tabs under Code tab
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers
+ - An indicator is now displayed at the top of the comment field for confidential issues.
+ - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
v 8.8.4 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
- Fix issue with arrow keys not working in search autocomplete dropdown
- 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
@@ -165,6 +180,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..f2ac70831f5 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
@@ -111,7 +111,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 +306,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..28de59beec7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -358,6 +358,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 +369,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 +390,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 +401,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 +475,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 +551,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 +580,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 +629,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 +740,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 +802,7 @@ GEM
builder
expression_parser
rinku
+ xml-simple (1.1.5)
xpath (2.0.0)
nokogiri (~> 1.3)
@@ -874,7 +888,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 +934,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 +942,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/application.js.coffee b/app/assets/javascripts/application.js.coffee
index a76b111bf03..ebf425550e9 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -4,7 +4,7 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
-#= require jquery
+#= require jquery2
#= require jquery-ui/autocomplete
#= require jquery-ui/datepicker
#= require jquery-ui/draggable
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/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
index 3cc70185178..3d009a96d05 100644
--- a/app/assets/javascripts/due_date_select.js.coffee
+++ b/app/assets/javascripts/due_date_select.js.coffee
@@ -21,7 +21,7 @@ class @DueDateSelect
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
)
addDueDate = (isDropdown) ->
@@ -42,12 +42,13 @@ class @DueDateSelect
type: 'PUT'
url: issueUpdateURL
data: data
+ dataType: 'json'
beforeSend: ->
$loading.fadeIn()
if isDropdown
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
$valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
diff --git a/app/assets/javascripts/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/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
index 345a0e447af..1d061d5edb7 100644
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ b/app/assets/javascripts/milestone_select.js.coffee
@@ -83,7 +83,7 @@ class @MilestoneSelect
$selectbox.hide()
# display:block overrides the hide-collapse rule
- $value.removeAttr('style')
+ $value.css('display', '')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
@@ -118,7 +118,7 @@ class @MilestoneSelect
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
- $value.removeAttr('style')
+ $value.css('display', '')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7c3d57fc194..8e33e915ba5 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
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/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 519618aa617..de0eae58bff 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -149,7 +149,7 @@ class @UsersSelect
hidden: (e) ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
- $value.removeAttr('style')
+ $value.css('display', '')
clicked: (user) ->
page = $('body').data 'page'
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/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 28634d0c59f..1ce7c57ebcd 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -122,10 +122,8 @@
a {
display: block;
position: relative;
- padding-left: 10px;
- padding-right: 10px;
+ padding: 5px 10px;
color: $dropdown-link-color;
- line-height: 34px;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
@@ -162,6 +160,16 @@
}
}
+.dropdown-menu-large {
+ width: 340px;
+}
+
+.dropdown-menu-no-wrap {
+ a {
+ white-space: normal;
+ }
+}
+
.dropdown-menu-full-width {
width: 100%;
}
@@ -236,8 +244,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 +539,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..cd2eba59f90 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -89,8 +89,11 @@
}
}
-$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 +105,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..a811778df70 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%;
@@ -309,8 +337,8 @@
}
.nav-control {
- .fade-right {
+ .fade-right {
@media (min-width: $screen-xs-max) {
right: 67px;
}
@@ -321,6 +349,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..46d46368d25 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,47 +36,8 @@
.sidebar-wrapper {
.header-logo {
- border-bottom: 1px solid transparent;
- float: left;
height: $header-height;
- 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;
- }
- }
- }
+ padding: 8px 26px;
&:hover {
background-color: #eee;
@@ -98,7 +45,7 @@
}
.sidebar-user {
- padding: 7px 22px;
+ padding: 15px 22px;
position: fixed;
bottom: 40px;
width: $sidebar_width;
@@ -126,8 +73,7 @@
.nav-sidebar {
- margin-top: 14 + $header-height;
- margin-bottom: 100px;
+ margin: 22px 0;
transition-duration: .3s;
list-style: none;
overflow: hidden;
@@ -145,13 +91,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;
@@ -166,14 +111,12 @@
i {
width: 16px;
color: $gray-light;
- margin-right: 13px;
}
- .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 +160,13 @@
}
.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;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
@@ -246,6 +177,10 @@
}
}
+ #logo {
+ display: none;
+ }
+
.nav-sidebar {
width: $sidebar_collapsed_width;
@@ -261,44 +196,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 +242,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..60207ecf1d6 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;
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/labels.scss b/app/assets/stylesheets/pages/labels.scss
index e179bdf0048..2cd9d74b2de 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -51,7 +51,7 @@
.label-row {
.label-name {
display: inline-block;
- width: 200px;
+ width: 170px;
@media (max-width: $screen-xs-min) {
display: block;
@@ -138,3 +138,34 @@
}
}
}
+
+.prioritized-labels {
+ margin-bottom: 30px;
+
+ .add-priority {
+ display: none;
+ color: $gray-light;
+ }
+}
+
+.other-labels {
+ .remove-priority {
+ display: none;
+ }
+}
+
+.toggle-priority {
+ display: inline-block;
+ vertical-align: middle;
+
+ button {
+ border-color: transparent;
+ padding: 5px 8px;
+ vertical-align: top;
+ font-size: 14px;
+
+ &:hover {
+ border-color: transparent;
+ }
+ }
+}
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/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/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index cfea1266516..832d7deb57d 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -37,7 +37,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
private
def build
- @build ||= project.builds.unscoped.find_by!(id: params[:build_id])
+ @build ||= project.builds.find_by!(id: params[:build_id])
end
def artifacts_file
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index bb1f6c5e980..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
@@ -81,7 +81,7 @@ class Projects::BuildsController < Projects::ApplicationController
private
def build
- @build ||= project.builds.unscoped.find_by!(id: params[:id])
+ @build ||= project.builds.find_by!(id: params[:id])
end
def build_path(build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 10b5932affa..20637fa46fe 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -99,12 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
- def ci_commits
- @ci_commits ||= project.ci_commits.where(sha: commit.sha)
+ def pipelines
+ @pipelines ||= project.pipelines.where(sha: commit.sha)
end
def ci_builds
- @ci_builds ||= Ci::Build.where(commit: ci_commits)
+ @ci_builds ||= Ci::Build.where(pipeline: pipelines)
end
def define_show_vars
@@ -117,8 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
- @statuses = CommitStatus.where(commit: ci_commits)
- @builds = Ci::Build.where(commit: ci_commits)
+ @statuses = CommitStatus.where(pipeline: pipelines)
+ @builds = Ci::Build.where(pipeline: pipelines)
end
def assign_change_commit_vars(mr_source_branch)
diff --git a/app/controllers/projects/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..67e7187c10d 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -58,9 +58,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
- format.json { render json: @merge_request }
- format.diff { render text: @merge_request.to_diff }
- format.patch { render text: @merge_request.to_patch }
+ format.json { render json: @merge_request }
+ format.patch { render text: @merge_request.to_patch }
+ format.diff do
+ return render_404 unless @merge_request.diff_refs
+
+ send_git_diff @project.repository, @merge_request.diff_refs
+ end
end
end
@@ -120,8 +124,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
- @ci_commit = @merge_request.ci_commit
- @statuses = @ci_commit.statuses if @ci_commit
+ @pipeline = @merge_request.pipeline
+ @statuses = @pipeline.statuses if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@@ -200,7 +204,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 +235,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def ci_status
- ci_commit = @merge_request.ci_commit
- if ci_commit
- status = ci_commit.status
- coverage = ci_commit.try(:coverage)
+ pipeline = @merge_request.pipeline
+ if pipeline
+ status = pipeline.status
+ coverage = pipeline.try(:coverage)
status ||= "preparing"
else
@@ -317,8 +321,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
- @ci_commit = @merge_request.ci_commit
- @statuses = @ci_commit.statuses if @ci_commit
+ @pipeline = @merge_request.pipeline
+ @statuses = @pipeline.statuses if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@@ -327,8 +331,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_widget_vars
- @ci_commit = @merge_request.ci_commit
- @ci_commits = [@ci_commit].compact
+ @pipeline = @merge_request.pipeline
+ @pipelines = [@pipeline].compact
closes_issues
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 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/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/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/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/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 54ab9179efc..b8e64b3890a 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -31,6 +31,21 @@ module NotificationsHelper
end
end
+ def notification_description(level)
+ case level.to_sym
+ when :participating
+ 'You will only receive notifications from related resources'
+ when :mention
+ 'You will receive notifications only for comments in which you were @mentioned'
+ when :watch
+ 'You will receive notifications for any activity'
+ when :disabled
+ 'You will not get any notifications via email'
+ when :global
+ 'Use your global notification setting'
+ end
+ end
+
def notification_list_item(level, setting)
title = notification_title(level)
@@ -39,9 +54,10 @@ module NotificationsHelper
notification_title: title
}
- content_tag(:li, class: ('active' if setting.level == level)) do
- link_to '#', class: 'update-notification', data: data do
- notification_icon(level, title)
+ content_tag(:li, role: "menuitem") do
+ link_to '#', class: "update-notification #{('is-active' if setting.level == level)}", data: data do
+ link_output = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
+ link_output << content_tag(:span, notification_description(level), class: 'dropdown-menu-inner-content')
end
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 630e10ea892..d86f1999f5c 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -14,7 +14,8 @@ module SortingHelper
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
- sort_value_upvotes => sort_title_upvotes
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_priority => sort_title_priority
}
end
@@ -28,6 +29,10 @@ module SortingHelper
}
end
+ def sort_title_priority
+ 'Priority'
+ end
+
def sort_title_oldest_updated
'Oldest updated'
end
@@ -84,6 +89,10 @@ module SortingHelper
'Most popular'
end
+ def sort_value_priority
+ 'priority'
+ end
+
def sort_value_oldest_updated
'updated_asc'
end
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 9d306c9096e..2bd0dbfd095 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -1,4 +1,4 @@
-# Helpers to send Git blobs or archives through Workhorse.
+# Helpers to send Git blobs, diffs or archives through Workhorse.
# Workhorse will also serve files when using `send_file`.
module WorkhorseHelper
# Send a Git blob through Workhorse
@@ -9,6 +9,13 @@ module WorkhorseHelper
head :ok # 'render nothing: true' messes up the Content-Type
end
+ # Send a Git diff through Workhorse
+ def send_git_diff(repository, diff_refs)
+ headers.store(*Gitlab::Workhorse.send_git_diff(repository, diff_refs))
+ headers['Content-Disposition'] = 'inline'
+ head :ok
+ end
+
# Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
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..b5637bc4fbc 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -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..92526a99147 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -105,17 +105,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 +146,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?
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/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/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/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/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/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 306ebd5fcf7..df77d9cf83e 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -2,54 +2,50 @@
= 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
+ .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
+ .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
+ .nav-link-text
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw')
- %span
+ .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
+ .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
+ .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/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..f0e04a0235d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -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/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/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/_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/show.html.haml b/app/views/projects/issues/show.html.haml
index a35c13fbd40..b2f14a54073 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -68,9 +68,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..1c51ea676c7 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,4 +1,5 @@
-%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
@@ -10,18 +11,18 @@
= pluralize label.open_issues_count(current_user), 'open issue'
- if current_user
- .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
- .subscription-status{data: {status: label_subscription_status(label)}}
+ .label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ .subscription-status{ data: { status: label_subscription_status(label) } }
%button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
%span= label_subscription_toggle_button_text(label)
- - 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
+ - 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
+ = 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
- if current_user
:javascript
- new Subscription('##{dom_id(label)} .label-subscription');
+ new Subscription('##{label_css_id} .label-subscription');
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 2557d1a4d5b..c72eddba37f 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,22 +1,36 @@
- 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..c30459ae566 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -54,7 +54,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/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/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..5ddd0ecc4c1 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -19,20 +19,24 @@
.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 note_editable
+ = 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')
= 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/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..d315a3fe93b 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,5 +1,12 @@
%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
+ = markdown(label.description, pipeline: :single_line)
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/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index d6552ae7f18..1ec2436c835 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
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/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..240dcc74b06 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -719,10 +719,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 +760,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..c2fcac4c53d
--- /dev/null
+++ b/db/migrate/20160603180330_remove_duplicated_notification_settings.rb
@@ -0,0 +1,7 @@
+class RemoveDuplicatedNotificationSettings < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM notification_settings WHERE id NOT IN ( SELECT min_id from (SELECT MIN(id) as min_id FROM notification_settings GROUP BY user_id, source_type, source_id) as dups )
+ SQL
+ 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/schema.rb b/db/schema.rb
index 659ddc6df04..00829d63b66 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160530150109) do
+ActiveRecord::Schema.define(version: 20160603182247) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -497,8 +497,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|
@@ -675,6 +677,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/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..67a986ead57
--- /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-8-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.7)
+
+### 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..e02b57bbf84 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -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/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/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/merge_requests.rb b/lib/api/merge_requests.rb
index db304abe1c3..2e7836dc8fb 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -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/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/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/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..442b4c389fe 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,68 @@ 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|
+ 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 +181,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/workhorse.rb b/lib/gitlab/workhorse.rb
index 96e99dc0088..8930149b12c 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -14,7 +14,7 @@ module Gitlab
[
SEND_DATA_HEADER,
- "git-blob:#{encode(params)}",
+ "git-blob:#{encode(params)}"
]
end
@@ -26,7 +26,22 @@ module Gitlab
[
SEND_DATA_HEADER,
- "git-archive:#{encode(params)}",
+ "git-archive:#{encode(params)}"
+ ]
+ end
+
+ def send_git_diff(repository, diff_refs)
+ from, to = diff_refs
+
+ params = {
+ 'RepoPath' => repository.path_to_repo,
+ 'ShaFrom' => from.sha,
+ 'ShaTo' => to.sha
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "git-diff:#{encode(params)}"
]
end
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/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..4b408c03703 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -84,17 +84,14 @@ describe Projects::MergeRequestsController do
end
describe "as diff" do
- include_examples "export merge as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
+ it "triggers workhorse to serve the request" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
- format: format)
+ format: :diff)
- expect(response.body).to start_with("diff --git")
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-diff:")
end
end
@@ -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/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 7a05d30e8b5..df221ab1f3b 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -5,8 +5,9 @@ describe "Builds" do
before do
login_as(:user)
- @commit = FactoryGirl.create :ci_commit
- @build = FactoryGirl.create :ci_build, commit: @commit
+ @commit = FactoryGirl.create :ci_pipeline
+ @build = FactoryGirl.create :ci_build, pipeline: @commit
+ @build2 = FactoryGirl.create :ci_build
@project = @commit.project
@project.team << [@user, :developer]
end
@@ -66,13 +67,24 @@ describe "Builds" do
end
describe "GET /:project/builds/:id" do
- before do
- visit namespace_project_build_path(@project.namespace, @project, @build)
+ context "Build from project" do
+ before do
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content @commit.sha[0..7] }
+ it { expect(page).to have_content @commit.git_commit_message }
+ it { expect(page).to have_content @commit.git_author_name }
end
- it { expect(page).to have_content @commit.sha[0..7] }
- it { expect(page).to have_content @commit.git_commit_message }
- it { expect(page).to have_content @commit.git_author_name }
+ context "Build from other project" do
+ before do
+ visit namespace_project_build_path(@project.namespace, @project, @build2)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
context "Download artifacts" do
before do
@@ -103,51 +115,143 @@ describe "Builds" do
end
describe "POST /:project/builds/:id/cancel" do
- before do
- @build.run!
- visit namespace_project_build_path(@project.namespace, @project, @build)
- click_link "Cancel"
+ context "Build from project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link "Cancel"
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content 'canceled' }
+ it { expect(page).to have_content 'Retry' }
end
- it { expect(page).to have_content 'canceled' }
- it { expect(page).to have_content 'Retry' }
+ context "Build from other project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2))
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
end
describe "POST /:project/builds/:id/retry" do
- before do
- @build.run!
- visit namespace_project_build_path(@project.namespace, @project, @build)
- click_link "Cancel"
- click_link 'Retry'
+ context "Build from project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link 'Cancel'
+ click_link 'Retry'
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page).to have_content 'pending' }
+ it { expect(page).to have_content 'Cancel' }
end
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
+ context "Build from other project" do
+ before do
+ @build.run!
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ click_link 'Cancel'
+ page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
end
describe "GET /:project/builds/:id/download" do
- before do
- @build.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_build_path(@project.namespace, @project, @build)
- page.within('.artifacts') { click_link 'Download' }
+ context "Build from project" do
+ before do
+ @build.update_attributes(artifacts_file: artifacts_file)
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ page.within('.artifacts') { click_link 'Download' }
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) }
end
- it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) }
+ context "Build from other project" do
+ before do
+ @build2.update_attributes(artifacts_file: artifacts_file)
+ visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
end
describe "GET /:project/builds/:id/raw" do
- before do
- Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
- @build.run!
- @build.trace = 'BUILD TRACE'
- visit namespace_project_build_path(@project.namespace, @project, @build)
+ context "Build from project" do
+ before do
+ Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ @build.run!
+ @build.trace = 'BUILD TRACE'
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ page.within('.build-controls') { click_link 'Raw' }
+ end
+
+ it 'sends the right headers' do
+ expect(page.status_code).to eq(200)
+ expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace)
+ end
+ end
+
+ context "Build from other project" do
+ before do
+ Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile')
+ @build2.run!
+ @build2.trace = 'BUILD TRACE'
+ visit raw_namespace_project_build_path(@project.namespace, @project, @build2)
+ puts page.status_code
+ puts current_url
+ end
+
+ it 'sends the right headers' do
+ expect(page.status_code).to eq(404)
+ end
+ end
+ end
+
+ describe "GET /:project/builds/:id/trace.json" do
+ context "Build from project" do
+ before do
+ visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json)
+ end
+
+ it { expect(page.status_code).to eq(200) }
+ end
+
+ context "Build from other project" do
+ before do
+ visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json)
+ end
+
+ it { expect(page.status_code).to eq(404) }
+ end
+ end
+
+ describe "GET /:project/builds/:id/status" do
+ context "Build from project" do
+ before do
+ visit status_namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it { expect(page.status_code).to eq(200) }
end
- it 'sends the right headers' do
- page.within('.build-controls') { click_link 'Raw' }
+ context "Build from other project" do
+ before do
+ visit status_namespace_project_build_path(@project.namespace, @project, @build2)
+ end
- expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace)
+ it { expect(page.status_code).to eq(404) }
end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 20f0b27bcc1..45e1a157a1f 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -8,15 +8,15 @@ describe 'Commits' do
describe 'CI' do
before do
login_as :user
- stub_ci_commit_to_return_yaml_file
+ stub_ci_pipeline_to_return_yaml_file
end
- let!(:commit) do
- FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
+ let!(:pipeline) do
+ FactoryGirl.create :ci_pipeline, project: project, sha: project.commit.sha
end
context 'commit status is Generic Commit Status' do
- let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
+ let!(:status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
before do
project.team << [@user, :reporter]
@@ -24,10 +24,10 @@ describe 'Commits' do
describe 'Commit builds' do
before do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
- it { expect(page).to have_content commit.sha[0..7] }
+ it { expect(page).to have_content pipeline.sha[0..7] }
it 'contains generic commit status build' do
page.within('.table-holder') do
@@ -39,7 +39,7 @@ describe 'Commits' do
end
context 'commit status is Ci Build' do
- let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+ let!(:build) { FactoryGirl.create :ci_build, pipeline: pipeline }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
context 'when logged as developer' do
@@ -53,7 +53,7 @@ describe 'Commits' do
end
it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
+ page.within("//li[@id='commit-#{pipeline.short_sha}']") do
expect(page).to have_css(".ci-status-link")
end
end
@@ -61,12 +61,12 @@ describe 'Commits' do
describe 'Commit builds' do
before do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
- it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.git_commit_message }
- it { expect(page).to have_content commit.git_author_name }
+ it { expect(page).to have_content pipeline.sha[0..7] }
+ it { expect(page).to have_content pipeline.git_commit_message }
+ it { expect(page).to have_content pipeline.git_author_name }
end
context 'Download artifacts' do
@@ -75,7 +75,7 @@ describe 'Commits' do
end
it do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Download artifacts'
expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
end
@@ -83,7 +83,7 @@ describe 'Commits' do
describe 'Cancel all builds' do
it 'cancels commit' do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
end
@@ -91,7 +91,7 @@ describe 'Commits' do
describe 'Cancel build' do
it 'cancels build' do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
click_on 'Cancel'
expect(page).to have_content 'canceled'
end
@@ -100,13 +100,13 @@ describe 'Commits' do
describe '.gitlab-ci.yml not found warning' do
context 'ci builds enabled' do
it "does not show warning" do
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
it 'shows warning' do
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
+ stub_ci_pipeline_yaml_file(nil)
+ visit ci_status_path(pipeline)
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
end
end
@@ -114,8 +114,8 @@ describe 'Commits' do
context 'ci builds disabled' do
before do
stub_ci_builds_disabled
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
+ stub_ci_pipeline_yaml_file(nil)
+ visit ci_status_path(pipeline)
end
it 'does not show warning' do
@@ -129,13 +129,13 @@ describe 'Commits' do
before do
project.team << [@user, :reporter]
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
it do
- expect(page).to have_content commit.sha[0..7]
- expect(page).to have_content commit.git_commit_message
- expect(page).to have_content commit.git_author_name
+ expect(page).to have_content pipeline.sha[0..7]
+ expect(page).to have_content pipeline.git_commit_message
+ expect(page).to have_content pipeline.git_author_name
expect(page).to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry failed')
@@ -148,13 +148,13 @@ describe 'Commits' do
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(commit)
+ visit ci_status_path(pipeline)
end
it do
- expect(page).to have_content commit.sha[0..7]
- expect(page).to have_content commit.git_commit_message
- expect(page).to have_content commit.git_author_name
+ expect(page).to have_content pipeline.sha[0..7]
+ expect(page).to have_content pipeline.git_commit_message
+ expect(page).to have_content pipeline.git_author_name
expect(page).not_to have_link('Download artifacts')
expect(page).not_to have_link('Cancel running')
expect(page).not_to have_link('Retry failed')
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index a4eed5031c9..460d7f82b36 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -365,13 +365,9 @@ describe 'Issues', feature: true do
page.within('.assignee') do
expect(page).to have_content "#{@user.name}"
- end
- find('.block.assignee .edit-link').click
- sleep 2 # wait for ajax stuff to complete
- first('.dropdown-menu-user-link').click
- sleep 2
- page.within('.assignee') do
+ click_link 'Edit'
+ click_link 'Unassigned'
expect(page).to have_content 'No assignee'
end
diff --git a/spec/features/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/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/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/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index 5b992447473..56970e22e34 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -9,14 +9,14 @@ describe("ContributorsStatGraphUtil", function () {
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 6, deletions: 1},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 19, deletions: 3},
{author_email: "dzaporozhets@email.com", author_name: "Dmitriy Zaporozhets", date: "2013-05-08", additions: 29, deletions: 3}]
-
+
var correct_parsed_log = {
total: [
{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:
[
- {
+ {
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
@@ -132,8 +132,8 @@ describe("ContributorsStatGraphUtil", function () {
total: [{date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}],
by_author:[
- {
- author: "Karlo Soriano",
+ {
+ author: "Karlo Soriano",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
{
@@ -161,11 +161,11 @@ describe("ContributorsStatGraphUtil", function () {
it("returns the log by author sorted by specified field", function () {
var fake_parsed_log = {
total: [
- {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
+ {date: "2013-05-09", additions: 471, deletions: 0, commits: 1},
{date: "2013-05-08", additions: 54, deletions: 7, commits: 3}
],
by_author: [
- {
+ {
author_name: "Karlo Soriano", author_email: "karlo@email.com",
"2013-05-09": {date: "2013-05-09", additions: 471, deletions: 0, commits: 1}
},
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
index f2ce85efcdc..ce773793817 100644
--- a/spec/javascripts/new_branch_spec.js.coffee
+++ b/spec/javascripts/new_branch_spec.js.coffee
@@ -1,4 +1,4 @@
-#= require jquery-ui
+#= require jquery-ui/autocomplete
#= require new_branch_form
describe 'Branch', ->
diff --git a/spec/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/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index b6f7a2e7ec4..2034445a197 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -42,9 +42,7 @@ describe Gitlab::Badge::Build do
end
context 'build exists' do
- let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) }
- let!(:build) { create(:ci_build, commit: ci_commit) }
-
+ let!(:build) { create_build(project, sha, branch) }
context 'build success' do
before { build.success! }
@@ -96,6 +94,28 @@ describe Gitlab::Badge::Build do
end
end
+ context 'when outdated pipeline for given ref exists' do
+ before do
+ build = create_build(project, sha, branch)
+ build.success!
+
+ old_build = create_build(project, '11eeffdd', branch)
+ old_build.drop!
+ end
+
+ it 'does not take outdated pipeline into account' do
+ expect(badge.to_s).to eq 'build-success'
+ end
+ end
+
+ def create_build(project, sha, branch)
+ pipeline = create(:ci_pipeline, project: project,
+ sha: sha,
+ ref: branch)
+
+ create(:ci_build, pipeline: pipeline)
+ end
+
def status_node(data, status)
xml = Nokogiri::XML.parse(data)
xml.at(%Q{text:contains("#{status}")})
diff --git a/spec/lib/gitlab/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/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/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/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/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/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/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index f73416a3d0f..93f96cacc00 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -13,12 +13,12 @@ module StubGitlabCalls
allow_any_instance_of(Network).to receive(:projects) { project_hash_array }
end
- def stub_ci_commit_to_return_yaml_file
- stub_ci_commit_yaml_file(gitlab_ci_yaml)
+ def stub_ci_pipeline_to_return_yaml_file
+ stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
end
- def stub_ci_commit_yaml_file(ci_yaml)
- allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
+ def stub_ci_pipeline_yaml_file(ci_yaml)
+ allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
end
def stub_ci_builds_disabled
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 20d3dfb42b3..b8e73682c91 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -52,16 +52,16 @@ describe PostReceive do
context "gitlab-ci.yml" do
subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) }
- context "creates a Ci::Commit for every change" do
- before { stub_ci_commit_to_return_yaml_file }
+ context "creates a Ci::Pipeline for every change" do
+ before { stub_ci_pipeline_to_return_yaml_file }
- it { expect{ subject }.to change{ Ci::Commit.count }.by(2) }
+ it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) }
end
- context "does not create a Ci::Commit" do
- before { stub_ci_commit_yaml_file(nil) }
+ context "does not create a Ci::Pipeline" do
+ before { stub_ci_pipeline_yaml_file(nil) }
- it { expect{ subject }.not_to change{ Ci::Commit.count } }
+ it { expect{ subject }.not_to change{ Ci::Pipeline.count } }
end
end
end
diff --git a/vendor/assets/javascripts/task_list.js.coffee b/vendor/assets/javascripts/task_list.js.coffee
new file mode 100644
index 00000000000..584751af8ea
--- /dev/null
+++ b/vendor/assets/javascripts/task_list.js.coffee
@@ -0,0 +1,258 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2014 GitHub, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# TaskList Behavior
+#
+#= provides tasklist:enabled
+#= provides tasklist:disabled
+#= provides tasklist:change
+#= provides tasklist:changed
+#
+#
+# Enables Task List update behavior.
+#
+# ### Example Markup
+#
+# <div class="js-task-list-container">
+# <ul class="task-list">
+# <li class="task-list-item">
+# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
+# text
+# </li>
+# </ul>
+# <form>
+# <textarea class="js-task-list-field">- [ ] text</textarea>
+# </form>
+# </div>
+#
+# ### Specification
+#
+# TaskLists MUST be contained in a `(div).js-task-list-container`.
+#
+# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
+#
+# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
+# `disabled` by default.
+#
+# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
+# `value` attribute is the source (Markdown) to be udpated. The source MUST
+# follow the syntax guidelines.
+#
+# TaskList updates trigger `tasklist:change` events. If the change is
+# successful, `tasklist:changed` is fired. The change can be canceled.
+#
+# jQuery is required.
+#
+# ### Methods
+#
+# `.taskList('enable')` or `.taskList()`
+#
+# Enables TaskList updates for the container.
+#
+# `.taskList('disable')`
+#
+# Disables TaskList updates for the container.
+#
+## ### Events
+#
+# `tasklist:enabled`
+#
+# Fired when the TaskList is enabled.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-container`
+#
+# `tasklist:disabled`
+#
+# Fired when the TaskList is disabled.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-container`
+#
+# `tasklist:change`
+#
+# Fired before the TaskList item change takes affect.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** Yes
+# * **Target** `.js-task-list-field`
+#
+# `tasklist:changed`
+#
+# Fired once the TaskList item change has taken affect.
+#
+# * **Synchronicity** Sync
+# * **Bubbles** Yes
+# * **Cancelable** No
+# * **Target** `.js-task-list-field`
+#
+# ### NOTE
+#
+# Task list checkboxes are rendered as disabled by default because rendered
+# user content is cached without regard for the viewer.
+
+incomplete = "[ ]"
+complete = "[x]"
+
+# Escapes the String for regular expression matching.
+escapePattern = (str) ->
+ str.
+ replace(/([\[\]])/g, "\\$1"). # escape square brackets
+ replace(/\s/, "\\s"). # match all white space
+ replace("x", "[xX]") # match all cases
+
+incompletePattern = ///
+ #{escapePattern(incomplete)}
+///
+completePattern = ///
+ #{escapePattern(complete)}
+///
+
+# Pattern used to identify all task list items.
+# Useful when you need iterate over all items.
+itemPattern = ///
+ ^
+ (?: # prefix, consisting of
+ \s* # optional leading whitespace
+ (?:>\s*)* # zero or more blockquotes
+ (?:[-+*]|(?:\d+\.)) # list item indicator
+ )
+ \s* # optional whitespace prefix
+ ( # checkbox
+ #{escapePattern(complete)}|
+ #{escapePattern(incomplete)}
+ )
+ \s+ # is followed by whitespace
+ (?!
+ \(.*?\) # is not part of a [foo](url) link
+ )
+ (?= # and is followed by zero or more links
+ (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
+ (?:[^\[]|$) # and either a non-link or the end of the string
+ )
+///
+
+# Used to filter out code fences from the source for comparison only.
+# http://rubular.com/r/x5EwZVrloI
+# Modified slightly due to issues with JS
+codeFencesPattern = ///
+ ^`{3} # ```
+ (?:\s*\w+)? # followed by optional language
+ [\S\s] # whitespace
+ .* # code
+ [\S\s] # whitespace
+ ^`{3}$ # ```
+///mg
+
+# Used to filter out potential mismatches (items not in lists).
+# http://rubular.com/r/OInl6CiePy
+itemsInParasPattern = ///
+ ^
+ (
+ #{escapePattern(complete)}|
+ #{escapePattern(incomplete)}
+ )
+ .+
+ $
+///g
+
+# Given the source text, updates the appropriate task list item to match the
+# given checked value.
+#
+# Returns the updated String text.
+updateTaskListItem = (source, itemIndex, checked) ->
+ clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
+ replace(itemsInParasPattern, '').split("\n")
+ index = 0
+ result = for line in source.split("\n")
+ if line in clean && line.match(itemPattern)
+ index += 1
+ if index == itemIndex
+ line =
+ if checked
+ line.replace(incompletePattern, complete)
+ else
+ line.replace(completePattern, incomplete)
+ line
+ result.join("\n")
+
+# Updates the $field value to reflect the state of $item.
+# Triggers the `tasklist:change` event before the value has changed, and fires
+# a `tasklist:changed` event once the value has changed.
+updateTaskList = ($item) ->
+ $container = $item.closest '.js-task-list-container'
+ $field = $container.find '.js-task-list-field'
+ index = 1 + $container.find('.task-list-item-checkbox').index($item)
+ checked = $item.prop 'checked'
+
+ event = $.Event 'tasklist:change'
+ $field.trigger event, [index, checked]
+
+ unless event.isDefaultPrevented()
+ $field.val updateTaskListItem($field.val(), index, checked)
+ $field.trigger 'change'
+ $field.trigger 'tasklist:changed', [index, checked]
+
+# When the task list item checkbox is updated, submit the change
+$(document).on 'change', '.task-list-item-checkbox', ->
+ updateTaskList $(this)
+
+# Enables TaskList item changes.
+enableTaskList = ($container) ->
+ if $container.find('.js-task-list-field').length > 0
+ $container.
+ find('.task-list-item').addClass('enabled').
+ find('.task-list-item-checkbox').attr('disabled', null)
+ $container.addClass('is-task-list-enabled').
+ trigger 'tasklist:enabled'
+
+# Enables a collection of TaskList containers.
+enableTaskLists = ($containers) ->
+ for container in $containers
+ enableTaskList $(container)
+
+# Disable TaskList item changes.
+disableTaskList = ($container) ->
+ $container.
+ find('.task-list-item').removeClass('enabled').
+ find('.task-list-item-checkbox').attr('disabled', 'disabled')
+ $container.removeClass('is-task-list-enabled').
+ trigger 'tasklist:disabled'
+
+# Disables a collection of TaskList containers.
+disableTaskLists = ($containers) ->
+ for container in $containers
+ disableTaskList $(container)
+
+$.fn.taskList = (method) ->
+ $container = $(this).closest('.js-task-list-container')
+
+ methods =
+ enable: enableTaskLists
+ disable: disableTaskLists
+
+ methods[method || 'enable']($container)