diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000000..42afed5437 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,47 @@ +--- +engines: + brakeman: + enabled: true + bundler-audit: + enabled: true + duplication: + enabled: true + config: + languages: + - ruby + - javascript + exclude_paths: + - "lib/api/v3/*" + eslint: + enabled: true + rubocop: + enabled: true +ratings: + paths: + - Gemfile.lock + - "**.erb" + - "**.haml" + - "**.rb" + - "**.rhtml" + - "**.slim" + - "**.inc" + - "**.js" + - "**.jsx" + - "**.module" +exclude_paths: +- config/ +- db/ +- features/ +- node_modules/ +- spec/ +- vendor/ +- .yarn-cache/ +- tmp/ +- builds/ +- coverage/ +- public/ +- shared/ +- webpack-report/ +- log/ +- backups/ +- coverage-javascript/ diff --git a/.eslintrc b/.eslintrc index aba8112c5a..3e07edbccf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,6 +11,7 @@ "gon": false, "localStorage": false }, + "parser": "babel-eslint", "plugins": [ "filenames", "import", @@ -27,7 +28,9 @@ }, "rules": { "filenames/match-regex": [2, "^[a-z0-9_]+$"], + "import/no-commonjs": "error", "no-multiple-empty-lines": ["error", { "max": 1 }], - "promise/catch-or-return": "error" + "promise/catch-or-return": "error", + "no-underscore-dangle": ["error", { "allow": ["__"]}] } } diff --git a/.flayignore b/.flayignore index 4759702511..b63ce4c4df 100644 --- a/.flayignore +++ b/.flayignore @@ -3,3 +3,5 @@ lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/diff/position_tracer.rb app/policies/project_policy.rb app/models/concerns/relative_positioning.rb +app/workers/stuck_merge_jobs_worker.rb +lib/gitlab/redis/*.rb diff --git a/.gitignore b/.gitignore index 0fb97ffb98..3baf640a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,10 @@ eslint-report.html .sass-cache/ /.secret /.vagrant +/.yarn-cache /.byebug_history /Vagrantfile +/app/assets/javascripts/locale/**/app.js /backups/* /config/aws.yml /config/database.yml @@ -29,6 +31,9 @@ eslint-report.html /config/initializers/smtp_settings.rb /config/initializers/relative_url.rb /config/resque.yml +/config/redis.cache.yml +/config/redis.queues.yml +/config/redis.shared_state.yml /config/unicorn.rb /config/secrets.yml /config/sidekiq.yml @@ -48,6 +53,7 @@ eslint-report.html /public/uploads/ /shared/artifacts/ /spec/javascripts/fixtures/blob/pdf/ +/spec/javascripts/fixtures/blob/balsamiq/ /rails_best_practices_output.html /tags /tmp/* @@ -57,3 +63,4 @@ eslint-report.html /.gitlab_workhorse_secret /webpack-report/ /locale/**/LC_MESSAGES +/.rspec diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86c459a9f6..95e833ff25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,20 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6" -cache: - key: "ruby-233" +.default-cache: &default-cache + key: "ruby-233-with-yarn" paths: - - vendor/ruby + - vendor/ruby + - .yarn-cache/ + +.push-cache: &push-cache + cache: + <<: *default-cache + policy: push + +.pull-cache: &pull-cache + cache: + <<: *default-cache + policy: pull variables: MYSQL_ALLOW_EMPTY_PASSWORD: "1" @@ -23,11 +34,11 @@ before_script: - source scripts/prepare_build.sh stages: -- build -- prepare -- test -- post-test -- pages + - build + - prepare + - test + - post-test + - pages # Predefined scopes .dedicated-runner: &dedicated-runner @@ -40,10 +51,6 @@ stages: SETUP_DB: "false" USE_BUNDLE_INSTALL: "false" KNAPSACK_S3_BUCKET: "gitlab-ce-cache" - cache: - key: "knapsack" - paths: - - knapsack/ artifacts: expire_in: 31d paths: @@ -59,34 +66,39 @@ stages: - mysql:latest - redis:alpine -.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql +.only-if-want-mysql: &only-if-want-mysql only: - /mysql/ + - /-stable/ - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee - master@gitlab/gitlabhq + - master@gitlab/gitlab-ee - tags@gitlab-org/gitlab-ce + - tags@gitlab-org/gitlab-ee - tags@gitlab/gitlabhq - - //@gitlab-org/gitlab-ee - - //@gitlab/gitlab-ee + - tags@gitlab/gitlab-ee # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. # https://docs.gitlab.com/ce/development/writing_documentation.html#testing .except-docs: &except-docs except: - - /^docs\/.*/ + - /(^docs[\/-].*|.*-docs$)/ .rspec-knapsack: &rspec-knapsack - stage: test <<: *dedicated-runner + <<: *pull-cache + stage: test script: - JOB_NAME=( $CI_JOB_NAME ) - export CI_NODE_INDEX=${JOB_NAME[-2]} - export CI_NODE_TOTAL=${JOB_NAME[-1]} - - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json + - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - export CACHE_CLASSES=true - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} + - scripts/gitaly-test-spawn - knapsack rspec "--color --format documentation" artifacts: expire_in: 31d @@ -104,17 +116,18 @@ stages: .rspec-knapsack-mysql: &rspec-knapsack-mysql <<: *rspec-knapsack <<: *use-mysql - <<: *only-master-and-ee-or-mysql + <<: *only-if-want-mysql <<: *except-docs .spinach-knapsack: &spinach-knapsack - stage: test <<: *dedicated-runner + <<: *pull-cache + stage: test script: - JOB_NAME=( $CI_JOB_NAME ) - export CI_NODE_INDEX=${JOB_NAME[-2]} - export CI_NODE_TOTAL=${JOB_NAME[-1]} - - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_${JOB_NAME[1]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json + - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - export CACHE_CLASSES=true - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} @@ -135,30 +148,33 @@ stages: .spinach-knapsack-mysql: &spinach-knapsack-mysql <<: *spinach-knapsack <<: *use-mysql - <<: *only-master-and-ee-or-mysql + <<: *only-if-want-mysql <<: *except-docs +.only-canonical-masters: &only-canonical-masters + only: + - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee + - master@gitlab/gitlabhq + - master@gitlab/gitlab-ee + # Trigger a package build on omnibus-gitlab repository build-package: + image: ruby:2.3-alpine + before_script: [] services: [] variables: SETUP_DB: "false" USE_BUNDLE_INSTALL: "false" stage: build + cache: {} when: manual script: - # If no branch in omnibus is specified, trigger pipeline against master - - if [ -z "$OMNIBUS_BRANCH" ] ; then export OMNIBUS_BRANCH=master ;fi - - echo "token=${BUILD_TRIGGER_TOKEN}" > version_details - - echo "ref=${OMNIBUS_BRANCH}" >> version_details - - echo "variables[ALTERNATIVE_SOURCES]=true" >> version_details - - echo "variables[GITLAB_VERSION]=${CI_COMMIT_SHA}" >> version_details - # Collect version details of all components - - for f in *_VERSION; do echo "variables[$f]=$(cat $f)" >> version_details; done - # Trigger the API and pass values collected above as parameters to it - - cat version_details | tr '\n' '&' | curl -X POST https://gitlab.com/api/v4/projects/20699/trigger/pipeline --data-binary @- - - rm version_details + - scripts/trigger-build + only: + - //@gitlab-org/gitlab-ce + - //@gitlab-org/gitlab-ee # Prepare and merge knapsack tests knapsack: @@ -166,6 +182,11 @@ knapsack: <<: *dedicated-runner <<: *except-docs stage: prepare + cache: + key: knapsack + paths: + - knapsack/ + policy: pull script: - mkdir -p knapsack/${CI_PROJECT_NAME}/ - wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH @@ -176,28 +197,34 @@ knapsack: update-knapsack: <<: *knapsack-state <<: *dedicated-runner + <<: *only-canonical-masters stage: post-test + cache: + key: knapsack + paths: + - knapsack/ + policy: push script: - - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_pg_node_*.json - - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach_pg_node_*.json + - retry gem install fog-aws mime-types + - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json + - scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json - '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH' - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json - only: - - master@gitlab-org/gitlab-ce - - master@gitlab-org/gitlab-ee - - master@gitlab/gitlabhq - - master@gitlab/gitlab-ee setup-test-env: <<: *use-pg <<: *dedicated-runner <<: *except-docs stage: prepare + cache: + <<: *default-cache script: - node --version - - yarn install --pure-lockfile + - yarn install --pure-lockfile --cache-folder .yarn-cache + - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' + - scripts/gitaly-test-build # Do not use 'bundle exec' here artifacts: expire_in: 7d paths: @@ -205,81 +232,82 @@ setup-test-env: - public/assets - tmp/tests -rspec pg 0 20: *rspec-knapsack-pg -rspec pg 1 20: *rspec-knapsack-pg -rspec pg 2 20: *rspec-knapsack-pg -rspec pg 3 20: *rspec-knapsack-pg -rspec pg 4 20: *rspec-knapsack-pg -rspec pg 5 20: *rspec-knapsack-pg -rspec pg 6 20: *rspec-knapsack-pg -rspec pg 7 20: *rspec-knapsack-pg -rspec pg 8 20: *rspec-knapsack-pg -rspec pg 9 20: *rspec-knapsack-pg -rspec pg 10 20: *rspec-knapsack-pg -rspec pg 11 20: *rspec-knapsack-pg -rspec pg 12 20: *rspec-knapsack-pg -rspec pg 13 20: *rspec-knapsack-pg -rspec pg 14 20: *rspec-knapsack-pg -rspec pg 15 20: *rspec-knapsack-pg -rspec pg 16 20: *rspec-knapsack-pg -rspec pg 17 20: *rspec-knapsack-pg -rspec pg 18 20: *rspec-knapsack-pg -rspec pg 19 20: *rspec-knapsack-pg +rspec-pg 0 25: *rspec-knapsack-pg +rspec-pg 1 25: *rspec-knapsack-pg +rspec-pg 2 25: *rspec-knapsack-pg +rspec-pg 3 25: *rspec-knapsack-pg +rspec-pg 4 25: *rspec-knapsack-pg +rspec-pg 5 25: *rspec-knapsack-pg +rspec-pg 6 25: *rspec-knapsack-pg +rspec-pg 7 25: *rspec-knapsack-pg +rspec-pg 8 25: *rspec-knapsack-pg +rspec-pg 9 25: *rspec-knapsack-pg +rspec-pg 10 25: *rspec-knapsack-pg +rspec-pg 11 25: *rspec-knapsack-pg +rspec-pg 12 25: *rspec-knapsack-pg +rspec-pg 13 25: *rspec-knapsack-pg +rspec-pg 14 25: *rspec-knapsack-pg +rspec-pg 15 25: *rspec-knapsack-pg +rspec-pg 16 25: *rspec-knapsack-pg +rspec-pg 17 25: *rspec-knapsack-pg +rspec-pg 18 25: *rspec-knapsack-pg +rspec-pg 19 25: *rspec-knapsack-pg +rspec-pg 20 25: *rspec-knapsack-pg +rspec-pg 21 25: *rspec-knapsack-pg +rspec-pg 22 25: *rspec-knapsack-pg +rspec-pg 23 25: *rspec-knapsack-pg +rspec-pg 24 25: *rspec-knapsack-pg -rspec mysql 0 20: *rspec-knapsack-mysql -rspec mysql 1 20: *rspec-knapsack-mysql -rspec mysql 2 20: *rspec-knapsack-mysql -rspec mysql 3 20: *rspec-knapsack-mysql -rspec mysql 4 20: *rspec-knapsack-mysql -rspec mysql 5 20: *rspec-knapsack-mysql -rspec mysql 6 20: *rspec-knapsack-mysql -rspec mysql 7 20: *rspec-knapsack-mysql -rspec mysql 8 20: *rspec-knapsack-mysql -rspec mysql 9 20: *rspec-knapsack-mysql -rspec mysql 10 20: *rspec-knapsack-mysql -rspec mysql 11 20: *rspec-knapsack-mysql -rspec mysql 12 20: *rspec-knapsack-mysql -rspec mysql 13 20: *rspec-knapsack-mysql -rspec mysql 14 20: *rspec-knapsack-mysql -rspec mysql 15 20: *rspec-knapsack-mysql -rspec mysql 16 20: *rspec-knapsack-mysql -rspec mysql 17 20: *rspec-knapsack-mysql -rspec mysql 18 20: *rspec-knapsack-mysql -rspec mysql 19 20: *rspec-knapsack-mysql +rspec-mysql 0 25: *rspec-knapsack-mysql +rspec-mysql 1 25: *rspec-knapsack-mysql +rspec-mysql 2 25: *rspec-knapsack-mysql +rspec-mysql 3 25: *rspec-knapsack-mysql +rspec-mysql 4 25: *rspec-knapsack-mysql +rspec-mysql 5 25: *rspec-knapsack-mysql +rspec-mysql 6 25: *rspec-knapsack-mysql +rspec-mysql 7 25: *rspec-knapsack-mysql +rspec-mysql 8 25: *rspec-knapsack-mysql +rspec-mysql 9 25: *rspec-knapsack-mysql +rspec-mysql 10 25: *rspec-knapsack-mysql +rspec-mysql 11 25: *rspec-knapsack-mysql +rspec-mysql 12 25: *rspec-knapsack-mysql +rspec-mysql 13 25: *rspec-knapsack-mysql +rspec-mysql 14 25: *rspec-knapsack-mysql +rspec-mysql 15 25: *rspec-knapsack-mysql +rspec-mysql 16 25: *rspec-knapsack-mysql +rspec-mysql 17 25: *rspec-knapsack-mysql +rspec-mysql 18 25: *rspec-knapsack-mysql +rspec-mysql 19 25: *rspec-knapsack-mysql +rspec-mysql 20 25: *rspec-knapsack-mysql +rspec-mysql 21 25: *rspec-knapsack-mysql +rspec-mysql 22 25: *rspec-knapsack-mysql +rspec-mysql 23 25: *rspec-knapsack-mysql +rspec-mysql 24 25: *rspec-knapsack-mysql -spinach pg 0 10: *spinach-knapsack-pg -spinach pg 1 10: *spinach-knapsack-pg -spinach pg 2 10: *spinach-knapsack-pg -spinach pg 3 10: *spinach-knapsack-pg -spinach pg 4 10: *spinach-knapsack-pg -spinach pg 5 10: *spinach-knapsack-pg -spinach pg 6 10: *spinach-knapsack-pg -spinach pg 7 10: *spinach-knapsack-pg -spinach pg 8 10: *spinach-knapsack-pg -spinach pg 9 10: *spinach-knapsack-pg +spinach-pg 0 5: *spinach-knapsack-pg +spinach-pg 1 5: *spinach-knapsack-pg +spinach-pg 2 5: *spinach-knapsack-pg +spinach-pg 3 5: *spinach-knapsack-pg +spinach-pg 4 5: *spinach-knapsack-pg -spinach mysql 0 10: *spinach-knapsack-mysql -spinach mysql 1 10: *spinach-knapsack-mysql -spinach mysql 2 10: *spinach-knapsack-mysql -spinach mysql 3 10: *spinach-knapsack-mysql -spinach mysql 4 10: *spinach-knapsack-mysql -spinach mysql 5 10: *spinach-knapsack-mysql -spinach mysql 6 10: *spinach-knapsack-mysql -spinach mysql 7 10: *spinach-knapsack-mysql -spinach mysql 8 10: *spinach-knapsack-mysql -spinach mysql 9 10: *spinach-knapsack-mysql +spinach-mysql 0 5: *spinach-knapsack-mysql +spinach-mysql 1 5: *spinach-knapsack-mysql +spinach-mysql 2 5: *spinach-knapsack-mysql +spinach-mysql 3 5: *spinach-knapsack-mysql +spinach-mysql 4 5: *spinach-knapsack-mysql -# Other generic tests +# Static analysis jobs .ruby-static-analysis: &ruby-static-analysis + <<: *pull-cache variables: SIMPLECOV: "false" SETUP_DB: "false" - USE_BUNDLE_INSTALL: "true" .rake-exec: &rake-exec <<: *ruby-static-analysis <<: *dedicated-runner <<: *except-docs + <<: *pull-cache stage: test script: - bundle exec rake $CI_JOB_NAME @@ -290,17 +318,15 @@ static-analysis: <<: *except-docs stage: test script: - - rm -r node_modules - - yarn install --pure-lockfile - scripts/static-analysis # Documentation checks: # - Check validity of relative links # - Make sure cURL examples in API docs use the full switches docs lint: + <<: *dedicated-runner image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine" stage: test - <<: *dedicated-runner cache: {} dependencies: [] before_script: [] @@ -319,7 +345,7 @@ downtime_check: - master - tags - /^[\d-]+-stable(-ee)?$/ - - /^docs\/*/ + - /(^docs[\/-].*|.*-docs$)/ ee_compat_check: <<: *rake-exec @@ -328,7 +354,7 @@ ee_compat_check: except: - master - tags - - /^[\d-]+-stable(-ee)?$/ + - /^[\d-]+-stable(-ee)?/ allow_failure: yes cache: key: "ee_compat_check_repo" @@ -341,67 +367,96 @@ ee_compat_check: paths: - ee_compat_check/patches/*.patch +# DB migration, rollback, and seed jobs .db-migrate-reset: &db-migrate-reset - stage: test <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + stage: test script: - bundle exec rake db:migrate:reset -rake pg db:migrate:reset: +db:migrate:reset-pg: <<: *db-migrate-reset <<: *use-pg -rake mysql db:migrate:reset: +db:migrate:reset-mysql: <<: *db-migrate-reset <<: *use-mysql -.db-rollback: &db-rollback +.migration-paths: &migration-paths + <<: *dedicated-runner + <<: *only-canonical-masters + <<: *pull-cache stage: test + variables: + SETUP_DB: "false" + script: + - git fetch origin v8.14.10 + - git checkout -f FETCH_HEAD + - bundle install $BUNDLE_INSTALL_FLAGS + - cp config/gitlab.yml.example config/gitlab.yml + - bundle exec rake db:drop db:create db:schema:load db:seed_fu + - git checkout $CI_COMMIT_SHA + - bundle install $BUNDLE_INSTALL_FLAGS + - . scripts/prepare_build.sh + - bundle exec rake db:migrate + +migration:path-pg: + <<: *migration-paths + <<: *use-pg + +migration:path-mysql: + <<: *migration-paths + <<: *use-mysql + +.db-rollback: &db-rollback <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + stage: test script: - bundle exec rake db:rollback STEP=120 - bundle exec rake db:migrate -rake pg db:rollback: +db:rollback-pg: <<: *db-rollback <<: *use-pg -rake mysql db:rollback: +db:rollback-mysql: <<: *db-rollback <<: *use-mysql .db-seed_fu: &db-seed_fu - stage: test <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + stage: test variables: SIZE: "1" - SETUP_DB: "false" - RAILS_ENV: "development" script: - - git clone https://gitlab.com/gitlab-org/gitlab-test.git - /home/git/repositories/gitlab-org/gitlab-test.git - - bundle exec rake db:setup db:seed_fu + - cp -R db/fixtures/development db/fixtures/test + - bundle exec rake db:seed_fu artifacts: when: on_failure expire_in: 1d paths: - log/development.log -rake pg db:seed_fu: +db:seed_fu-pg: <<: *db-seed_fu <<: *use-pg -rake mysql db:seed_fu: +db:seed_fu-mysql: <<: *db-seed_fu <<: *use-mysql -rake gitlab:assets:compile: - stage: test +# Frontend-related jobs +gitlab:assets:compile: <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + stage: test dependencies: [] variables: NODE_ENV: "production" @@ -410,65 +465,63 @@ rake gitlab:assets:compile: USE_DB: "false" SKIP_STORAGE_VALIDATION: "true" WEBPACK_REPORT: "true" + NO_COMPRESSION: "true" script: - - bundle exec rake yarn:install gitlab:assets:compile + - yarn install --pure-lockfile --production --cache-folder .yarn-cache + - bundle exec rake gettext:po_to_json + - bundle exec rake gitlab:assets:compile artifacts: name: webpack-report expire_in: 31d paths: - - webpack-report/ + - webpack-report/ -rake karma: - stage: test +karma: <<: *use-pg <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6" + stage: test variables: BABEL_ENV: "coverage" + CHROME_LOG_FILE: "chrome_debug.log" script: - - rm -r node_modules - - yarn install --pure-lockfile + - scripts/gitaly-test-spawn + - bundle exec rake gettext:po_to_json - bundle exec rake karma coverage: '/^Statements *: (\d+\.\d+%)/' artifacts: name: coverage-javascript expire_in: 31d + when: always paths: + - chrome_debug.log - coverage-javascript/ -.migration-paths: &migration-paths +codeclimate: + <<: *except-docs + <<: *pull-cache + before_script: [] + image: docker:latest stage: test - <<: *dedicated-runner variables: SETUP_DB: "false" - only: - - master@gitlab-org/gitlab-ce - - master@gitlab-org/gitlab-ee - - master@gitlab/gitlabhq - - master@gitlab/gitlab-ee + DOCKER_DRIVER: overlay + services: + - docker:dind script: - - git fetch origin v8.14.10 - - git checkout -f FETCH_HEAD - - bundle install $BUNDLE_INSTALL_FLAGS - - bundle exec rake db:drop db:create db:schema:load db:seed_fu - - git checkout $CI_COMMIT_SHA - - bundle install $BUNDLE_INSTALL_FLAGS - - . scripts/prepare_build.sh - - bundle exec rake db:migrate - -migration pg paths: - <<: *migration-paths - <<: *use-pg - -migration mysql paths: - <<: *migration-paths - <<: *use-mysql + - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json + - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json + artifacts: + paths: [codeclimate.json] coverage: - stage: post-test - services: [] <<: *dedicated-runner <<: *except-docs + <<: *pull-cache + stage: post-test + services: [] variables: SETUP_DB: "false" USE_BUNDLE_INSTALL: "true" @@ -485,7 +538,10 @@ coverage: lint:javascript:report: <<: *dedicated-runner <<: *except-docs + <<: *pull-cache stage: post-test + dependencies: + - setup-test-env before_script: [] script: - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files @@ -496,33 +552,15 @@ lint:javascript:report: paths: - eslint-report.html -# Trigger docs build -# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process -trigger_docs: - stage: post-test - image: "alpine" - <<: *dedicated-runner - before_script: - - apk update && apk add curl - variables: - GIT_STRATEGY: "none" - cache: {} - artifacts: {} - script: - - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)" - - if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi - only: - - master@gitlab-org/gitlab-ce - - master@gitlab-org/gitlab-ee - pages: + <<: *dedicated-runner + <<: *pull-cache before_script: [] stage: pages - <<: *dedicated-runner dependencies: - coverage - - rake karma - - rake gitlab:assets:compile + - karma + - gitlab:assets:compile - lint:javascript:report script: - mv public/ .public/ @@ -542,6 +580,7 @@ pages: # rubygems.org in the future. cache gems: <<: *dedicated-runner + <<: *pull-cache only: - tags variables: @@ -554,3 +593,11 @@ cache gems: only: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee + +gitlab_git_test: + <<: *pull-cache + <<: *except-docs + variables: + SETUP_DB: "false" + script: + - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 66e1e0e20b..aec734870d 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -1,11 +1,18 @@ Please read this! Before opening a new issue, make sure to search for keywords in the issues -filtered by the "regression" or "bug" label: +filtered by the "regression" or "bug" label. + +For the Community Edition issue tracker: - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug +For the Enterprise Edition issue tracker: + +- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=regression +- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug + and verify the issue you're about to submit isn't a duplicate. Please remove this notice if you're confident your issue isn't a duplicate. @@ -20,6 +27,12 @@ Please remove this notice if you're confident your issue isn't a duplicate. (How one can reproduce the issue - this is very important) +### Example Project + +(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report) + +(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version) + ### What is the current *bug* behavior? (What actually happens) @@ -40,6 +53,7 @@ logs, and code as it's very hard to read otherwise.) #### Results of GitLab environment info
+Expand for output related to GitLab environment info
 
 (For installations with omnibus-gitlab package run and paste the output of:
@@ -54,6 +68,7 @@ logs, and code as it's very hard to read otherwise.)
 #### Results of GitLab application Check
 
 
+Expand for output related to the GitLab application check
 
 (For installations with omnibus-gitlab package run and paste the output of:
diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md
index d96c9ad59e..1278061a41 100644
--- a/.gitlab/issue_templates/Feature Proposal.md	
+++ b/.gitlab/issue_templates/Feature Proposal.md	
@@ -3,8 +3,14 @@ Please read this!
 Before opening a new issue, make sure to search for keywords in the issues
 filtered by the "feature proposal" label:
 
+For the Community Edition issue tracker:
+
 - https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
 
+For the Enterprise Edition issue tracker:
+
+- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
+
 and verify the issue you're about to submit isn't a duplicate.
 
 Please remove this notice if you're confident your issue isn't a duplicate.
@@ -21,12 +27,24 @@ Please remove this notice if you're confident your issue isn't a duplicate.
 
 ### Documentation blurb
 
-(Write the start of the documentation of this feature here, include:
+#### Overview
 
-1. Why should someone use it; what's the underlying problem.
-2. What is the solution.
-3. How does someone use this
+What is it?
+Why should someone use this feature?
+What is the underlying (business) problem?
+How do you use this feature?
 
-During implementation, this can then be copied and used as a starter for the documentation.)
+#### Use cases
 
-/label ~"feature proposal"
+Who is this for? Provide one or more use cases.
+
+### Feature checklist
+
+Make sure these are completed before closing the issue,
+with a link to the relevant commit.
+
+- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
+- [ ] Documentation
+- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
+
+/label ~"feature proposal"
\ No newline at end of file
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 528f99d08d..32c7de0fb7 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -35,9 +35,21 @@ linters:
   HtmlAttributes:
     enabled: true
 
+  IdNames:
+    enabled: false
+
   ImplicitDiv:
     enabled: true
 
+  InlineJavaScript:
+    enabled: true
+
+  InlineStyles:
+    enabled: false
+
+  InstanceVariables:
+    enabled: false
+
   LeadingCommentSpace:
     enabled: false
 
@@ -54,6 +66,9 @@ linters:
   ObjectReferenceAttributes:
     enabled: true
 
+  RepeatedId:
+    enabled: false
+
   RuboCop:
     enabled: false
     # These cops are incredibly noisy when it comes to HAML templates, so we
@@ -101,3 +116,6 @@ linters:
 
   UnnecessaryStringOutput:
     enabled: true
+
+  ViewLength:
+    enabled: false
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000000..72906051c5
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+7.5
\ No newline at end of file
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 35f4d7441e..0000000000
--- a/.rspec
+++ /dev/null
@@ -1,2 +0,0 @@
---color
---format Fuubar
diff --git a/.rubocop.yml b/.rubocop.yml
index e53af97a92..a5ccec0437 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -6,6 +6,7 @@ inherit_from: .rubocop_todo.yml
 
 AllCops:
   TargetRubyVersion: 2.3
+  TargetRailsVersion: 4.2
   # Cop names are not d§splayed in offense messages by default. Change behavior
   # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
   # option.
@@ -29,12 +30,221 @@ AllCops:
 Bundler/OrderedGems:
   Enabled: false
 
-# Style #######################################################################
+# Layout ######################################################################
 
 # Check indentation of private/protected visibility modifiers.
-Style/AccessModifierIndentation:
+Layout/AccessModifierIndentation:
   Enabled: true
 
+# Align the elements of an array literal if they span more than one line.
+Layout/AlignArray:
+  Enabled: true
+
+# Align the elements of a hash literal if they span more than one line.
+Layout/AlignHash:
+  Enabled: true
+
+# Here we check if the parameters on a multi-line method call or
+# definition are aligned.
+Layout/AlignParameters:
+  Enabled: false
+
+# Put end statement of multiline block on its own line.
+Layout/BlockEndNewline:
+  Enabled: true
+
+# Indentation of when in a case/when/[else/]end.
+Layout/CaseIndentation:
+  Enabled: true
+
+# Indentation of comments.
+Layout/CommentIndentation:
+  Enabled: true
+
+# Multi-line method chaining should be done with leading dots.
+Layout/DotPosition:
+  Enabled: true
+  EnforcedStyle: leading
+
+# Align elses and elsifs correctly.
+Layout/ElseAlignment:
+  Enabled: true
+
+# Add an empty line after magic comments to separate them from code.
+Layout/EmptyLineAfterMagicComment:
+  Enabled: false
+
+# Use empty lines between defs.
+Layout/EmptyLineBetweenDefs:
+  Enabled: true
+
+# Don't use several empty lines in a row.
+Layout/EmptyLines:
+  Enabled: true
+
+# Keep blank lines around access modifiers.
+Layout/EmptyLinesAroundAccessModifier:
+  Enabled: true
+
+# Keeps track of empty lines around block bodies.
+Layout/EmptyLinesAroundBlockBody:
+  Enabled: true
+
+# Keeps track of empty lines around class bodies.
+Layout/EmptyLinesAroundClassBody:
+  Enabled: true
+
+# Keeps track of empty lines around exception handling keywords.
+Layout/EmptyLinesAroundExceptionHandlingKeywords:
+  Enabled: false
+
+# Keeps track of empty lines around method bodies.
+Layout/EmptyLinesAroundMethodBody:
+  Enabled: true
+
+# Keeps track of empty lines around module bodies.
+Layout/EmptyLinesAroundModuleBody:
+  Enabled: true
+
+# Use Unix-style line endings.
+Layout/EndOfLine:
+  Enabled: true
+
+# Checks for a line break before the first parameter in a multi-line method
+# parameter definition.
+Layout/FirstMethodParameterLineBreak:
+  Enabled: true
+
+# Keep indentation straight.
+Layout/IndentationConsistency:
+  Enabled: true
+
+# Use 2 spaces for indentation.
+Layout/IndentationWidth:
+  Enabled: true
+
+# Checks the indentation of the first line of the right-hand-side of a
+# multi-line assignment.
+Layout/IndentAssignment:
+  Enabled: true
+
+# This cops checks the indentation of the here document bodies.
+Layout/IndentHeredoc:
+  Enabled: false
+
+# Comments should start with a space.
+Layout/LeadingCommentSpace:
+  Enabled: true
+
+# Checks that the closing brace in an array literal is either on the same line
+# as the last array element, or a new line.
+Layout/MultilineArrayBraceLayout:
+  Enabled: true
+  EnforcedStyle: symmetrical
+
+# Ensures newlines after multiline block do statements.
+Layout/MultilineBlockLayout:
+  Enabled: true
+
+# Checks that the closing brace in a hash literal is either on the same line as
+# the last hash element, or a new line.
+Layout/MultilineHashBraceLayout:
+  Enabled: true
+  EnforcedStyle: symmetrical
+
+# Checks that the closing brace in a method call is either on the same line as
+# the last method argument, or a new line.
+Layout/MultilineMethodCallBraceLayout:
+  Enabled: false
+  EnforcedStyle: symmetrical
+
+# Checks indentation of method calls with the dot operator that span more than
+# one line.
+Layout/MultilineMethodCallIndentation:
+  Enabled: false
+
+# Checks that the closing brace in a method definition is symmetrical with
+# respect to the opening brace and the method parameters.
+Layout/MultilineMethodDefinitionBraceLayout:
+  Enabled: false
+
+# Checks indentation of binary operations that span more than one line.
+Layout/MultilineOperationIndentation:
+  Enabled: true
+  EnforcedStyle: indented
+
+# Use spaces after colons.
+Layout/SpaceAfterColon:
+  Enabled: true
+
+# Use spaces after commas.
+Layout/SpaceAfterComma:
+  Enabled: true
+
+# Do not put a space between a method name and the opening parenthesis in a
+# method definition.
+Layout/SpaceAfterMethodName:
+  Enabled: true
+
+# Tracks redundant space after the ! operator.
+Layout/SpaceAfterNot:
+  Enabled: true
+
+# Use spaces after semicolons.
+Layout/SpaceAfterSemicolon:
+  Enabled: true
+
+# Use space around equals in parameter default
+Layout/SpaceAroundEqualsInParameterDefault:
+  Enabled: true
+
+# Use a space around keywords if appropriate.
+Layout/SpaceAroundKeyword:
+  Enabled: true
+
+# Use a single space around operators.
+Layout/SpaceAroundOperators:
+  Enabled: true
+
+# No spaces before commas.
+Layout/SpaceBeforeComma:
+  Enabled: true
+
+# Checks for missing space between code and a comment on the same line.
+Layout/SpaceBeforeComment:
+  Enabled: true
+
+# No spaces before semicolons.
+Layout/SpaceBeforeSemicolon:
+  Enabled: true
+
+# Checks for spaces inside square brackets.
+Layout/SpaceInsideBrackets:
+  Enabled: true
+
+# Use spaces inside hash literal braces - or don't.
+Layout/SpaceInsideHashLiteralBraces:
+  Enabled: true
+
+# No spaces inside range literals.
+Layout/SpaceInsideRangeLiteral:
+  Enabled: true
+
+# Checks for padding/surrounding spaces inside string interpolation.
+Layout/SpaceInsideStringInterpolation:
+  EnforcedStyle: no_space
+  Enabled: true
+
+# No hard tabs.
+Layout/Tab:
+  Enabled: true
+
+# Checks trailing blank lines and final newline.
+Layout/TrailingBlankLines:
+  Enabled: true
+
+# Style #######################################################################
+
 # Check the naming of accessor methods for get_/set_.
 Style/AccessorMethodName:
   Enabled: false
@@ -44,19 +254,6 @@ Style/Alias:
   EnforcedStyle: prefer_alias_method
   Enabled: true
 
-# Align the elements of an array literal if they span more than one line.
-Style/AlignArray:
-  Enabled: true
-
-# Align the elements of a hash literal if they span more than one line.
-Style/AlignHash:
-  Enabled: true
-
-# Here we check if the parameters on a multi-line method call or
-# definition are aligned.
-Style/AlignParameters:
-  Enabled: false
-
 # Whether `and` and `or` are banned only in conditionals (conditionals)
 # or completely (always).
 Style/AndOr:
@@ -91,10 +288,6 @@ Style/BlockComments:
 Style/BlockDelimiters:
   Enabled: true
 
-# Put end statement of multiline block on its own line.
-Style/BlockEndNewline:
-  Enabled: true
-
  # This cop checks for braces around the last parameter in a method call
 # if the last parameter is a hash.
 Style/BracesAroundHashParameters:
@@ -104,10 +297,6 @@ Style/BracesAroundHashParameters:
 Style/CaseEquality:
   Enabled: false
 
-# Indentation of when in a case/when/[else/]end.
-Style/CaseIndentation:
-  Enabled: true
-
 # Checks for uses of character literals.
 Style/CharacterLiteral:
   Enabled: true
@@ -142,10 +331,6 @@ Style/ColonMethodCall:
 Style/CommentAnnotation:
   Enabled: false
 
-# Indentation of comments.
-Style/CommentIndentation:
-  Enabled: true
-
 # Check for `if` and `case` statements where each branch is used for
 # assignment to the same variable when using the return of the
 # condition can be used instead.
@@ -170,46 +355,10 @@ Style/Documentation:
 Style/DoubleNegation:
   Enabled: false
 
-# Align elses and elsifs correctly.
-Style/ElseAlignment:
-  Enabled: true
-
-# Use empty lines between defs.
-Style/EmptyLineBetweenDefs:
-  Enabled: true
-
-# Don't use several empty lines in a row.
-Style/EmptyLines:
-  Enabled: true
-
-# Keep blank lines around access modifiers.
-Style/EmptyLinesAroundAccessModifier:
-  Enabled: true
-
-# Keeps track of empty lines around block bodies.
-Style/EmptyLinesAroundBlockBody:
-  Enabled: true
-
-# Keeps track of empty lines around class bodies.
-Style/EmptyLinesAroundClassBody:
-  Enabled: true
-
-# Keeps track of empty lines around method bodies.
-Style/EmptyLinesAroundMethodBody:
-  Enabled: true
-
-# Keeps track of empty lines around module bodies.
-Style/EmptyLinesAroundModuleBody:
-  Enabled: true
-
 # Avoid the use of END blocks.
 Style/EndBlock:
   Enabled: true
 
-# Use Unix-style line endings.
-Style/EndOfLine:
-  Enabled: true
-
 # Favor the use of Fixnum#even? && Fixnum#odd?
 Style/EvenOdd:
   Enabled: true
@@ -218,11 +367,6 @@ Style/EvenOdd:
 Style/FileName:
   Enabled: true
 
-# Checks for a line break before the first parameter in a multi-line method
-# parameter definition.
-Style/FirstMethodParameterLineBreak:
-  Enabled: true
-
 # Checks for flip flops.
 Style/FlipFlop:
   Enabled: true
@@ -231,6 +375,10 @@ Style/FlipFlop:
 Style/For:
   Enabled: true
 
+# Use a consistent style for format string tokens.
+Style/FormatStringToken:
+  Enabled: false
+
 # Checks if there is a magic comment to enforce string literals
 Style/FrozenStringLiteralComment:
   Enabled: false
@@ -256,31 +404,19 @@ Style/IdenticalConditionalBranches:
 Style/IfWithSemicolon:
   Enabled: true
 
-# Checks the indentation of the first line of the right-hand-side of a
-# multi-line assignment.
-Style/IndentAssignment:
-  Enabled: true
-
-# Keep indentation straight.
-Style/IndentationConsistency:
-  Enabled: true
-
-# Use 2 spaces for indentation.
-Style/IndentationWidth:
-  Enabled: true
-
 # Use Kernel#loop for infinite loops.
 Style/InfiniteLoop:
   Enabled: true
 
+# Use the inverse method instead of `!.method`
+# if an inverse method is defined.
+Style/InverseMethods:
+  Enabled: false
+
 # Use lambda.call(...) instead of lambda.(...).
 Style/LambdaCall:
   Enabled: true
 
-# Comments should start with a space.
-Style/LeadingCommentSpace:
-  Enabled: true
-
 # Checks if the method definitions have or don't have parentheses.
 Style/MethodDefParentheses:
   Enabled: true
@@ -293,55 +429,23 @@ Style/MethodName:
 Style/ModuleFunction:
   Enabled: false
 
-# Checks that the closing brace in an array literal is either on the same line
-# as the last array element, or a new line.
-Style/MultilineArrayBraceLayout:
-  Enabled: true
-  EnforcedStyle: symmetrical
-
 # Avoid multi-line chains of blocks.
 Style/MultilineBlockChain:
   Enabled: true
 
-# Ensures newlines after multiline block do statements.
-Style/MultilineBlockLayout:
-  Enabled: true
-
-# Checks that the closing brace in a hash literal is either on the same line as
-# the last hash element, or a new line.
-Style/MultilineHashBraceLayout:
-  Enabled: true
-  EnforcedStyle: symmetrical
-
 # Do not use then for multi-line if/unless.
 Style/MultilineIfThen:
   Enabled: true
 
-# Checks that the closing brace in a method call is either on the same line as
-# the last method argument, or a new line.
-Style/MultilineMethodCallBraceLayout:
-  Enabled: false
-  EnforcedStyle: symmetrical
-
-# Checks indentation of method calls with the dot operator that span more than
-# one line.
-Style/MultilineMethodCallIndentation:
-  Enabled: false
-
-# Checks that the closing brace in a method definition is symmetrical with
-# respect to the opening brace and the method parameters.
-Style/MultilineMethodDefinitionBraceLayout:
-  Enabled: false
-
-# Checks indentation of binary operations that span more than one line.
-Style/MultilineOperationIndentation:
-  Enabled: true
-  EnforcedStyle: indented
-
 # Avoid multi-line `? :` (the ternary operator), use if/unless instead.
 Style/MultilineTernaryOperator:
   Enabled: true
 
+# Avoid comparing a variable with multiple items in a conditional,
+# use Array#include? instead.
+Style/MultipleComparison:
+  Enabled: false
+
 # This cop checks whether some constant value isn't a
 # mutable literal (e.g. array or hash).
 Style/MutableConstant:
@@ -390,6 +494,15 @@ Style/OpMethod:
 Style/ParenthesesAroundCondition:
   Enabled: true
 
+# This cop (by default) checks for uses of methods Hash#has_key? and
+# Hash#has_value? where it enforces Hash#key? and Hash#value?
+# It is configurable to enforce the inverse, using `verbose` method
+# names also.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: short, verbose
+Style/PreferredHashMethods:
+  Enabled: false
+
 # Checks for an obsolete RuntimeException argument in raise/fail.
 Style/RedundantException:
   Enabled: true
@@ -407,68 +520,6 @@ Style/SignalException:
   EnforcedStyle: only_raise
   Enabled: true
 
-# Use spaces after colons.
-Style/SpaceAfterColon:
-  Enabled: true
-
-# Use spaces after commas.
-Style/SpaceAfterComma:
-  Enabled: true
-
-# Do not put a space between a method name and the opening parenthesis in a
-# method definition.
-Style/SpaceAfterMethodName:
-  Enabled: true
-
-# Tracks redundant space after the ! operator.
-Style/SpaceAfterNot:
-  Enabled: true
-
-# Use spaces after semicolons.
-Style/SpaceAfterSemicolon:
-  Enabled: true
-
-# Use space around equals in parameter default
-Style/SpaceAroundEqualsInParameterDefault:
-  Enabled: true
-
-# Use a space around keywords if appropriate.
-Style/SpaceAroundKeyword:
-  Enabled: true
-
-# Use a single space around operators.
-Style/SpaceAroundOperators:
-  Enabled: true
-
-# No spaces before commas.
-Style/SpaceBeforeComma:
-  Enabled: true
-
-# Checks for missing space between code and a comment on the same line.
-Style/SpaceBeforeComment:
-  Enabled: true
-
-# No spaces before semicolons.
-Style/SpaceBeforeSemicolon:
-  Enabled: true
-
-# Checks for spaces inside square brackets.
-Style/SpaceInsideBrackets:
-  Enabled: true
-
-# Use spaces inside hash literal braces - or don't.
-Style/SpaceInsideHashLiteralBraces:
-  Enabled: true
-
-# No spaces inside range literals.
-Style/SpaceInsideRangeLiteral:
-  Enabled: true
-
-# Checks for padding/surrounding spaces inside string interpolation.
-Style/SpaceInsideStringInterpolation:
-  EnforcedStyle: no_space
-  Enabled: true
-
 # Check for the usage of parentheses around stabby lambda arguments.
 Style/StabbyLambdaParentheses:
   EnforcedStyle: require_parentheses
@@ -484,17 +535,19 @@ Style/StringMethods:
     intern: to_sym
   Enabled: true
 
-# No hard tabs.
-Style/Tab:
-  Enabled: true
-
-# Checks trailing blank lines and final newline.
-Style/TrailingBlankLines:
-  Enabled: true
+# Use %i or %I for arrays of symbols.
+Style/SymbolArray:
+  Enabled: false
 
 # This cop checks for trailing comma in array and hash literals.
 Style/TrailingCommaInLiteral:
-  Enabled: false
+  Enabled: true
+  EnforcedStyleForMultiline: no_comma
+
+# This cop checks for trailing comma in argument lists.
+Style/TrailingCommaInArguments:
+  Enabled: true
+  EnforcedStyleForMultiline: no_comma
 
 # Checks for %W when interpolation is not needed.
 Style/UnneededCapitalW:
@@ -533,6 +586,10 @@ Style/WhileUntilModifier:
 Style/WordArray:
   Enabled: true
 
+# Do not use literals as the first operand of a comparison.
+Style/YodaCondition:
+  Enabled: false
+
 # Use `proc` instead of `Proc.new`.
 Style/Proc:
   Enabled: true
@@ -543,7 +600,7 @@ Style/Proc:
 # branches, and conditions.
 Metrics/AbcSize:
   Enabled: true
-  Max: 57.08
+  Max: 56.96
 
 # This cop checks if the length of a block exceeds some maximum value.
 Metrics/BlockLength:
@@ -588,6 +645,11 @@ Metrics/PerceivedComplexity:
 
 # Lint ########################################################################
 
+# Checks for ambiguous block association with method when param passed without
+# parentheses.
+Lint/AmbiguousBlockAssociation:
+  Enabled: false
+
 # Checks for ambiguous operators in the first argument of a method invocation
 # without parentheses.
 Lint/AmbiguousOperator:
@@ -789,6 +851,10 @@ Lint/Void:
 
 # Performance #################################################################
 
+# Use `caller(n..n)` instead of `caller`.
+Performance/Caller:
+  Enabled: false
+
 # Use `casecmp` rather than `downcase ==`.
 Performance/Casecmp:
   Enabled: true
@@ -863,14 +929,23 @@ Rails/ActionFilter:
   Enabled: true
   EnforcedStyle: action
 
+# Check that models subclass ApplicationRecord.
+Rails/ApplicationRecord:
+  Enabled: false
+
+# Enforce using `blank?` and `present?`.
+Rails/Blank:
+  Enabled: false
+
 # Checks the correct usage of date aware methods, such as `Date.today`,
 # `Date.current`, etc.
 Rails/Date:
   Enabled: false
 
 # Prefer delegate method for delegations.
+# Disabled per https://gitlab.com/gitlab-org/gitlab-ce/issues/35869
 Rails/Delegate:
-  Enabled: true
+  Enabled: false
 
 # This cop checks dynamic `find_by_*` methods.
 Rails/DynamicFindBy:
@@ -919,10 +994,18 @@ Rails/OutputSafety:
 Rails/PluralizationGrammar:
   Enabled: true
 
+# Enforce using `blank?` and `present?`.
+Rails/Present:
+  Enabled: false
+
 # Checks for `read_attribute(:attr)` and `write_attribute(:attr, val)`.
 Rails/ReadWriteAttribute:
   Enabled: false
 
+# Do not assign relative date to constants.
+Rails/RelativeDateConstant:
+  Enabled: false
+
 # Checks the arguments of ActiveRecord scopes.
 Rails/ScopeArgs:
   Enabled: true
@@ -945,6 +1028,10 @@ RSpec/AnyInstance:
 RSpec/BeEql:
   Enabled: true
 
+# We don't enforce this as we use this technique in a few places.
+RSpec/BeforeAfterAll:
+  Enabled: false
+
 # Check that the first argument to the top level describe is the tested class or
 # module.
 RSpec/DescribeClass:
@@ -963,6 +1050,12 @@ RSpec/DescribeSymbol:
 RSpec/DescribedClass:
   Enabled: true
 
+# Checks if an example group does not include any tests.
+RSpec/EmptyExampleGroup:
+  Enabled: true
+  CustomIncludeMethods:
+    - run_permission_checks
+
 # Checks for long example.
 RSpec/ExampleLength:
   Enabled: false
@@ -981,6 +1074,10 @@ RSpec/ExampleWording:
 RSpec/ExpectActual:
   Enabled: true
 
+# Checks for opportunities to use `expect { … }.to output`.
+RSpec/ExpectOutput:
+  Enabled: true
+
 # Checks the file and folder naming of the spec file.
 RSpec/FilePath:
   Enabled: true
@@ -994,6 +1091,12 @@ RSpec/FilePath:
 RSpec/Focus:
   Enabled: true
 
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: is_expected, should
+RSpec/ImplicitExpect:
+  Enabled: true
+  EnforcedStyle: is_expected
+
 # Checks for the usage of instance variables.
 RSpec/InstanceVariable:
   Enabled: false
@@ -1039,6 +1142,13 @@ RSpec/NotToNot:
 RSpec/RepeatedDescription:
   Enabled: false
 
+# Ensure RSpec hook blocks are always multi-line.
+RSpec/SingleLineHook:
+  Enabled: true
+  Exclude:
+    - 'spec/factories/*'
+    - 'spec/requests/api/v3/*'
+
 # Checks for stubbed test subjects.
 RSpec/SubjectStub:
   Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 38b22afdf8..9caef3bde0 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,45 +1,89 @@
 # This configuration was generated by
 # `rubocop --auto-gen-config --exclude-limit 0`
-# on 2017-04-07 20:17:35 -0400 using RuboCop version 0.47.1.
+# on 2017-07-10 01:48:30 +0900 using RuboCop version 0.49.1.
 # The point is for the user to remove these configuration records
 # one by one as the offenses are removed from the code base.
 # Note that changes in the inspected code, or installation of new
 # versions of RuboCop, may require this file to be generated again.
 
-# Offense count: 54
-RSpec/BeforeAfterAll:
+# Offense count: 181
+# Cop supports --auto-correct.
+# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
+Layout/ExtraSpacing:
   Enabled: false
 
-# Offense count: 15
-# Configuration parameters: CustomIncludeMethods.
-RSpec/EmptyExampleGroup:
+# Offense count: 119
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# SupportedStyles: special_inside_parentheses, consistent, align_brackets
+Layout/IndentArray:
   Enabled: false
 
-# Offense count: 233
+# Offense count: 208
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# SupportedStyles: special_inside_parentheses, consistent, align_braces
+Layout/IndentHash:
+  Enabled: false
+
+# Offense count: 174
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: space, no_space
+Layout/SpaceBeforeBlockBraces:
+  Enabled: false
+
+# Offense count: 8
+# Cop supports --auto-correct.
+# Configuration parameters: AllowForAlignment.
+Layout/SpaceBeforeFirstArg:
+  Enabled: false
+
+# Offense count: 64
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: require_no_space, require_space
+Layout/SpaceInLambdaLiteral:
+  Enabled: false
+
+# Offense count: 256
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
+# SupportedStyles: space, no_space
+# SupportedStylesForEmptyBraces: space, no_space
+Layout/SpaceInsideBlockBraces:
+  Enabled: false
+
+# Offense count: 135
+# Cop supports --auto-correct.
+Layout/SpaceInsideParens:
+  Enabled: false
+
+# Offense count: 14
+# Cop supports --auto-correct.
+Layout/SpaceInsidePercentLiteralDelimiters:
+  Enabled: false
+
+# Offense count: 89
+# Cop supports --auto-correct.
+Layout/TrailingWhitespace:
+  Enabled: false
+
+# Offense count: 272
 RSpec/EmptyLineAfterFinalLet:
   Enabled: false
 
-# Offense count: 167
+# Offense count: 181
 RSpec/EmptyLineAfterSubject:
   Enabled: false
 
-# Offense count: 3
-RSpec/ExpectOutput:
-  Enabled: false
-
-# Offense count: 72
+# Offense count: 78
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: implicit, each, example
 RSpec/HookArgument:
   Enabled: false
 
-# Offense count: 12
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: is_expected, should
-RSpec/ImplicitExpect:
-  Enabled: false
-
-# Offense count: 11
+# Offense count: 9
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: it_behaves_like, it_should_behave_like
 RSpec/ItBehavesLike:
@@ -49,19 +93,19 @@ RSpec/ItBehavesLike:
 RSpec/IteratedExpectation:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 2
 RSpec/OverwritingSetup:
   Enabled: false
 
-# Offense count: 34
+# Offense count: 36
 RSpec/RepeatedExample:
   Enabled: false
 
-# Offense count: 43
+# Offense count: 86
 RSpec/ScatteredLet:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 20
 RSpec/ScatteredSetup:
   Enabled: false
 
@@ -69,7 +113,7 @@ RSpec/ScatteredSetup:
 RSpec/SharedContext:
   Enabled: false
 
-# Offense count: 150
+# Offense count: 115
 Rails/FilePath:
   Enabled: false
 
@@ -79,97 +123,71 @@ Rails/FilePath:
 Rails/ReversibleMigration:
   Enabled: false
 
-# Offense count: 302
+# Offense count: 336
 # Configuration parameters: Blacklist.
 # Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters
 Rails/SkipsModelValidations:
   Enabled: false
 
-# Offense count: 7
+# Offense count: 11
 # Cop supports --auto-correct.
 Security/YAMLLoad:
   Enabled: false
 
-# Offense count: 59
+# Offense count: 58
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: percent_q, bare_percent
 Style/BarePercentLiterals:
   Enabled: false
 
-# Offense count: 1403
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: leading, trailing
-Style/DotPosition:
-  Enabled: false
-
-# Offense count: 5
+# Offense count: 6
 # Cop supports --auto-correct.
 Style/EachWithObject:
   Enabled: false
 
-# Offense count: 28
+# Offense count: 31
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: empty, nil, both
 Style/EmptyElse:
   Enabled: false
 
-# Offense count: 4
+# Offense count: 9
 # Cop supports --auto-correct.
 Style/EmptyLiteral:
   Enabled: false
 
-# Offense count: 59
+# Offense count: 78
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: compact, expanded
 Style/EmptyMethod:
   Enabled: false
 
-# Offense count: 214
+# Offense count: 23
 # Cop supports --auto-correct.
-# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
-Style/ExtraSpacing:
-  Enabled: false
-
-# Offense count: 9
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: format, sprintf, percent
 Style/FormatString:
   Enabled: false
 
-# Offense count: 285
+# Offense count: 301
 # Configuration parameters: MinBodyLength.
 Style/GuardClause:
   Enabled: false
 
-# Offense count: 16
+# Offense count: 18
 Style/IfInsideElse:
   Enabled: false
 
-# Offense count: 186
+# Offense count: 182
 # Cop supports --auto-correct.
 # Configuration parameters: MaxLineLength.
 Style/IfUnlessModifier:
   Enabled: false
 
-# Offense count: 99
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
-# SupportedStyles: special_inside_parentheses, consistent, align_brackets
-Style/IndentArray:
-  Enabled: false
-
-# Offense count: 160
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
-# SupportedStyles: special_inside_parentheses, consistent, align_braces
-Style/IndentHash:
-  Enabled: false
-
-# Offense count: 50
+# Offense count: 52
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: line_count_dependent, lambda, literal
@@ -181,63 +199,63 @@ Style/Lambda:
 Style/LineEndConcatenation:
   Enabled: false
 
-# Offense count: 34
+# Offense count: 40
 # Cop supports --auto-correct.
 Style/MethodCallWithoutArgsParentheses:
   Enabled: false
 
-# Offense count: 10
+# Offense count: 13
 Style/MethodMissing:
   Enabled: false
 
-# Offense count: 3
+# Offense count: 6
 # Cop supports --auto-correct.
 Style/MultilineIfModifier:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 26
 # Cop supports --auto-correct.
 Style/NestedParenthesizedCalls:
   Enabled: false
 
-# Offense count: 18
+# Offense count: 20
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
 # SupportedStyles: skip_modifier_ifs, always
 Style/Next:
   Enabled: false
 
-# Offense count: 37
+# Offense count: 45
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
 # SupportedOctalStyles: zero_with_o, zero_only
 Style/NumericLiteralPrefix:
   Enabled: false
 
-# Offense count: 88
+# Offense count: 98
 # Cop supports --auto-correct.
 # Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
 # SupportedStyles: predicate, comparison
 Style/NumericPredicate:
   Enabled: false
 
-# Offense count: 36
+# Offense count: 42
 # Cop supports --auto-correct.
 Style/ParallelAssignment:
   Enabled: false
 
-# Offense count: 570
+# Offense count: 800
 # Cop supports --auto-correct.
 # Configuration parameters: PreferredDelimiters.
 Style/PercentLiteralDelimiters:
   Enabled: false
 
-# Offense count: 14
+# Offense count: 15
 # Cop supports --auto-correct.
 Style/PerlBackrefs:
   Enabled: false
 
-# Offense count: 83
+# Offense count: 105
 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
 # NamePrefix: is_, has_, have_
 # NamePrefixBlacklist: is_, has_, have_
@@ -245,54 +263,47 @@ Style/PerlBackrefs:
 Style/PredicateName:
   Enabled: false
 
-# Offense count: 45
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: short, verbose
-Style/PreferredHashMethods:
-  Enabled: false
-
-# Offense count: 65
+# Offense count: 58
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: compact, exploded
 Style/RaiseArgs:
   Enabled: false
 
-# Offense count: 5
+# Offense count: 6
 # Cop supports --auto-correct.
 Style/RedundantBegin:
   Enabled: false
 
-# Offense count: 32
+# Offense count: 37
 # Cop supports --auto-correct.
 Style/RedundantFreeze:
   Enabled: false
 
-# Offense count: 15
+# Offense count: 14
 # Cop supports --auto-correct.
 # Configuration parameters: AllowMultipleReturnValues.
 Style/RedundantReturn:
   Enabled: false
 
-# Offense count: 382
+# Offense count: 406
 # Cop supports --auto-correct.
 Style/RedundantSelf:
   Enabled: false
 
-# Offense count: 111
+# Offense count: 115
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
 # SupportedStyles: slashes, percent_r, mixed
 Style/RegexpLiteral:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 29
 # Cop supports --auto-correct.
 Style/RescueModifier:
   Enabled: false
 
-# Offense count: 7
+# Offense count: 8
 # Cop supports --auto-correct.
 Style/SelfAssignment:
   Enabled: false
@@ -303,108 +314,58 @@ Style/SelfAssignment:
 Style/SingleLineMethods:
   Enabled: false
 
-# Offense count: 168
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: space, no_space
-Style/SpaceBeforeBlockBraces:
-  Enabled: false
-
-# Offense count: 8
-# Cop supports --auto-correct.
-# Configuration parameters: AllowForAlignment.
-Style/SpaceBeforeFirstArg:
-  Enabled: false
-
-# Offense count: 46
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: require_no_space, require_space
-Style/SpaceInLambdaLiteral:
-  Enabled: false
-
-# Offense count: 229
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
-# SupportedStyles: space, no_space
-# SupportedStylesForEmptyBraces: space, no_space
-Style/SpaceInsideBlockBraces:
-  Enabled: false
-
-# Offense count: 116
-# Cop supports --auto-correct.
-Style/SpaceInsideParens:
-  Enabled: false
-
-# Offense count: 12
-# Cop supports --auto-correct.
-Style/SpaceInsidePercentLiteralDelimiters:
-  Enabled: false
-
-# Offense count: 57
+# Offense count: 64
 # Cop supports --auto-correct.
 # Configuration parameters: SupportedStyles.
 # SupportedStyles: use_perl_names, use_english_names
 Style/SpecialGlobalVars:
   EnforcedStyle: use_perl_names
 
-# Offense count: 42
+# Offense count: 44
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles.
 # SupportedStyles: single_quotes, double_quotes
 Style/StringLiteralsInInterpolation:
   Enabled: false
 
-# Offense count: 64
+# Offense count: 84
 # Cop supports --auto-correct.
 # Configuration parameters: IgnoredMethods.
 # IgnoredMethods: respond_to, define_method
 Style/SymbolProc:
   Enabled: false
 
-# Offense count: 6
+# Offense count: 8
 # Cop supports --auto-correct.
 # Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
 # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
 Style/TernaryParentheses:
   Enabled: false
 
-# Offense count: 53
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline.
-# SupportedStylesForMultiline: comma, consistent_comma, no_comma
-Style/TrailingCommaInArguments:
-  Enabled: false
-
-# Offense count: 18
+# Offense count: 17
 # Cop supports --auto-correct.
 # Configuration parameters: AllowNamedUnderscoreVariables.
 Style/TrailingUnderscoreVariable:
   Enabled: false
 
-# Offense count: 78
-# Cop supports --auto-correct.
-Style/TrailingWhitespace:
-  Enabled: false
-
-# Offense count: 3
+# Offense count: 4
 # Cop supports --auto-correct.
 # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
 # Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
 Style/TrivialAccessors:
   Enabled: false
 
-# Offense count: 6
+# Offense count: 5
 # Cop supports --auto-correct.
 Style/UnlessElse:
   Enabled: false
 
-# Offense count: 24
+# Offense count: 28
 # Cop supports --auto-correct.
 Style/UnneededInterpolation:
   Enabled: false
 
-# Offense count: 8
+# Offense count: 11
 # Cop supports --auto-correct.
 Style/ZeroLengthPredicate:
   Enabled: false
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 83c68309fa..73f8d27f78 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -10,12 +10,12 @@ linters:
   # Reports when you use improper spacing around ! (the "bang") in !default,
   # !global, !important, and !optional flags.
   BangFormat:
-    enabled: false
-  
+    enabled: true
+
   # Whether or not to prefer `border: 0` over `border: none`.
   BorderZero:
     enabled: false
-  
+
   # Reports when you define a rule set using a selector with chained classes
   # (a.k.a. adjoining classes).
   ChainedClasses:
@@ -25,13 +25,13 @@ linters:
   # (e.g. `color: green` is a color keyword)
   ColorKeyword:
     enabled: false
-  
+
   # Prefer color literals (keywords or hexadecimal codes) to be used only in
   # variable declarations. They should be referred to via variables everywhere
   # else.
   ColorVariable:
     enabled: true
-  
+
   # Which form of comments to prefer in CSS.
   Comment:
     enabled: false
@@ -39,14 +39,15 @@ linters:
   # Reports @debug statements (which you probably left behind accidentally).
   DebugStatement:
     enabled: false
-  
+
   # Rule sets should be ordered as follows:
   # - @extend declarations
   # - @include declarations without inner @content
-  # - properties, @include declarations with inner @content
+  # - properties
+  # - @include declarations with inner @content
   # - nested rule sets.
   DeclarationOrder:
-    enabled: false
+    enabled: true
 
   # `scss-lint:disable` control comments should be preceded by a comment
   # explaining why these linters are being disabled for this file.
@@ -54,19 +55,19 @@ linters:
   # more information.
   DisableLinterReason:
     enabled: true
-  
+
   # Reports when you define the same property twice in a single rule set.
   DuplicateProperty:
-    enabled: false
-  
+    enabled: true
+
   # Separate rule, function, and mixin declarations with empty lines.
   EmptyLineBetweenBlocks:
     enabled: true
-  
+
   # Reports when you have an empty rule set.
   EmptyRule:
     enabled: true
-  
+
   # Reports when you have an @extend directive.
   ExtendDirective:
     enabled: false
@@ -75,49 +76,49 @@ linters:
   # when adding lines to the file, since SCM systems such as git won't
   # think that you touched the last line.
   FinalNewline:
-    enabled: false
-  
+    enabled: true
+
   # HEX colors should use three-character values where possible.
   HexLength:
     enabled: false
-  
+
   # HEX color values should use lower-case colors to differentiate between
   # letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
   HexNotation:
     enabled: true
-  
+
   # Avoid using ID selectors.
   IdSelector:
     enabled: false
-  
+
   # The basenames of @imported SCSS partials should not begin with an
   # underscore and should not include the filename extension.
   ImportPath:
-    enabled: false
-  
+    enabled: true
+
   # Avoid using !important in properties. It is usually indicative of a
   # misunderstanding of CSS specificity and can lead to brittle code.
   ImportantRule:
     enabled: false
-  
+
   # Indentation should always be done in increments of 2 spaces.
   Indentation:
     enabled: true
     width: 2
-  
+
   # Don't write leading zeros for numeric values with a decimal point.
   LeadingZero:
     enabled: false
-  
+
   # Reports when you define the same selector twice in a single sheet.
   MergeableSelector:
     enabled: false
-  
+
   # Functions, mixins, variables, and placeholders should be declared
   # with all lowercase letters and hyphens instead of underscores.
   NameFormat:
     enabled: false
-  
+
   # Avoid nesting selectors too deeply.
   NestingDepth:
     enabled: false
@@ -129,12 +130,12 @@ linters:
   # Sort properties in a strict order.
   PropertySortOrder:
     enabled: false
-  
+
   # Reports when you use an unknown or disabled CSS property
   # (ignoring vendor-prefixed properties).
   PropertySpelling:
-    enabled: false
-  
+    enabled: true
+
   # Configure which units are allowed for property values.
   PropertyUnits:
     enabled: false
@@ -144,25 +145,25 @@ linters:
   # be declared with one colon.
   PseudoElement:
     enabled: true
-  
+
   # Avoid qualifying elements in selectors (also known as "tag-qualifying").
   QualifyingElement:
     enabled: false
-  
+
   # Don't write selectors with a depth of applicability greater than 3.
   SelectorDepth:
     enabled: false
-  
+
   # Selectors should always use hyphenated-lowercase, rather than camelCase or
   # snake_case.
   SelectorFormat:
     enabled: false
     convention: hyphenated_lowercase
-  
+
   # Prefer the shortest shorthand form possible for properties that support it.
   Shorthand:
     enabled: true
-  
+
   # Each property should have its own line, except in the special case of
   # single line rulesets.
   SingleLinePerProperty:
@@ -173,11 +174,15 @@ linters:
   # individual selector occupy a single line.
   SingleLinePerSelector:
     enabled: true
-  
+
   # Commas in lists should be followed by a space.
   SpaceAfterComma:
+    enabled: true
+
+  # Comment literals should be followed by a space.
+  SpaceAfterComment:
     enabled: false
-  
+
   # Properties should be formatted with a single space separating the colon
   # from the property's value.
   SpaceAfterPropertyColon:
@@ -197,12 +202,12 @@ linters:
   # colon.
   SpaceAfterVariableName:
     enabled: false
-  
+
   # Operators should be formatted with a single space on both sides of an
   # infix operator.
   SpaceAroundOperator:
     enabled: true
-  
+
   # Opening braces should be preceded by a single space.
   SpaceBeforeBrace:
     enabled: true
@@ -210,7 +215,7 @@ linters:
   # Parentheses should not be padded with spaces.
   SpaceBetweenParens:
     enabled: false
-  
+
   # Enforces that string literals should be written with a consistent form
   # of quotes (single or double).
   StringQuotes:
@@ -240,8 +245,8 @@ linters:
   # Do not use parent selector references (&) when they would otherwise
   # be unnecessary.
   UnnecessaryParentReference:
-    enabled: false
-  
+    enabled: true
+
   # URLs should be valid and not contain protocols or domain names.
   UrlFormat:
     enabled: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a9aa6319c..5fc14f42c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,712 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
-## 9.2.10 (2017-08-09)
+## 9.5.4 (2017-09-06)
 
+- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
+- [SECURITY] Prevent a persistent XSS in the commit author block.
+- Fix XSS issue in go-get handling.
+- Resolve CSRF token leakage via pathname manipulation on environments page.
+- Fixes race condition in project uploads.
+- Disallow arbitrary properties in `th` and `td` `style` attributes.
+- Disallow the `name` attribute on all user-provided markup.
+
+## 9.5.3 (2017-09-03)
+
+- [SECURITY] Filter additional secrets from Rails logs.
+- [FIXED] Make username update fail if the namespace update fails. !13642
+- [FIXED] Fix failure when issue is authored by a deleted user. !13807
+- [FIXED] Reverts changes made to signin_enabled. !13956
+- [FIXED] Fix Merge when pipeline succeeds button dropdown caret icon horizontal alignment.
+- [FIXED] Fixed diff changes bar buttons from showing/hiding whilst scrolling.
+- [FIXED] Fix events error importing GitLab projects.
+- [FIXED] Fix pipeline trigger via API fails with 500 Internal Server Error in 9.5.
+- [FIXED] Fixed fly-out nav flashing in & out.
+- [FIXED] Remove closing external issues by reference error.
+- [FIXED] Re-allow appearances.description_html to be NULL.
+- [CHANGED] Update and fix resolvable note icons for easier recognition.
+- [OTHER] Eager load head pipeline projects for MRs index.
+- [OTHER] Instrument MergeRequest#fetch_ref.
+- [OTHER] Instrument MergeRequest#ensure_ref_fetched.
+
+## 9.5.2 (2017-08-28)
+
+- [FIXED] Fix signing in using LDAP when attribute mapping uses simple strings instead of arrays.
+- [FIXED] Show un-highlighted text diffs when we do not have references to the correct blobs.
+- [FIXED] Fix display of push events for removed refs.
+- [FIXED] Testing of some integrations were broken due to missing ServiceHook record.
+- [FIXED] Fire system hooks when a user is created via LDAP.
+- [FIXED] Fix new project form not resetting the template value.
+
+## 9.5.1 (2017-08-23)
+
+- [FIXED] Fix merge request pipeline status when pipeline has errors. !13664
+- [FIXED] Commit rows would occasionally render with the wrong language.
+- [FIXED] Fix caching of future broadcast messages.
+- [FIXED] Only require Sidekiq throttling library when enabled, to reduce cache misses.
+- Raise Housekeeping timeout to 24 hours. !13719
+
+## 9.5.0 (2017-08-22)
+
+- [FIXED] Fix timeouts when creating projects in groups with many members. !13508
+- [FIXED] Improve API pagination headers when no record found. !13629 (Jordan Patterson)
+- [FIXED] Fix deleting GitLab Pages files when a project is removed. !13631
+- [FIXED] Fix commit list not loading the correct page when scrolling.
+- [OTHER] Cache the number of forks of a project. !13535
+- GPG signed commits integration. !9546 (Alexis Reigel)
+- Alert the user if a Wiki page changed while they were editing it in order to prevent overwriting changes. !9707 (Hiroyuki Sato)
+- Add custom linter for inline JavaScript to haml_lint. !9742 (winniehell)
+- Add /shrug and /tableflip commands. !10068 (Alex Ives)
+- Allow wiki pages to be renamed in the UI. !10069 (wendy0402)
+- Insert user name directly without encoding. !10085 (Nathan Neulinger )
+- Avoid plucking Todo ids in TodoService. !10845
+- Handle errors while a project is being deleted asynchronously. !11088
+- Decrease ABC threshold to 56.96. !11227 (Maxim Rydkin)
+- Remove Mattermost team when deleting a group. !11362
+- Block access to failing repository storage. !11449
+- Add coordinator url to admin area runner page. !11603
+- Allow testing any events for project hooks and system hooks. !11728 (Alexander Randa (@randaalex))
+- Disallow running the pipeline if ref is protected and user cannot merge the branch or create the tag. !11910
+- Remove project_key from the Jira configuration. !12050
+- Add CSRF token verification to API. !12154 (Vitaliy @blackst0ne Klachkov)
+- Fixes needed when GitLab sign-in is not enabled. !12491 (Robin Bobbitt)
+- Lazy load images for better Frontend performance. !12503
+- Replaces dashboard/event_filters.feature spinach with rspec. !12651 (Alexander Randa (@randaalex))
+- Toggle import description with import_sources_enabled. !12691 (Brianna Kicia)
+- Bump scss-lint to 0.54.0. !12733 (Takuya Noguchi)
+- Enable SpaceAfterComma in scss-lint. !12734 (Takuya Noguchi)
+- Remove CSS for nprogress removed. !12737 (Takuya Noguchi)
+- Enable UnnecessaryParentReference in scss-lint. !12738 (Takuya Noguchi)
+- Extract "@request.env[devise.mapping] = Devise.mappings[:user]" to a test helper. !12742 (Jacopo Beschi @jacopo-beschi)
+- Enable ImportPath in scss-lint. !12749 (Takuya Noguchi)
+- Enable PropertySpelling in scss-lint. !12752 (Takuya Noguchi)
+- Add API for protected branches to allow for wildcard matching and no access restrictions. !12756 (Eric Yu)
+- refactor initializations in dropzone_input.js. !12768 (Brandon Everett)
+- Improve CSS for global nav dropdown UI. !12772 (Takuya Noguchi)
+- Remove public/ci/favicon.ico. !12803 (Takuya Noguchi)
+- Enable DeclarationOrder in scss-lint. !12805 (Takuya Noguchi)
+- Increase width of dropdown menus automatically. !12809 (Thomas Wucher)
+- Enable BangFormat in scss-lint [ci skip]. !12815 (Takuya Noguchi)
+- Added /duplicate quick action to close a duplicate issue. !12845 (Ryan Scott)
+- Make all application-settings accessible through the API. !12851
+- Remove Inactive Personal Access Tokens list from Access Tokens page. !12866
+- Replaces dashboard/dashboard.feature spinach with rspec. !12876 (Alexander Randa (@randaalex))
+- Reduce memory usage of the GitHub importer. !12886
+- Bump fog-core to 1.44.3 and fog providers' plugins to latest. !12897 (Takuya Noguchi)
+- Use only CSS to truncate commit message in blame. !12900 (Takuya Noguchi)
+- Protect manual actions against protected tag too. !12908
+- Allow to configure automatic retry of a failed CI/CD job. !12909
+- Remove help message about prioritized labels for non-members. !12912 (Takuya Noguchi)
+- Add link to doc/api/ci/lint.md. !12914 (Takuya Noguchi)
+- Add RequestCache which makes caching with RequestStore easier. !12920
+- Free up some top level words, reject top level groups named like files in the public folder. !12932
+- Extend API for Group Secret Variable. !12936
+- Hide description about protected branches to non-member. !12945 (Takuya Noguchi)
+- Support custom directory in gitlab:backup:create task. !12984 (Markus Koller)
+- Raise guessed encoding confidence threshold to 50. !12990
+- Add author_id & assignee_id param to /issues API. !13004
+- Fix today day highlight in calendar. !13048
+- Prevent LDAP login callback from being called with a GET request. !13059
+- Add top-level merge_requests API endpoint. !13060
+- Handle maximum pages artifacts size correctly. !13072
+- Enable gitaly_post_upload_pack by default. !13078
+- Add Prometheus metrics exporter to Sidekiq. !13082
+- Fix improperly skipped backups of wikis. !13096
+- Projects can be created from templates. !13108
+- Fix the /projects/:id/repository/branches endpoint to handle dots in the branch name when the project full path contains a `/`. !13115
+- Fix project logos that are not centered vertically on list pages. !13124 (Florian Lemaitre)
+- Derive project path from import URL. !13131
+- Fix deletion of deploy keys linked to other projects. !13162
+- repository archive download url now ends with selected file extension. !13178 (haseebeqx)
+- Show auto-generated avatars for Groups without avatars. !13188
+- Allow any logged in users to read_users_list even if it's restricted. !13201
+- Unlock stuck merge request and set the proper state. !13207
+- Fix timezone inconsistencies in user contribution graph. !13208
+- Fix Issue board when using Ruby 2.4. !13220
+- Don't rename namespace called system when upgrading from 9.1.x to 9.5. !13228
+- Fix encoding error for WebHook logging. !13230 (Alexander Randa (@randaalex))
+- Uniquify reserved word usernames on OAuth user creation. !13244 (Robin Bobbitt)
+- Expose target_iid in Events API. !13247 (sue445)
+- Add star for action scope, in order to delete image from registry. !13248 (jean)
+- Make Delete Merged Branches handle wildcard protected branches correctly. !13251
+- Fix an order of operations for CI connection error message in merge request widget. !13252
+- Don't send rejection mails for all auto-generated mails. !13254
+- Expose noteable_iid in Note. !13265 (sue445)
+- Fix pipeline_schedules pages when active schedule has an abnormal state. !13286
+- Move some code from services to workers in order to improve performance. !13326
+- Fix destroy of case-insensitive conflicting redirects. !13357
+- Fix the /projects/:id/repository/tags endpoint to handle dots in the tag name when the project full path contains a `/`. !13368
+- Fix the /projects/:id/repository/commits endpoint to handle dots in the ref name when the project full path contains a `/`. !13370
+- Project pending delete no longer return 500 error in admins projects view. !13389
+- Use full path of user's avatar in webhooks. !13401 (Vitaliy @blackst0ne Klachkov)
+- Make GPGME temporary directory handling thread safe. !13481 (Alexis Reigel)
+- Add support for kube_namespace in Metrics queries. !16169
+- Fix bar chart does not display label at 0 hour. !35136 (Jason Dai)
+- Use project_ref_path to create the link to a branch to fix links that 404.
+- Declare related resources into V4 API entities.
+- Add Slack and JIRA services counts to Usage Data.
+- Prevent web hook and project service background jobs from going to the dead jobs queue.
+- Display specific error message when JIRA test fails.
+- clean up merge request widget UI.
+- Associate Issues tab only with internal issues tracker.
+- Remove events column from notification settings table.
+- Clarifies and rearranges the input variables on the kubernetes integration page and adjusts the docs slightly to meet the same order.
+- Respect blockquote line breaks in markdown.
+- Update confidential issue UI - add confidential visibility and settings to sidebar.
+- Add icons to contextual sidebars.
+- Make contextual sidebar collapsible.
+- Update Pipeline's badge count in Merge Request and Commits view to match real-time content.
+- Added link to the MR widget that directs to the monitoring dashboard.
+- Use jQuery to control scroll behavior in job log for cross browser consistency.
+- move edit comment button outside of dropdown.
+- Updates vue resource and code according to breaking changes.
+- Add GitHub imported projects count to usage data.
+- Rename about to overview for group and project page.
+- Prevent disabled pagination button to be clicked.
+- Remove coffee-rails gem. (Takuya Noguchi)
+- Remove net-ssh gem. (Takuya Noguchi)
+- Bump rubocop to 0.49.1 and rubocop-rspec to 1.15.1. (Takuya Noguchi)
+- improve file upload/replace experience.
+- allow closing Cycle Analytics intro box in firefox.
+- Fix label creation from new list for subgroup projects.
+- fix transient js error in rspec tests.
+- fix jump to next discussion button.
+- Fix translations for Star/Unstar in JS file.
+- Improve mobile sidebar.
+- Rename Pipelines tab to CI / CD in new navigation.
+- Fix display of new diff comments after changing b between diff views.
+- Store & use ConvDev percentages returned by the Version app.
+- Fixes new issue button for failed job returning 404.
+- Align OR separator to center in new project page.
+- Add filtered search to group issue dashboard.
+- Cache Appearance instances in Redis.
+- Fixed breadcrumbs title aggressively collapsing.
+- Better caching and indexing of broadcast messages.
+- Moved diff changed files into a dropdown.
+- Improve performance of large (initial) push into default branch.
+- Improve performance of checking for projects on the projects dashboard.
+- Eager load project creators for project dashboards.
+- Modify if condition to be more readable.
+- Fix links to group milestones from issue and merge request sidebar.
 - Remove hidden symlinks from project import files.
+- Fixed sign-in restrictions buttons not toggling active state.
+- Fix replying to commit comments on merge requests created from forks.
+- Support Markdown references, autocomplete, and quick actions for group milestones.
+- Cache recent projects for group-level new resource creation.
+- Fix API responses when dealing with txt files.
+- Fix project milestones import when projects belongs to a group.
+- Fix Mattermost integration.
+- Memoize the number of personal projects a user has to reduce COUNT queries.
+- Merge issuable "reopened" state into "opened".
+- Migrate events into a new format to reduce the storage necessary and improve performance.
+- MR branch link now links to tree instead of commits.
+- Use Prev/Next pagination for exploring projects.
+- Pass before_script and script as-is preserving arrays.
+- Change project FK migration to skip existing FKs.
+- Remove redundant query when retrieving the most recent push of a user.
+- Re-organise "issues" indexes for faster ordering.
 - Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric character.
+- Fix search box losing focus when typing.
+- Add structured logging for Rails processes.
+- Skip oAuth authorization for trusted applications.
+- Use a specialized class for querying events to improve performance.
+- Update build badges to be pipeline badges and display passing instead of success.
+
+## 9.4.3 (2017-07-31)
+
+- Fix Prometheus client PID reuse bug. !13130
+- Improve deploy environment chatops slash command. !13150
+- Fix asynchronous javascript paths when GitLab is installed under a relative URL. !13165
+- Fix LDAP authentication to Git repository or container registry.
+- Fixed new navigation breadcrumb title on help pages.
+- Ensure filesystem metrics test files are deleted.
+- Properly affixes nav bar in job view in microsoft edge.
+
+## 9.4.2 (2017-07-28)
+
+- Fix job merge request link to a forked source project. !12965
+- Improve redirect route query performance. !13062
+- Allow admin to read_users_list even if it's restricted. !13066
+- Fixes 500 error caused by pending delete projects in admin dashboard. !13067
+- Add instrumentation to MarkupHelper#link_to_gfm. !13069
+- Pending delete projects should not show in deploy keys. !13088
+- Fix sizing of custom header logo in new navigation.
+- Fix crash on /help/ui.
+- Fix creating merge request diffs when diff contains bytes that are invalid in UTF-8.
+- fix vertical alignment of New Project button.
+- Add LDAP SSL certificate verification option.
+- Fix vertical alignment in firefox and safari for pipeline mini graph.
+
+## 9.4.1 (2017-07-25)
+
+- Fix pipeline_schedules pages throwing error 500 (when ref is empty). !12983
+- Fix editing project with container images present. !13028
+- Fix some invalid entries in PO files. !13032
+- Fix cross site request protection when logging in as a regular user when LDAP is enabled. !13049
+- Fix bug causing metrics files to be truncated. !35420
+- Fix anonymous access to public projects in groups with pending invites.
+- Fixed issue boards sidebar close icon size.
+- Fixed duplicate new milestone buttons when new navigation is turned on.
+- Fix margins in the mini graph for pipeline in commits box.
+
+## 9.4.0 (2017-07-22)
+
+- Add blame view age mapping. !7198 (Jeff Stubler)
+- Add support for image and services configuration in .gitlab-ci.yml. !8578
+- Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients. !9045 (jneen)
+- Use fa-chevron-down on dropdown arrows for consistency. !9659 (TM Lee)
+- Update the devise mail templates to match the design of the pipeline emails. !10483 (Alexis Reigel)
+- Handle renamed submodules in repository browser. !10798 (David Turner)
+- Display all current broadcast messages, not just the last one. !11113 (rickettm)
+- Fix CI/CD status in case there are only allowed to failed jobs in the pipeline. !11166
+- Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname. !11218 (Stefan Hanreich)
+- Moved "Members in a project" menu entry and path locations. !11560
+- Additional Prometheus metrics support. !11712
+- Rename all reserved paths that could have been created. !11713
+- Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name. !11713
+- Fix offline runner detection. !11751 (Alessio Caiazza)
+- Use authorize_update_pipeline_schedule in PipelineSchedulesController. !11846
+- Rollback project repo move if there is an error in Projects::TransferService. !11877
+- Help landing page customizations. !11878 (Robin Bobbitt)
+- Fixes "sign in / Register" active state underline misalignment. !11890 (Frank Sierra)
+- Honor the "Remember me" parameter for OAuth-based login. !11963
+- Instruct user to use personal access token for Git over HTTP. !11986 (Robin Bobbitt)
+- Accept image for avatar in project API. !11988 (Ivan Chernov)
+- Supplement Simplified Chinese translation of Project Page & Repository Page. !11994 (Huang Tao)
+- Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page. !11995 (Huang Tao)
+- Make the revision on the `/help` page clickable. !12016
+- Display issue state in issue links section of merge request widget. !12021
+- Enable support for webpack code-splitting by dynamically setting publicPath at runtime. !12032
+- Replace PhantomJS with headless Chrome for karma test suite. !12036
+- Prevent description change notes when toggling tasks. !12057 (Jared Deckard )
+- Update QA Dockerfile to lock Chrome browser version. !12071
+- Fix FIDO U2F for Opera browser. !12082 (Jakub Kramarz and Jonas Kalderstam)
+- Supplement Bulgarian translation of Project Page & Repository Page. !12083 (Lyubomir Vasilev)
+- Removes deleted_at and pending_delete occurrences in Project related queries. !12091
+- Provide hint to create a personal access token for Git over HTTP. !12105 (Robin Bobbitt)
+- Display own user id in account settings page. !12141 (Riccardo Padovani)
+- Accept image for avatar in user API. !12143 (Ivan Chernov)
+- Disable fork button on project limit. !12145 (Ivan Chernov)
+- Added "created_after" and "created_before" params to issuables. !12151 (Kyle Bishop @kybishop)
+- Supplement Portuguese Brazil translation of Project Page & Repository Page. !12156 (Huang Tao)
+- Add review apps to usage metrics. !12185
+- Adding French translations. !12200 (Erwan "Dremor" Georget)
+- Ensures default user limits when external user is unchecked. !12218
+- Provide KUBECONFIG from KubernetesService for runners. !12223
+- Filter archived project in API v3 only if param present. !12245 (Ivan Chernov)
+- Add explicit message when no runners on admin. !12266 (Takuya Noguchi)
+- Split pipelines as internal and external in the usage data. !12277
+- Fix API Scoping. !12300
+- Remove registry image delete button if user cant delete it. !12317 (Ivan Chernov)
+- Allow the feature flags to be enabled/disabled with more granularity. !12357
+- Allow to enable the performance bar per user or Feature group. !12362
+- Rename duplicated variables with the same key for projects. Add environment_scope column to variables and add unique constraint to make sure that no variables could be created with the same key within a project. !12363
+- Add variables to pipelines schedules. !12372
+- Add User#full_private_access? to check if user has access to all private groups & projects. !12373
+- Change milestone endpoint for groups. !12374 (Takuya Noguchi)
+- Improve performance of the pipeline charts page. !12378
+- Add option to run Gitaly on a remote server. !12381
+- #20628 Enable implicit grant in GitLab as OAuth Provider. !12384 (Mateusz Pytel)
+- Replace 'snippets/snippets.feature' spinach with rspec. !12385 (Alexander Randa @randaalex)
+- Add Simplified Chinese translations of Commits Page. !12405 (Huang Tao)
+- Add Traditional Chinese in HongKong translations of Commits Page. !12406 (Huang Tao)
+- Add Traditional Chinese in Taiwan translations of Commits Page. !12407 (Huang Tao)
+- Add Portuguese Brazil translations of Commits Page. !12408 (Huang Tao)
+- Add French translations of Commits Page. !12409 (Huang Tao)
+- Add Esperanto translations of Commits Page. !12410 (Huang Tao)
+- Add Bulgarian translations of Commits Page. !12411 (Huang Tao)
+- Remove bin/ci/upgrade.rb as not working all. !12414 (Takuya Noguchi)
+- Store merge request ref_fetched status in the database. !12424
+- Replace 'dashboard/merge_requests' spinach with rspec. !12440 (Alexander Randa (@randaalex))
+- Add Esperanto translations for Cycle Analytics, Project, and Repository pages. !12442 (Huang Tao)
+- Allow unauthenticated access to the /api/v4/users API. !12445
+- Drop GFM support for the title of Milestone/MergeRequest in template. !12451 (Takuya Noguchi)
+- Replace 'dashboard/todos' spinach with rspec. !12453 (Alexander Randa (@randaalex))
+- Cache open issue and merge request counts for project tabs to speed up project pages. !12457
+- Introduce cache policies for CI jobs. !12483
+- Improve support for external issue references. !12485
+- Fix errors caused by attempts to report already blocked or deleted users. !12502 (Horacio Bertorello)
+- Allow customize CI config path. !12509 (Keith Pope)
+- Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page. !12514 (Huang Tao)
+- Closes any open Autocomplete of the markdown editor when the form is closed. !12521
+- Inserts exact matches of name, username and email to the top of the search list. !12525
+- Use smaller min-width for dropdown-menu-nav only on mobile. !12528 (Takuya Noguchi)
+- Hide archived project labels from group issue tracker. !12547 (Horacio Bertorello)
+- Replace 'dashboard/new-project.feature' spinach with rspec. !12550 (Alexander Randa (@randaalex))
+- Remove group modal like remove project modal (requires typing + confirmation). !12569 (Diego Souza)
+- Add Italian translation of Cycle Analytics Page & Project Page & Repository Page. !12578 (Huang Tao)
+- Add Group secret variables. !12582
+- Update jobs page output to have a scrollable page. !12587
+- Add user projects API. !12596 (Ivan Chernov)
+- Allow creation of files and directories with spaces through Web UI. !12608
+- Improve members view on mobile. !12619
+- Fixed the chart legend not being set correctly. !12628
+- Add Italian translations of Commits Page. !12645 (Huang Tao)
+- Allow admins to disable all restricted visibility levels. !12649
+- Allow admins to retrieve user agent details for an issue or snippet. !12655
+- Update welcome page UX for new users. !12662
+- N+1 problems on milestone page. !12670 (Takuya Noguchi)
+- Upgrade GitLab Workhorse to v2.3.0. !12676
+- Remove option to disable Gitaly. !12677
+- Improve the performance of the project list API. !12679
+- Add creation time filters to user search API for admins. !12682
+- Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12693 (Huang Tao)
+- Undo adding the /reassign quick action. !12701
+- Fix dashboard labels dropdown. !12708
+- Username and password are no longer stripped from import url on mirror update. !12725
+- Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12743 (Huang Tao)
+- Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12744 (Huang Tao)
+- Prevent bad data being added to application settings when Redis is unavailable. !12750
+- Do not show pipeline schedule button for non-member. !12757 (Takuya Noguchi)
+- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
+- Recover from renaming project that has container images. !12840
+- Exact matches of username and email are now on top of the user search. !12868
+- Use Ghost user for last_edited_by and merge_user when original user is deleted. !12933
+- Fix docker tag reference routing constraints. !12961
+- Optimize creation of commit API by using Repository#commit instead of Repository#commits.
+- Speed up used languages calculation on charts page.
+- Make loading new merge requests (those created after the 9.4 upgrade) faster.
+- Ensure participants for issues, merge requests, etc. are calculated correctly when sending notifications.
+- Handle nameless legacy jobs.
+- Bump Faraday and dependent OAuth2 gem version to support no_proxy variable.
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Render CI statuses with warnings in orange.
+- Document the Delete Merged Branches functionality.
+- Add wells to admin dashboard overview to fix spacing problems.
+- Removes hover style for nodes that are either links or buttons in the pipeline graph.
+- more visual contrast in pagination widget.
+- Deprecate Healthcheck Access Token in favor of IP whitelist.
+- Drop GFM support for issuable title on milestone for consistency and performance. (Takuya Noguchi)
+- fix left & right padding on sidebar.
+- Cleanup minor UX issues in the performance dashboard.
+- Remove two columned layout from project member settings.
+- Make font size of contextual sub menu items 14px.
+- Fix vertical space in job details sidebar.
+- Fix alignment of controls in mr issuable list.
+- Add wip message to new navigation preference section.
+- Add group members counting and plan related data on namespaces API.
+- Fix spacing on runner buttons.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+- Change order of monospace fonts to fix bug on some linux distros.
+- Limit commit & snippets comments width.
+- Fixed dashboard milestone tabs not loading.
+- Detect if file that appears to be text in the first 1024 bytes is actually binary afer loading all data.
+- Fix inconsistent display of the "Browse files" button in the commit list.
+- Implement diff viewers.
+- Fix 'New merge request' button for users who don't have push access to canonical project.
+- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
+- Show group name instead of path on group page.
+- Don't check if MailRoom is running on Omnibus.
+- Limit OpenGraph image size to 64x64.
+- Don't show auxiliary blob viewer for README when there is no wiki.
+- Strip trailing whitespace in relative submodule URL.
+- Update /target_branch slash command description to be more consistent.
+- Remove unnecessary top padding on group MR index.
+- Added printing_merge_requst_link_enabled to the API. (David Turner )
+- Re-enable realtime for environments table.
+- Create responsive mobile view for pipelines table.
+- Adds realtime feature to job show view header and sidebar info. Updates UX.
+- Use color inputs for broadcast messages.
+- Center dropdown for mini graph.
+- Users can subscribe to group labels on the group labels page.
+- Add issuable-list class to shared mr/issue lists to fix new responsive layout design.
+- Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor of "slash commands".
+- Don't mark empty MRs as merged on push to the target branch.
+- Improve issue rendering performance with lots of notes from other users.
+- Fixed overflow on mobile screens for the slash commands.
+- Fix an infinite loop when handling user-supplied regular expressions.
+- Fixed sidebar not collapsing on merge requests in mobile screens.
+- Speed up project removals by adding foreign keys with cascading deletes to various tables.
+- Fix mobile view of files view buttons.
+- Fixed dropdown filter input not focusing after transition.
+- Fixed GFM references not being included when updating issues inline.
+- Remove issues/merge requests drag n drop and sorting from milestone view.
+- Add native group milestones.
+- Fix API bug accepting wrong parameter to create merge request.
+- Clean up UI of issuable lists and make more responsive.
+- Improve the overall UX for the new monitoring dashboard.
+- Fixed the y_label not setting correctly for each graph on the monitoring dashboard.
+- Changed utilities imports from ~ to relative paths.
+- Remove unused space in sidebar todo toggle when not signed in.
+- Limit the width of the projects README text.
+- Add a simple mode to merge request API.
+- Make Project#ensure_repository force create a repo.
+- Use uploads/system directory for personal snippets.
+- Defer project destroys within a namespace in Groups::DestroyService#async_execute.
+- Log rescued exceptions to Sentry.
+- Remove remaining N+1 queries in merge requests API with emojis and labels.
+
+## 9.3.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.3.8 (2017-07-19)
+
+- Improve support for external issue references. !12485
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Use uploads/system directory for personal snippets.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
+## 9.3.7 (2017-07-18)
+
+- Prevent bad data being added to application settings when Redis is unavailable. !12750
+- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
+
+## 9.3.6 (2017-07-12)
+
+- Fix API Scoping. !12300
+- Username and password are no longer stripped from import url on mirror update. !12725
+- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
+- Fixed GFM references not being included when updating issues inline.
+
+## 9.3.5 (2017-07-05)
+
+- Remove "Remove from board" button from backlog and closed list. !12430
+- Do not delete protected branches when deleting all merged branches. !12624
+- Set default for Remove source branch to false.
+- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
+- Expires full_path cache after a repository is renamed/transferred.
+
+## 9.3.4 (2017-07-03)
+
+- Update gitlab-shell to 5.1.1 !12615
+
+## 9.3.3 (2017-06-30)
+
+- Fix head pipeline stored in merge request for external pipelines. !12478
+- Bring back branches badge to main project page. !12548
+- Fix diff of requirements.txt file by not matching newlines as part of package names.
+- Perform housekeeping only when an import of a fresh project is completed.
+- Fixed issue boards closed list not showing all closed issues.
+- Fixed multi-line markdown tooltip buttons in issue edit form.
+
+## 9.3.2 (2017-06-27)
+
+- API: Fix optional arugments for POST :id/variables. !12474
+- Bump premailer-rails gem to 1.9.7 and its dependencies to prevent network retrieval of assets.
+
+## 9.3.1 (2017-06-26)
+
+- Fix reversed breadcrumb order for nested groups. !12322
+- Fix 500 when failing to create private group. !12394
+- Fix linking to line number on side-by-side diff creating empty discussion box.
+- Don't match tilde and exclamation mark as part of requirements.txt package name.
+- Perform project housekeeping after importing projects.
+- Fixed ctrl+enter not submit issue edit form.
+
+## 9.3.0 (2017-06-22)
+
+- Refactored gitlab:app:check into SystemCheck liberary and improve some checks. !9173
+- Add an ability to cancel attaching file and redesign attaching files UI. !9431 (blackst0ne)
+- Add Aliyun OSS as the backup storage provider. !9721 (Yuanfei Zhu)
+- Add suport for find_local_branches GRPC from Gitaly. !10059
+- Allow manual bypass of auto_sign_in_with_provider with a new param. !10187 (Maxime Besson)
+- Redirect to user's keys index instead of user's index after a key is deleted in the admin. !10227 (Cyril Jouve)
+- Changed Blame to Annotate in the UI to promote blameless culture. !10378 (Ilya Vassilevsky)
+- Implement ability to update deploy keys. !10383 (Alexander Randa)
+- Allow numeric values in gitlab-ci.yml. !10607 (blackst0ne)
+- Add a feature test for Unicode trace. !10736 (dosuken123)
+- Notes: Warning message should go away once resolved. !10823 (Jacopo Beschi @jacopo-beschi)
+- Project authorizations are calculated much faster when using PostgreSQL, and nested groups support for MySQL has been removed
+. !10885
+- Fix long urls in the title of commit. !10938 (Alexander Randa)
+- Update gem sidekiq-cron from 0.4.4 to 0.6.0 and rufus-scheduler from 3.1.10 to 3.4.0. !10976 (dosuken123)
+- Use relative paths for group/project/user avatars. !11001 (blackst0ne)
+- Enable cancelling non-HEAD pending pipelines by default for all projects. !11023
+- Implement web hook logging. !11027 (Alexander Randa)
+- Add indices for auto_canceled_by_id for ci_pipelines and ci_builds on PostgreSQL. !11034
+- Add post-deploy migration to clean up projects in `pending_delete` state. !11044
+- Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour. !11053
+- Disallow multiple selections for Milestone dropdown. !11084
+- Link to commit author user page from pipelines. !11100
+- Fix the last coverage in trace log should be extracted. !11128 (dosuken123)
+- Remove redirect for old issue url containing id instead of iid. !11135 (blackst0ne)
+- Backported new SystemHook event: `repository_update`. !11140
+- Keep input data after creating a tag that already exists. !11155
+- Fix support for external CI services. !11176
+- Translate backend for Project & Repository pages. !11183
+- Fix LaTeX formatting for AsciiDoc wiki. !11212
+- Add foreign key for pipeline schedule owner. !11233
+- Print Go version in rake gitlab:env:info. !11241
+- Include the blob content when printing a blob page. !11247
+- Sync email address from specified omniauth provider. !11268 (Robin Bobbitt)
+- Disable reference prefixes in notes for Snippets. !11278
+- Rename build_events to job_events. !11287
+- Add API support for pipeline schedule. !11307 (dosuken123)
+- Use route.cache_key for project list cache key. !11325
+- Make environment table realtime. !11333
+- Cache npm modules between pipelines with yarn to speed up setup-test-env. !11343
+- Allow GitLab instance to start when InfluxDB hostname cannot be resolved. !11356
+- Add ConvDev Index page to admin area. !11377
+- Fix Git-over-HTTP error statuses and improve error messages. !11398
+- Renamed users 'Audit Log'' to 'Authentication Log'. !11400
+- Style people in issuable search bar. !11402
+- Change /builds in the URL to /-/jobs. Backward URLs were also added. !11407
+- Update password field label while editing service settings. !11431
+- Add an optional performance bar to view performance metrics for the current page. !11439
+- Update task_list to version 2.0.0. !11525 (Jared Deckard )
+- Avoid resource intensive login checks if password is not provided. !11537 (Horatiu Eugen Vlad)
+- Allow numeric pages domain. !11550
+- Exclude manual actions when checking if pipeline can be canceled. !11562
+- Add server uptime to System Info page in admin dashboard. !11590 (Justin Boltz)
+- Simplify testing and saving service integrations. !11599
+- Fixed handling of the `can_push` attribute in the v3 deploy_keys api. !11607 (Richard Clamp)
+- Improve user experience around slash commands in instant comments. !11612
+- Show current user immediately in issuable filters. !11630
+- Add extra context-sensitive functionality for the top right menu button. !11632
+- Reorder Issue action buttons in order of usability. !11642
+- Expose atom links with an RSS token instead of using the private token. !11647 (Alexis Reigel)
+- Respect merge, instead of push, permissions for protected actions. !11648
+- Job details page update real time. !11651
+- Improve performance of ProjectFinder used in /projects API endpoint. !11666
+- Remove redundant data-turbolink attributes from links. !11672 (blackst0ne)
+- Minimum postgresql version is now 9.2. !11677
+- Add protected variables which would only be passed to protected branches or protected tags. !11688
+- Introduce optimistic locking support via optional parameter last_commit_sha on File Update API. !11694 (electroma)
+- Add $CI_ENVIRONMENT_URL to predefined variables for pipelines. !11695
+- Simplify project repository settings page. !11698
+- Fix pipeline_schedules pages throwing error 500. !11706 (dosuken123)
+- Add performance deltas between app deployments on Merge Request widget. !11730
+- Add feature toggles and API endpoints for admins. !11747
+- Replace 'starred_projects.feature' spinach test with an rspec analog. !11752 (blackst0ne)
+- Introduce an Events API. !11755
+- Display Shared Runner status in Admin Dashboard. !11783 (Ivan Chernov)
+- Persist pipeline stages in the database. !11790
+- Revert the feature that would include the current user's username in the HTTP clone URL. !11792
+- Enable Gitaly by default in installations from source. !11796
+- Use zopfli compression for frontend assets. !11798
+- Add tag_list param to project api. !11799 (Ivan Chernov)
+- Add changelog for improved Registry description. !11816
+- Automatically adjust project settings to match changes in project visibility. !11831
+- Add slugify project path to CI enviroment variables. !11838 (Ivan Chernov)
+- Add all pipeline sources as special keywords to 'only' and 'except'. !11844 (Filip Krakowski)
+- Allow pulling of container images using personal access tokens. !11845
+- Expose import_status in Projects API. !11851 (Robin Bobbitt)
+- Allow admins to delete users from the admin users page. !11852
+- Allow users to be hard-deleted from the API. !11853
+- Fix hard-deleting users when they have authored issues. !11855
+- Fix missing optional path parameter in "Create project for user" API. !11868
+- Allow users to be hard-deleted from the admin panel. !11874
+- Add a Rake task to aid in rotating otp_key_base. !11881
+- Fix submodule link to then project under subgroup. !11906
+- Fix binary encoding error on MR diffs. !11929
+- Limit non-administrators to adding 100 members at a time to groups and projects. !11940
+- add bulgarian translation of cycle analytics page to I18N. !11958 (Lyubomir Vasilev)
+- Make backup task to continue on corrupt repositories. !11962
+- Fix incorrect ETag cache key when relative instance URL is used. !11964
+- Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm)
+- Fix edit button for deploy keys available from other projects. !12301 (Alexander Randa)
+- Fix passing CI_ENVIRONMENT_NAME and CI_ENVIRONMENT_SLUG for CI_ENVIRONMENT_URL. !12344
+- Disable environment list refresh due to bug https://gitlab.com/gitlab-org/gitlab-ee/issues/2677. !12347
+- Standardize timeline note margins across different viewport sizes. !12364
+- Fix Ordered Task List Items. !31483 (Jared Deckard )
+- Upgrade dependency to Go 1.8.3. !31943
+- Add prometheus metrics on pipeline creation.
+- Fix etag route not being a match for environments.
+- Sort folder for environments.
+- Support descriptions for snippets.
+- Hide clone panel and file list when user is only a guest. (James Clark)
+- Don’t create comment on JIRA if it already exists for the entity.
+- Update Dashboard Groups UI with better support for subgroups.
+- Confirm Project forking behaviour via the API.
+- Add prometheus based metrics collection to gitlab webapp.
+- Fix: Wiki is not searchable with Guest permissions.
+- Center all empty states.
+- Remove 'New issue' button when issues search returns no results.
+- Add API URL to JIRA settings.
+- animate adding issue to boards.
+- Update session cookie key name to be unique to instance in development.
+- Single click on filter to open filtered search dropdown.
+- Makes header information of pipeline show page realtine.
+- Creates a mediator for pipeline details vue in order to mount several vue apps with the same data.
+- Scope issue/merge request recent searches to project.
+- Increase individual diff collapse limit to 100 KB, and render limit to 200 KB.
+- Fix Pipelines table empty state - only render empty state if we receive 0 pipelines.
+- Make New environment empty state btn lowercase.
+- Removes duplicate environment variable in documentation.
+- Change links in issuable meta to black.
+- Fix border-bottom for project activity tab.
+- Adds new icon for CI skipped status.
+- Create equal padding for emoji.
+- Use briefcase icon for company in profile page.
+- Remove overflow from comment form for confidential issues and vertically aligns confidential issue icon.
+- Keep trailing newline when resolving conflicts by picking sides.
+- Fix /unsubscribe slash command creating extra todos when you were already mentioned in an issue.
+- Fix math rendering on blob pages.
+- Allow group reporters to manage group labels.
+- Use pre-wrap for commit messages to keep lists indented.
+- Count badges depend on translucent color to better adjust to different background colors and permission badges now feature a pill shaped design similar to labels.
+- Allow reporters to promote project labels to group labels.
+- Enabled keyboard shortcuts on artifacts pages.
+- Perform filtered search when state tab is changed.
+- Remove duplication for sharing projects with groups in project settings.
+- Change order of commits ahead and behind on divergence graph for branch list view.
+- Creates CI Header component for Pipelines and Jobs details pages.
+- Invalidate cache for issue and MR counters more granularly.
+- disable blocked manual actions.
+- Load tree readme asynchronously.
+- Display extra info about files on .gitlab-ci.yml, .gitlab/route-map.yml and LICENSE blob pages.
+- Fix replying to a commit discussion displayed in the context of an MR.
+- Consistently use monospace font for commit SHAs and branch and tag names.
+- Consistently display last push event widget.
+- Don't copy empty elements that were not selected on purpose as GFM.
+- Copy as GFM even when parts of other elements are selected.
+- Autolink package names in Gemfile.
+- Resolve N+1 query issue with discussions.
+- Don't match email addresses or foo@bar as user references.
+- Fix title of discussion jump button at top of page.
+- Don't return nil for missing objects from parser cache.
+- Make .gitmodules parsing more resilient to syntax errors.
+- Add username parameter to gravatar URL.
+- Autolink package names in more dependency files.
+- Return nil when looking up config for unknown LDAP provider.
+- Add system note with link to diff comparison when MR discussion becomes outdated.
+- Don't wrap pasted code when it's already inside code tags.
+- Revert 'New file from interface on existing branch'.
+- Show last commit for current tree on tree page.
+- Add documentation about adding foreign keys.
+- add username field to push webhook. (David Turner)
+- Rename CI/CD Pipelines to Pipelines in the project settings.
+- Make environment tables responsive.
+- Expand/collapse backlog & closed lists in issue boards.
+- Fix GitHub importer performance on branch existence check.
+- Fix counter cache for acts as taggable.
+- Github - Fix token interpolation when cloning wiki repository.
+- Fix token interpolation when setting the Github remote.
+- Fix N+1 queries for non-members in comment threads.
+- Fix terminals support for Kubernetes Service.
+- Fix: A diff comment on a change at last line of a file shows as two comments in discussion.
+- Instrument MergeRequestDiff#load_commits.
+- Introduce source to Pipeline entity.
+- Fixed create new label form in issue form not working for sub-group projects.
+- Fixed style on unsubscribe page. (Gustav Ernberg)
+- Enables inline editing for an issues title & description.
+- Ask for an example project for bug reports.
+- Add summary lines for collapsed details in the bug report template.
+- Prevent commits from upstream repositories to be re-processed by forks.
+- Avoid repeated queries for pipeline builds on merge requests.
+- Preloads head pipeline for merge request collection.
+- Handle head pipeline when creating merge requests.
+- Migrate artifacts to a new path.
+- Rescue OpenSSL::SSL::SSLError in JiraService & IssueTrackerService.
+- Repository browser: handle in-repository submodule urls. (David Turner)
+- Prevent project transfers if a new group is not selected.
+- Allow 'no one' as an option for allowed to merge on a procted branch.
+- Reduce time spent waiting for certain Sidekiq jobs to complete.
+- Refactor ProjectsFinder#init_collection to produce more efficient queries for retrieving projects.
+- Remove unused code and uses underscore.
+- Restricts search projects dropdown to group projects when group is selected.
+- Properly handle container registry redirects to fix metadata stored on a S3 backend.
+- Fix LFS timeouts when trying to save large files.
+- Set artifact working directory to be in the destination store to prevent unnecessary I/O.
+- Strip trailing whitespaces in submodule URLs.
+- Make sure reCAPTCHA configuration is loaded when spam checks are initiated.
+- Fix up arrow not editing last discussion comment.
+- Added application readiness endpoints to the monitoring health check admin view.
+- Use wait_for_requests for both ajax and Vue requests.
+- Cleanup ci_variables schema and table.
+- Remove foreigh key on ci_trigger_schedules only if it exists.
+- Allow translation of Pipeline Schedules.
 
 ## 9.2.9 (2017-07-20)
 
@@ -262,6 +964,42 @@ entry.
 - Fix preemptive scroll bar on user activity calendar.
 - Pipeline chat notifications convert seconds to minutes and hours.
 
+## 9.1.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.1.8 (2017-07-19)
+
+- Improve support for external issue references. !12485
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
+## 9.1.7 (2017-06-07)
+
+- No changes.
+
+## 9.1.6 (2017-06-02)
+
+- Fix visibility when referencing snippets.
+
+## 9.1.5 (2017-05-31)
+
+- Move uploads from 'public/uploads' to 'public/uploads/system'.
+- Restrict API X-Frame-Options to same origin.
+- Allow users autocomplete by author_id only for authenticated users.
+
+## 9.1.4 (2017-05-12)
+
+- Fix error on CI/CD Settings page related to invalid pipeline trigger. !10948 (dosuken123)
+- Sort the network graph both by commit date and topographically. !11057
+- Fix cross referencing for private and internal projects. !11243
+- Handle incoming emails from aliases correctly.
+- Gracefully handle failures for incoming emails which do not match on the To header, and have no References header.
+- Add missing project attributes to Import/Export.
+- Fixed search terms not correctly highlighting.
+- Fixed bug where merge request JSON would be displayed.
+
 ## 9.1.3 (2017-05-05)
 
 - Do not show private groups on subgroups page if user doesn't have access to.
@@ -302,6 +1040,7 @@ entry.
 
 ## 9.1.0 (2017-04-22)
 
+- Add Jupyter notebook rendering !10017
 - Added merge requests empty state. !7342
 - Add option to start a new resolvable discussion in an MR. !7527
 - Hide form inputs for group member without editing rights. !7816
@@ -548,6 +1287,30 @@ entry.
 - Only send chat notifications for the default branch.
 - Don't fill in the default kubernetes namespace.
 
+## 9.0.12 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.0.11 (2017-07-19)
+
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
+## 9.0.10 (2017-06-07)
+
+- No changes.
+
+## 9.0.9 (2017-06-02)
+
+- Fix visibility when referencing snippets.
+
+## 9.0.8 (2017-05-31)
+
+- Move uploads from 'public/uploads' to 'public/uploads/system'.
+- Restrict API X-Frame-Options to same origin.
+- Allow users autocomplete by author_id only for authenticated users.
+
 ## 9.0.7 (2017-05-05)
 
 - Enforce project features when searching blobs and wikis.
@@ -904,6 +1667,11 @@ entry.
 - Change development tanuki favicon colors to match logo color order.
 - API issues - support filtering by iids.
 
+## 8.17.7 (2017-07-19)
+
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+
 ## 8.17.6 (2017-05-05)
 
 - Enforce project features when searching blobs and wikis.
@@ -1151,8 +1919,6 @@ entry.
 
 ## 8.16.7 (2017-02-27)
 
-- No changes.
-- No changes.
 - Fix MR changes tab size count when there are over 100 files in the diff.
 
 ## 8.16.6 (2017-02-17)
@@ -1366,6 +2132,14 @@ entry.
 - Prevent the GitHub importer from assigning labels and comments to merge requests or issues belonging to other projects.
 - Patch XSS vulnerability in RDOC support.
 
+## 8.15.5 (2017-01-20)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
 ## 8.15.4 (2017-01-09)
 
 - Make successful pipeline emails off for watchers. !8176
@@ -1648,6 +2422,14 @@ entry.
 - Speed up group milestone index by passing group_id to IssuesFinder. !8363
 - Ensure issuable state changes only fire webhooks once.
 
+## 8.14.7 (2017-01-21)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
 ## 8.14.6 (2017-01-10)
 
 - Update the gitlab-markup gem to the version 1.5.1. !8509
@@ -1930,6 +2712,14 @@ entry.
 - Fix "Without projects" filter. !6611 (Ben Bodenmiller)
 - Fix 404 when visit /projects page
 
+## 8.13.12 (2017-01-21)
+
+- Ensure export files are removed after a namespace is deleted.
+- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
+- Prevent users from creating notes on resources they can't access.
+- Prevent users from deleting system deploy keys via the project deploy key API.
+- Upgrade omniauth gem to 1.3.2.
+
 ## 8.13.11 (2017-01-10)
 
 - Update the gitlab-markup gem to the version 1.5.1. !8509
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8b6c87ae51..12fb34b24b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -31,7 +31,7 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
   - [Issue tracker guidelines](#issue-tracker-guidelines)
   - [Issue weight](#issue-weight)
   - [Regression issues](#regression-issues)
-  - [Technical debt](#technical-debt)
+  - [Technical and UX debt](#technical-and-ux-debt)
   - [Stewardship](#stewardship)
 - [Merge requests](#merge-requests)
   - [Merge request guidelines](#merge-request-guidelines)
@@ -49,6 +49,8 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
 Thank you for your interest in contributing to GitLab. This guide details how
 to contribute to GitLab in a way that is efficient for everyone.
 
+Looking for something to work on? Look for the label [Accepting Merge Requests](#i-want-to-contribute).
+
 GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
 source edition, and GitLab Enterprise Edition (EE) which is our commercial
 edition. Throughout this guide you will see references to CE and EE for
@@ -112,8 +114,8 @@ scheduling into milestones. Labelling is a task for everyone.
 Most issues will have labels for at least one of the following:
 
 - Type: ~"feature proposal", ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, etc.
-- Team: ~CI, ~Discussion, ~Edge, ~Frontend, ~Platform, etc.
+- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Team: ~CI, ~Discussion, ~Edge, ~Platform, etc.
 - Priority: ~Deliverable, ~Stretch
 
 All labels, their meaning and priority are defined on the
@@ -276,7 +278,7 @@ For feature proposals for EE, open an issue on the
 In order to help track the feature proposals, we have created a
 [`feature proposal`][fpl] label. For the time being, users that are not members
 of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label `feature proposal` to the issue or add the following
+members to add the label ~"feature proposal" to the issue or add the following
 code snippet right after your description in a new line: `~"feature proposal"`.
 
 Please keep feature proposals as small and simple as possible, complex ones
@@ -342,27 +344,29 @@ addressed.
 [8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
 [update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
 
-### Technical debt
+### Technical and UX debt
 
-In order to track things that can be improved in GitLab's codebase, we created
-the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+In order to track things that can be improved in GitLab's codebase,
+we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+For user experience improvements, we use the ~"UX debt" label.
 
-This label should be added to issues that describe things that can be improved,
-shortcuts that have been taken, code that needs refactoring, features that need
-additional attention, and all other things that have been left behind due to
-high velocity of development.
+These labels should be added to issues that describe things that can be improved,
+shortcuts that have been taken, features that need additional attention, and all
+other things that have been left behind due to high velocity of development.
+For example, code that needs refactoring should use the ~"technical debt" label,
+user experience refinements should use the ~"UX debt" label.
 
 Everyone can create an issue, though you may need to ask for adding a specific
 label, if you do not have permissions to do it by yourself. Additional labels
-can be combined with the `technical debt` label, to make it easier to schedule
+can be combined with these labels, to make it easier to schedule
 the improvements for a release.
 
-Issues tagged with the `technical debt` label have the same priority like issues
+Issues tagged with these labels have the same priority like issues
 that describe a new feature to be introduced in GitLab, and should be scheduled
 for a release by the appropriate person.
 
-Make sure to mention the merge request that the `technical debt` issue is
-associated with in the description of the issue.
+Make sure to mention the merge request that the ~"technical debt" issue or
+~"UX debt" issue is associated with in the description of the issue.
 
 ### Stewardship
 
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 78bc1abd14..7b52f5e517 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.10.0
+0.35.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 2b7c5ae018..4b9fcbec10 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.4.2
+0.5.1
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 2d6c0bcf19..11d9efa3d5 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.0.4
+5.8.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 227cea2156..4a36342fca 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-2.0.0
+3.0.0
diff --git a/Gemfile b/Gemfile
index 4edd09f545..1610881f74 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,12 +12,13 @@ gem 'sprockets', '~> 3.7.0'
 gem 'default_value_for', '~> 3.0.0'
 
 # Supported DBs
-gem 'mysql2', '~> 0.3.16', group: :mysql
+gem 'mysql2', '~> 0.4.5', group: :mysql
 gem 'pg', '~> 0.18.2', group: :postgres
 
-gem 'rugged', '~> 0.25.1.1'
+gem 'rugged', '~> 0.26.0'
+gem 'grape-route-helpers', '~> 2.0.0'
 
-gem 'faraday', '~> 0.11.0'
+gem 'faraday', '~> 0.12'
 
 # Authentication libraries
 gem 'devise', '~> 4.2'
@@ -26,7 +27,7 @@ gem 'doorkeeper-openid_connect', '~> 1.1.0'
 gem 'omniauth', '~> 1.4.2'
 gem 'omniauth-auth0', '~> 1.4.1'
 gem 'omniauth-azure-oauth2', '~> 0.0.6'
-gem 'omniauth-cas3', '~> 1.1.2'
+gem 'omniauth-cas3', '~> 1.1.4'
 gem 'omniauth-facebook', '~> 4.0.0'
 gem 'omniauth-github', '~> 1.1.1'
 gem 'omniauth-gitlab', '~> 1.0.2'
@@ -37,7 +38,7 @@ gem 'omniauth-saml', '~> 1.7.0'
 gem 'omniauth-shibboleth', '~> 1.2.0'
 gem 'omniauth-twitter', '~> 1.2.0'
 gem 'omniauth_crowd', '~> 2.2.0'
-gem 'omniauth-authentiq', '~> 0.3.0'
+gem 'omniauth-authentiq', '~> 0.3.1'
 gem 'rack-oauth2', '~> 1.2.1'
 gem 'jwt', '~> 1.5.6'
 
@@ -57,10 +58,14 @@ gem 'validates_hostname', '~> 1.0.6'
 # Browser detection
 gem 'browser', '~> 2.2'
 
+# GPG
+gem 'gpgme'
+
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
 # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
+gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
+gem 'net-ldap'
 
 # Git Wiki
 # Required manually in config/initializers/gollum.rb to control load order
@@ -71,7 +76,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
 gem 'github-linguist', '~> 4.7.0', require: 'linguist'
 
 # API
-gem 'grape', '~> 0.19.0'
+gem 'grape', '~> 0.19.2'
 gem 'grape-entity', '~> 0.6.0'
 gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
 
@@ -79,24 +84,25 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
 gem 'hashie-forbidden_attributes'
 
 # Pagination
-gem 'kaminari', '~> 0.17.0'
+gem 'kaminari', '~> 1.0'
 
 # HAML
 gem 'hamlit', '~> 2.6.1'
 
 # Files attachments
-gem 'carrierwave', '~> 1.0'
+gem 'carrierwave', '~> 1.1'
 
 # Drag and Drop UI
 gem 'dropzonejs-rails', '~> 0.7.1'
 
 # for backups
-gem 'fog-aws', '~> 0.9'
+gem 'fog-aws', '~> 1.4'
 gem 'fog-core', '~> 1.44'
 gem 'fog-google', '~> 0.5'
 gem 'fog-local', '~> 0.3'
 gem 'fog-openstack', '~> 0.1'
 gem 'fog-rackspace', '~> 0.1.1'
+gem 'fog-aliyun', '~> 0.1.0'
 
 # for Google storage
 gem 'google-api-client', '~> 0.8.6'
@@ -109,7 +115,7 @@ gem 'seed-fu', '~> 2.3.5'
 
 # Markdown and HTML processing
 gem 'html-pipeline', '~> 1.11.0'
-gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
+gem 'deckar01-task_list', '2.0.0'
 gem 'gitlab-markup', '~> 1.5.1'
 gem 'redcarpet', '~> 3.4'
 gem 'RedCloth', '~> 4.3.2'
@@ -120,11 +126,9 @@ gem 'wikicloth', '0.8.1'
 gem 'asciidoctor', '~> 1.5.2'
 gem 'asciidoctor-plantuml', '0.0.7'
 gem 'rouge', '~> 2.0'
-gem 'truncato', '~> 0.7.8'
-
-# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
-# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
-gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
+gem 'truncato', '~> 0.7.9'
+gem 'bootstrap_form', '~> 2.7.0'
+gem 'nokogiri', '~> 1.8.0'
 
 # Diffs
 gem 'diffy', '~> 3.1.0'
@@ -145,24 +149,24 @@ gem 'acts-as-taggable-on', '~> 4.0'
 
 # Background jobs
 gem 'sidekiq', '~> 5.0'
-gem 'sidekiq-cron', '~> 0.4.4'
+gem 'sidekiq-cron', '~> 0.6.0'
 gem 'redis-namespace', '~> 1.5.2'
-gem 'sidekiq-limit_fetch', '~> 3.4'
+gem 'sidekiq-limit_fetch', '~> 3.4', require: false
 
 # Cron Parser
-gem 'rufus-scheduler', '~> 3.1.10'
+gem 'rufus-scheduler', '~> 3.4'
 
 # HTTP requests
 gem 'httparty', '~> 0.13.3'
 
 # Colored output to console
-gem 'rainbow', '~> 2.1.0'
+gem 'rainbow', '~> 2.2'
 
 # GitLab settings
 gem 'settingslogic', '~> 2.0.9'
 
 # Linear-time regex library for untrusted regular expressions
-gem 're2', '~> 1.0.0'
+gem 're2', '~> 1.1.1'
 
 # Misc
 
@@ -238,38 +242,52 @@ gem 'webpack-rails', '~> 0.9.10'
 gem 'rack-proxy', '~> 0.6.0'
 
 gem 'sass-rails', '~> 5.0.6'
-gem 'coffee-rails', '~> 4.1.0'
 gem 'uglifier', '~> 2.7.2'
 
 gem 'addressable', '~> 2.3.8'
 gem 'bootstrap-sass', '~> 3.3.0'
 gem 'font-awesome-rails', '~> 4.7'
-gem 'gemojione', '~> 3.0'
+gem 'gemojione', '~> 3.3'
 gem 'gon', '~> 6.1.0'
 gem 'jquery-atwho-rails', '~> 1.3.2'
 gem 'jquery-rails', '~> 4.1.0'
 gem 'request_store', '~> 1.3'
 gem 'select2-rails', '~> 3.5.9'
 gem 'virtus', '~> 1.0.1'
-gem 'net-ssh', '~> 3.0.1'
 gem 'base32', '~> 0.3.0'
 
 # Sentry integration
-gem 'sentry-raven', '~> 2.4.0'
+gem 'sentry-raven', '~> 2.5.3'
 
-gem 'premailer-rails', '~> 1.9.0'
+gem 'premailer-rails', '~> 1.9.7'
 
 # I18n
-gem 'ruby_parser', '~> 3.8.4', require: false
+gem 'ruby_parser', '~> 3.8', require: false
+gem 'rails-i18n', '~> 4.0.9'
 gem 'gettext_i18n_rails', '~> 1.8.0'
 gem 'gettext_i18n_rails_js', '~> 1.2.0'
 gem 'gettext', '~> 3.2.2', require: false, group: :development
 
+# Perf bar
+gem 'peek', '~> 1.0.1'
+gem 'peek-gc', '~> 0.0.2'
+gem 'peek-host', '~> 1.0.0'
+gem 'peek-mysql2', '~> 1.1.0', group: :mysql
+gem 'peek-performance_bar', '~> 1.3.0'
+gem 'peek-pg', '~> 1.3.0', group: :postgres
+gem 'peek-rblineprof', '~> 0.2.0'
+gem 'peek-redis', '~> 1.2.0'
+gem 'peek-sidekiq', '~> 1.0.3'
+
 # Metrics
 group :metrics do
   gem 'allocations', '~> 1.0', require: false, platform: :mri
   gem 'method_source', '~> 0.8', require: false
   gem 'influxdb', '~> 0.2', require: false
+
+  # Prometheus
+  gem 'prometheus-client-mmap', '~>0.7.0.beta14'
+  gem 'raindrops', '~> 0.18'
 end
 
 group :development do
@@ -293,11 +311,11 @@ group :development, :test do
   gem 'pry-rails', '~> 0.3.4'
 
   gem 'awesome_print', '~> 1.2.0', require: false
-  gem 'fuubar', '~> 2.0.0'
+  gem 'fuubar', '~> 2.2.0'
 
   gem 'database_cleaner', '~> 1.5.0'
   gem 'factory_girl_rails', '~> 4.7.0'
-  gem 'rspec-rails', '~> 3.5.0'
+  gem 'rspec-rails', '~> 3.6.0'
   gem 'rspec-retry', '~> 0.4.5'
   gem 'spinach-rails', '~> 0.2.1'
   gem 'spinach-rerun-reporter', '~> 0.0.2'
@@ -318,10 +336,10 @@ group :development, :test do
   gem 'spring-commands-rspec', '~> 1.0.4'
   gem 'spring-commands-spinach', '~> 1.1.0'
 
-  gem 'rubocop', '~> 0.47.1', require: false
-  gem 'rubocop-rspec', '~> 1.15.0', require: false
-  gem 'scss_lint', '~> 0.47.0', require: false
-  gem 'haml_lint', '~> 0.21.0', require: false
+  gem 'rubocop', '~> 0.49.1', require: false
+  gem 'rubocop-rspec', '~> 1.15.1', require: false
+  gem 'scss_lint', '~> 0.54.0', require: false
+  gem 'haml_lint', '~> 0.26.0', require: false
   gem 'simplecov', '~> 0.14.0', require: false
   gem 'flay', '~> 2.8.0', require: false
   gem 'bundler-audit', '~> 0.5.0', require: false
@@ -337,10 +355,10 @@ group :development, :test do
 end
 
 group :test do
-  gem 'shoulda-matchers', '~> 2.8.0', require: false
+  gem 'shoulda-matchers', '~> 3.1.2', require: false
   gem 'email_spec', '~> 1.6.0'
   gem 'json-schema', '~> 2.6.2'
-  gem 'webmock', '~> 1.24.0'
+  gem 'webmock', '~> 2.3.2'
   gem 'test_after_commit', '~> 1.1'
   gem 'sham_rack', '~> 1.3.6'
   gem 'timecop', '~> 0.8.0'
@@ -357,10 +375,10 @@ gem 'html2text'
 gem 'ruby-prof', '~> 0.16.2'
 
 # OAuth
-gem 'oauth2', '~> 1.3.0'
+gem 'oauth2', '~> 1.4'
 
 # Soft deletion
-gem 'paranoia', '~> 2.2'
+gem 'paranoia', '~> 2.3.1'
 
 # Health check
 gem 'health_check', '~> 2.6.0'
@@ -369,7 +387,24 @@ gem 'health_check', '~> 2.6.0'
 gem 'vmstat', '~> 2.3.0'
 gem 'sys-filesystem', '~> 1.1.6'
 
+# SSH host key support
+gem 'net-ssh', '~> 4.1.0'
+
+# Required for ED25519 SSH host key support
+group :ed25519 do
+  gem 'rbnacl-libsodium'
+  gem 'rbnacl', '~> 3.2'
+  gem 'bcrypt_pbkdf', '~> 1.0'
+end
+
 # Gitaly GRPC client
-gem 'gitaly', '~> 0.6.0'
+gem 'gitaly', '~> 0.26.0'
 
 gem 'toml-rb', '~> 0.3.15', require: false
+
+# Feature toggles
+gem 'flipper', '~> 0.10.2'
+gem 'flipper-active_record', '~> 0.10.2'
+
+# Structured logging
+gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index d437274f62..9a7cbaf849 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -56,6 +56,7 @@ GEM
     asciidoctor-plantuml (0.0.7)
       asciidoctor (~> 1.5)
     ast (2.3.0)
+    atomic (1.1.99)
     attr_encrypted (3.0.3)
       encryptor (~> 3.0.0)
     attr_required (1.0.0)
@@ -74,6 +75,7 @@ GEM
     babosa (1.0.2)
     base32 (0.3.2)
     bcrypt (3.1.11)
+    bcrypt_pbkdf (1.0.0)
     benchmark-ips (2.3.0)
     better_errors (2.1.1)
       coderay (>= 1.0.0)
@@ -85,6 +87,7 @@ GEM
     bootstrap-sass (3.3.6)
       autoprefixer-rails (>= 5.2.1)
       sass (>= 3.3.4)
+    bootstrap_form (2.7.0)
     brakeman (3.6.1)
     browser (2.2.0)
     builder (3.2.3)
@@ -105,7 +108,7 @@ GEM
     capybara-screenshot (1.0.14)
       capybara (>= 1.0, < 3)
       launchy
-    carrierwave (1.0.0)
+    carrierwave (1.1.0)
       activemodel (>= 4.0.0)
       activesupport (>= 4.0.0)
       mime-types (>= 1.16)
@@ -120,20 +123,15 @@ GEM
     coderay (1.1.1)
     coercible (1.0.0)
       descendants_tracker (~> 0.0.1)
-    coffee-rails (4.1.1)
-      coffee-script (>= 2.2.0)
-      railties (>= 4.0.0, < 5.1.x)
-    coffee-script (2.4.1)
-      coffee-script-source
-      execjs
-    coffee-script-source (1.10.0)
     colorize (0.7.7)
     concurrent-ruby (1.0.5)
+    concurrent-ruby-ext (1.0.5)
+      concurrent-ruby (= 1.0.5)
     connection_pool (2.2.1)
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
     creole (0.5.0)
-    css_parser (1.4.1)
+    css_parser (1.5.0)
       addressable
     d3_rails (3.5.11)
       railties (>= 3.1.0)
@@ -141,10 +139,8 @@ GEM
     database_cleaner (1.5.3)
     debug_inspector (0.0.2)
     debugger-ruby_core_source (1.3.8)
-    deckar01-task_list (1.0.6)
-      activesupport (~> 4.0)
+    deckar01-task_list (2.0.0)
       html-pipeline
-      rack (~> 1.0)
     default_value_for (3.0.2)
       activerecord (>= 3.2.0, < 5.1)
     descendants_tracker (0.0.4)
@@ -161,7 +157,7 @@ GEM
       devise (~> 4.0)
       railties
       rotp (~> 2.0)
-    diff-lcs (1.2.5)
+    diff-lcs (1.3)
     diffy (3.1.0)
     docile (1.1.5)
     domain_name (0.5.20161021)
@@ -181,8 +177,10 @@ GEM
     equalizer (0.0.11)
     erubis (2.7.0)
     escape_utils (1.1.1)
+    et-orbi (1.0.3)
+      tzinfo
     eventmachine (1.0.8)
-    excon (0.55.0)
+    excon (0.57.1)
     execjs (2.6.0)
     expression_parser (0.9.0)
     extlib (0.9.16)
@@ -191,7 +189,7 @@ GEM
     factory_girl_rails (4.7.0)
       factory_girl (~> 4.7.0)
       railties (>= 3.0.0)
-    faraday (0.11.0)
+    faraday (0.12.1)
       multipart-post (>= 1.2, < 3)
     faraday_middleware (0.11.0.1)
       faraday (>= 0.7.4, < 1.0)
@@ -206,29 +204,38 @@ GEM
       path_expander (~> 1.0)
       ruby_parser (~> 3.0)
       sexp_processor (~> 4.0)
+    flipper (0.10.2)
+    flipper-active_record (0.10.2)
+      activerecord (>= 3.2, < 6)
+      flipper (~> 0.10.2)
     flowdock (0.7.1)
       httparty (~> 0.7)
       multi_json
-    fog-aws (0.13.0)
+    fog-aliyun (0.1.0)
+      fog-core (~> 1.27)
+      fog-json (~> 1.0)
+      ipaddress (~> 0.8)
+      xml-simple (~> 1.1)
+    fog-aws (1.4.0)
       fog-core (~> 1.38)
       fog-json (~> 1.0)
       fog-xml (~> 0.1)
       ipaddress (~> 0.8)
-    fog-core (1.44.1)
+    fog-core (1.44.3)
       builder
       excon (~> 0.49)
       formatador (~> 0.2)
-    fog-google (0.5.0)
+    fog-google (0.5.3)
       fog-core
       fog-json
       fog-xml
     fog-json (1.0.2)
       fog-core (~> 1.0)
       multi_json (~> 1.10)
-    fog-local (0.3.0)
+    fog-local (0.3.1)
       fog-core (~> 1.27)
-    fog-openstack (0.1.6)
-      fog-core (>= 1.39)
+    fog-openstack (0.1.21)
+      fog-core (>= 1.40)
       fog-json (>= 1.0)
       ipaddress (>= 0.8)
     fog-rackspace (0.1.1)
@@ -244,12 +251,12 @@ GEM
     foreman (0.78.0)
       thor (~> 0.19.1)
     formatador (0.2.5)
-    fuubar (2.0.0)
-      rspec (~> 3.0)
+    fuubar (2.2.0)
+      rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
     gemnasium-gitlab-service (0.2.6)
       rugged (~> 0.21)
-    gemojione (3.0.1)
+    gemojione (3.3.0)
       json
     get_process_mem (0.2.0)
     gettext (3.2.2)
@@ -263,7 +270,7 @@ GEM
       po_to_json (>= 1.0.0)
       rails (>= 3.2.0)
     gherkin-ruby (0.3.2)
-    gitaly (0.6.0)
+    gitaly (0.26.0)
       google-protobuf (~> 3.1)
       grpc (~> 1.0)
     github-linguist (4.7.6)
@@ -271,7 +278,7 @@ GEM
       escape_utils (~> 1.1.0)
       mime-types (>= 1.19)
       rugged (>= 0.23.0b)
-    github-markup (1.4.0)
+    github-markup (1.6.1)
     gitlab-flowdock-git-hook (1.0.1)
       flowdock (~> 0.7)
       gitlab-grit (>= 2.4.1)
@@ -282,22 +289,23 @@ GEM
       mime-types (>= 1.16, < 3)
       posix-spawn (~> 0.3)
     gitlab-markup (1.5.1)
-    gitlab_omniauth-ldap (1.2.1)
-      net-ldap (~> 0.9)
-      omniauth (~> 1.0)
-      pyu-ruby-sasl (~> 0.0.3.1)
-      rubyntlm (~> 0.3)
+    gitlab_omniauth-ldap (2.0.3)
+      net-ldap (~> 0.16)
+      omniauth (~> 1.3)
+      pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
+      rubyntlm (~> 0.5)
     globalid (0.3.7)
       activesupport (>= 4.1.0)
     gollum-grit_adapter (1.0.1)
       gitlab-grit (~> 2.7, >= 2.7.1)
-    gollum-lib (4.2.1)
-      github-markup (~> 1.4.0)
+    gollum-lib (4.2.7)
+      gemojione (~> 3.2)
+      github-markup (~> 1.6)
       gollum-grit_adapter (~> 1.0)
-      nokogiri (~> 1.6.4)
-      rouge (~> 2.0)
-      sanitize (~> 2.1.0)
-      stringex (~> 2.5.1)
+      nokogiri (>= 1.6.1, < 2.0)
+      rouge (~> 2.1)
+      sanitize (~> 2.1)
+      stringex (~> 2.6)
     gollum-rugged_adapter (0.4.4)
       mime-types (>= 1.15)
       rugged (~> 0.25)
@@ -317,7 +325,7 @@ GEM
       multi_json (~> 1.10)
       retriable (~> 1.4)
       signet (~> 0.6)
-    google-protobuf (3.2.0.2)
+    google-protobuf (3.3.0)
     googleauth (0.5.1)
       faraday (~> 0.9)
       jwt (~> 1.4)
@@ -326,34 +334,41 @@ GEM
       multi_json (~> 1.11)
       os (~> 0.9)
       signet (~> 0.7)
-    grape (0.19.1)
+    gpgme (2.0.13)
+      mini_portile2 (~> 2.1)
+    grape (0.19.2)
       activesupport
       builder
       hashie (>= 2.1.0)
       multi_json (>= 1.3.2)
       multi_xml (>= 0.5.2)
-      mustermann-grape (~> 0.4.0)
+      mustermann-grape (~> 1.0.0)
       rack (>= 1.3.0)
       rack-accept
       virtus (>= 1.0.0)
     grape-entity (0.6.0)
       activesupport
       multi_json (>= 1.3.2)
-    grpc (1.2.5)
+    grape-route-helpers (2.0.0)
+      activesupport
+      grape (~> 0.16, >= 0.16.0)
+      rake
+    grpc (1.4.0)
       google-protobuf (~> 3.1)
       googleauth (~> 0.5.1)
     haml (4.0.7)
       tilt
-    haml_lint (0.21.0)
-      haml (~> 4.0)
+    haml_lint (0.26.0)
+      haml (>= 4.0, < 5.1)
+      rainbow
       rake (>= 10, < 13)
-      rubocop (>= 0.47.0)
+      rubocop (>= 0.49.0)
       sysexits (~> 1.1)
     hamlit (2.6.1)
       temple (~> 0.7.6)
       thor
       tilt
-    hashdiff (0.3.2)
+    hashdiff (0.3.4)
     hashie (3.5.5)
     hashie-forbidden_attributes (0.1.1)
       hashie (>= 3.0)
@@ -381,7 +396,7 @@ GEM
       json (~> 1.8)
       multi_xml (>= 0.5.2)
     httpclient (2.8.2)
-    i18n (0.8.1)
+    i18n (0.8.6)
     ice_nine (0.11.2)
     influxdb (0.2.3)
       cause
@@ -405,9 +420,18 @@ GEM
     json-schema (2.6.2)
       addressable (~> 2.3.8)
     jwt (1.5.6)
-    kaminari (0.17.0)
-      actionpack (>= 3.0.0)
-      activesupport (>= 3.0.0)
+    kaminari (1.0.1)
+      activesupport (>= 4.1.0)
+      kaminari-actionview (= 1.0.1)
+      kaminari-activerecord (= 1.0.1)
+      kaminari-core (= 1.0.1)
+    kaminari-actionview (1.0.1)
+      actionview
+      kaminari-core (= 1.0.1)
+    kaminari-activerecord (1.0.1)
+      activerecord
+      kaminari-core (= 1.0.1)
+    kaminari-core (1.0.1)
     kgio (2.10.0)
     knapsack (1.11.0)
       rake
@@ -437,35 +461,39 @@ GEM
     logging (2.2.2)
       little-plugger (~> 1.1)
       multi_json (~> 1.10)
+    lograge (0.5.1)
+      actionpack (>= 4, < 5.2)
+      activesupport (>= 4, < 5.2)
+      railties (>= 4, < 5.2)
     loofah (2.0.3)
       nokogiri (>= 1.5.9)
-    mail (2.6.5)
+    mail (2.6.6)
       mime-types (>= 1.16, < 4)
     mail_room (0.9.1)
     memoist (0.15.0)
     method_source (0.8.2)
     mime-types (2.99.3)
     mimemagic (0.3.0)
-    mini_portile2 (2.1.0)
+    mini_portile2 (2.2.0)
     minitest (5.7.0)
+    mmap2 (2.2.7)
     mousetrap-rails (1.4.6)
     multi_json (1.12.1)
     multi_xml (0.6.0)
     multipart-post (2.0.0)
-    mustermann (0.4.0)
-      tool (~> 0.2)
-    mustermann-grape (0.4.0)
-      mustermann (= 0.4.0)
-    mysql2 (0.3.20)
-    net-ldap (0.12.1)
-    net-ssh (3.0.1)
+    mustermann (1.0.0)
+    mustermann-grape (1.0.0)
+      mustermann (~> 1.0.0)
+    mysql2 (0.4.5)
+    net-ldap (0.16.0)
+    net-ssh (4.1.0)
     netrc (0.11.0)
-    nokogiri (1.6.8.1)
-      mini_portile2 (~> 2.1.0)
+    nokogiri (1.8.0)
+      mini_portile2 (~> 2.2.0)
     numerizer (0.1.1)
     oauth (0.5.1)
-    oauth2 (1.3.1)
-      faraday (>= 0.8, < 0.12)
+    oauth2 (1.4.0)
+      faraday (>= 0.8, < 0.13)
       jwt (~> 1.0)
       multi_json (~> 1.3)
       multi_xml (~> 0.5)
@@ -478,15 +506,15 @@ GEM
       rack (>= 1.0, < 3)
     omniauth-auth0 (1.4.1)
       omniauth-oauth2 (~> 1.1)
-    omniauth-authentiq (0.3.0)
+    omniauth-authentiq (0.3.1)
       omniauth-oauth2 (~> 1.3, >= 1.3.1)
     omniauth-azure-oauth2 (0.0.6)
       jwt (~> 1.0)
       omniauth (~> 1.0)
       omniauth-oauth2 (~> 1.1)
-    omniauth-cas3 (1.1.3)
+    omniauth-cas3 (1.1.4)
       addressable (~> 2.3)
-      nokogiri (~> 1.6.6)
+      nokogiri (~> 1.7, >= 1.7.1)
       omniauth (~> 1.2)
     omniauth-facebook (4.0.0)
       omniauth-oauth2 (~> 1.2)
@@ -530,11 +558,42 @@ GEM
       rubypants (~> 0.2)
     orm_adapter (0.5.0)
     os (0.9.6)
-    paranoia (2.2.0)
-      activerecord (>= 4.0, < 5.1)
+    parallel (1.11.2)
+    paranoia (2.3.1)
+      activerecord (>= 4.0, < 5.2)
     parser (2.4.0.0)
       ast (~> 2.2)
     path_expander (1.0.1)
+    peek (1.0.1)
+      concurrent-ruby (>= 0.9.0)
+      concurrent-ruby-ext (>= 0.9.0)
+      railties (>= 4.0.0)
+    peek-gc (0.0.2)
+      peek
+    peek-host (1.0.0)
+      peek
+    peek-mysql2 (1.1.0)
+      atomic (>= 1.0.0)
+      mysql2
+      peek
+    peek-performance_bar (1.3.0)
+      peek (>= 0.1.0)
+    peek-pg (1.3.0)
+      concurrent-ruby
+      concurrent-ruby-ext
+      peek
+      pg
+    peek-rblineprof (0.2.0)
+      peek
+      rblineprof
+    peek-redis (1.2.0)
+      atomic (>= 1.0.0)
+      peek
+      redis
+    peek-sidekiq (1.0.3)
+      atomic (>= 1.0.0)
+      peek
+      sidekiq
     pg (0.18.4)
     po_to_json (1.0.1)
       json (>= 1.6.0)
@@ -543,14 +602,22 @@ GEM
       cliver (~> 0.3.1)
       multi_json (~> 1.0)
       websocket-driver (>= 0.2.0)
-    posix-spawn (0.3.11)
+    posix-spawn (0.3.13)
     powerpack (0.1.1)
-    premailer (1.8.6)
-      css_parser (>= 1.3.6)
+    premailer (1.10.4)
+      addressable
+      css_parser (>= 1.4.10)
       htmlentities (>= 4.0.0)
-    premailer-rails (1.9.2)
+    premailer-rails (1.9.7)
       actionmailer (>= 3, < 6)
       premailer (~> 1.7, >= 1.7.9)
+    proc_to_ast (0.1.0)
+      coderay
+      parser
+      unparser
+    procto (0.0.3)
+    prometheus-client-mmap (0.7.0.beta14)
+      mmap2 (~> 2.2, >= 2.2.7)
     pry (0.10.4)
       coderay (~> 1.1.0)
       method_source (~> 0.8.1)
@@ -561,7 +628,7 @@ GEM
     pry-rails (0.3.5)
       pry (>= 0.9.10)
     pyu-ruby-sasl (0.0.3.3)
-    rack (1.6.5)
+    rack (1.6.8)
     rack-accept (0.4.5)
       rack (>= 0.4)
     rack-attack (4.4.1)
@@ -598,19 +665,27 @@ GEM
       rails-deprecated_sanitizer (>= 1.0.1)
     rails-html-sanitizer (1.0.3)
       loofah (~> 2.0)
+    rails-i18n (4.0.9)
+      i18n (~> 0.7)
+      railties (~> 4.0)
     railties (4.2.8)
       actionpack (= 4.2.8)
       activesupport (= 4.2.8)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
-    rainbow (2.1.0)
-    raindrops (0.17.0)
-    rake (10.5.0)
+    rainbow (2.2.2)
+      rake
+    raindrops (0.18.0)
+    rake (12.0.0)
     rblineprof (0.3.6)
       debugger-ruby_core_source (~> 1.3)
+    rbnacl (3.4.0)
+      ffi
+    rbnacl-libsodium (1.0.11)
+      rbnacl (>= 3.0.1)
     rdoc (4.2.2)
       json (~> 1.4)
-    re2 (1.0.0)
+    re2 (1.1.1)
     recaptcha (3.0.0)
       json
     recursive-open-struct (1.0.0)
@@ -644,47 +719,44 @@ GEM
     retriable (1.4.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (2.0.7)
+    rouge (2.1.0)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
       rqrcode (>= 0.4.2)
-    rspec (3.5.0)
-      rspec-core (~> 3.5.0)
-      rspec-expectations (~> 3.5.0)
-      rspec-mocks (~> 3.5.0)
-    rspec-core (3.5.0)
-      rspec-support (~> 3.5.0)
-    rspec-expectations (3.5.0)
+    rspec-core (3.6.0)
+      rspec-support (~> 3.6.0)
+    rspec-expectations (3.6.0)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.5.0)
-    rspec-mocks (3.5.0)
+      rspec-support (~> 3.6.0)
+    rspec-mocks (3.6.0)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.5.0)
-    rspec-rails (3.5.0)
+      rspec-support (~> 3.6.0)
+    rspec-rails (3.6.0)
       actionpack (>= 3.0)
       activesupport (>= 3.0)
       railties (>= 3.0)
-      rspec-core (~> 3.5.0)
-      rspec-expectations (~> 3.5.0)
-      rspec-mocks (~> 3.5.0)
-      rspec-support (~> 3.5.0)
+      rspec-core (~> 3.6.0)
+      rspec-expectations (~> 3.6.0)
+      rspec-mocks (~> 3.6.0)
+      rspec-support (~> 3.6.0)
     rspec-retry (0.4.5)
       rspec-core
     rspec-set (0.1.3)
-    rspec-support (3.5.0)
+    rspec-support (3.6.0)
     rspec_profiling (0.0.5)
       activerecord
       pg
       rails
       sqlite3
-    rubocop (0.47.1)
+    rubocop (0.49.1)
+      parallel (~> 1.10)
       parser (>= 2.3.3.1, < 3.0)
       powerpack (~> 0.1)
       rainbow (>= 1.99.1, < 3.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (~> 1.0, >= 1.0.1)
-    rubocop-rspec (1.15.0)
+    rubocop-rspec (1.15.1)
       rubocop (>= 0.42.0)
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
@@ -692,13 +764,14 @@ GEM
     ruby-progressbar (1.8.1)
     ruby-saml (1.4.1)
       nokogiri (>= 1.5.10)
-    ruby_parser (3.8.4)
+    ruby_parser (3.9.0)
       sexp_processor (~> 4.1)
-    rubyntlm (0.5.2)
+    rubyntlm (0.6.2)
     rubypants (0.2.0)
     rubyzip (1.2.1)
-    rufus-scheduler (3.1.10)
-    rugged (0.25.1.1)
+    rufus-scheduler (3.4.0)
+      et-orbi (~> 1.0)
+    rugged (0.26.0)
     safe_yaml (1.0.4)
     sanitize (2.1.0)
       nokogiri (>= 1.4.4)
@@ -712,31 +785,30 @@ GEM
     sawyer (0.8.1)
       addressable (>= 2.3.5, < 2.6)
       faraday (~> 0.8, < 1.0)
-    scss_lint (0.47.1)
-      rake (>= 0.9, < 11)
-      sass (~> 3.4.15)
+    scss_lint (0.54.0)
+      rake (>= 0.9, < 13)
+      sass (~> 3.4.20)
     securecompare (1.0.0)
     seed-fu (2.3.6)
       activerecord (>= 3.1)
       activesupport (>= 3.1)
     select2-rails (3.5.9.3)
       thor (~> 0.14)
-    sentry-raven (2.4.0)
+    sentry-raven (2.5.3)
       faraday (>= 0.7.6, < 1.0)
     settingslogic (2.0.9)
-    sexp_processor (4.8.0)
+    sexp_processor (4.9.0)
     sham_rack (1.3.6)
       rack
-    shoulda-matchers (2.8.0)
-      activesupport (>= 3.0.0)
-    sidekiq (5.0.0)
+    shoulda-matchers (3.1.2)
+      activesupport (>= 4.0.0)
+    sidekiq (5.0.4)
       concurrent-ruby (~> 1.0)
       connection_pool (~> 2.2, >= 2.2.0)
       rack-protection (>= 1.5.0)
       redis (~> 3.3, >= 3.3.3)
-    sidekiq-cron (0.4.4)
-      redis-namespace (>= 1.5.2)
-      rufus-scheduler (>= 2.0.24)
+    sidekiq-cron (0.6.0)
+      rufus-scheduler (>= 3.3.0)
       sidekiq (>= 4.2.1)
     sidekiq-limit_fetch (3.4.0)
       sidekiq (>= 4)
@@ -784,7 +856,7 @@ GEM
     state_machines-activerecord (0.4.0)
       activerecord (>= 4.1, < 5.1)
       state_machines-activemodel (>= 0.3.0)
-    stringex (2.5.2)
+    stringex (2.7.1)
     sys-filesystem (1.1.6)
       ffi
     sysexits (1.2.0)
@@ -803,11 +875,10 @@ GEM
     timfel-krb5-auth (0.8.3)
     toml-rb (0.3.15)
       citrus (~> 3.0, > 3.0)
-    tool (0.2.3)
-    truncato (0.7.8)
+    truncato (0.7.10)
       htmlentities (~> 4.3.1)
-      nokogiri (~> 1.6.1)
-    tzinfo (1.2.2)
+      nokogiri (~> 1.8.0, >= 1.7.0)
+    tzinfo (1.2.3)
       thread_safe (~> 0.1)
     u2f (0.2.1)
     uglifier (2.7.2)
@@ -817,7 +888,7 @@ GEM
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.2)
-    unicode-display_width (1.1.3)
+    unicode-display_width (1.3.0)
     unicorn (5.1.0)
       kgio (~> 2.6)
       raindrops (~> 0.7)
@@ -838,7 +909,7 @@ GEM
     vmstat (2.3.0)
     warden (1.2.6)
       rack (>= 1.0)
-    webmock (1.24.6)
+    webmock (2.3.2)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
@@ -874,27 +945,28 @@ DEPENDENCIES
   awesome_print (~> 1.2.0)
   babosa (~> 1.0.2)
   base32 (~> 0.3.0)
+  bcrypt_pbkdf (~> 1.0)
   benchmark-ips (~> 2.3.0)
   better_errors (~> 2.1.0)
   binding_of_caller (~> 0.7.2)
   bootstrap-sass (~> 3.3.0)
+  bootstrap_form (~> 2.7.0)
   brakeman (~> 3.6.0)
   browser (~> 2.2)
   bullet (~> 5.5.0)
   bundler-audit (~> 0.5.0)
   capybara (~> 2.6.2)
   capybara-screenshot (~> 1.0.0)
-  carrierwave (~> 1.0)
+  carrierwave (~> 1.1)
   charlock_holmes (~> 0.7.3)
   chronic (~> 0.10.2)
   chronic_duration (~> 0.10.6)
-  coffee-rails (~> 4.1.0)
   concurrent-ruby (~> 1.0.5)
   connection_pool (~> 2.0)
   creole (~> 0.5.0)
   d3_rails (~> 3.5.0)
   database_cleaner (~> 1.5.0)
-  deckar01-task_list (= 1.0.6)
+  deckar01-task_list (= 2.0.0)
   default_value_for (~> 3.0.0)
   devise (~> 4.2)
   devise-two-factor (~> 3.0.0)
@@ -905,10 +977,13 @@ DEPENDENCIES
   email_reply_trimmer (~> 0.1)
   email_spec (~> 1.6.0)
   factory_girl_rails (~> 4.7.0)
-  faraday (~> 0.11.0)
+  faraday (~> 0.12)
   ffaker (~> 2.4)
   flay (~> 2.8.0)
-  fog-aws (~> 0.9)
+  flipper (~> 0.10.2)
+  flipper-active_record (~> 0.10.2)
+  fog-aliyun (~> 0.1.0)
+  fog-aws (~> 1.4)
   fog-core (~> 1.44)
   fog-google (~> 0.5)
   fog-local (~> 0.3)
@@ -916,24 +991,26 @@ DEPENDENCIES
   fog-rackspace (~> 0.1.1)
   font-awesome-rails (~> 4.7)
   foreman (~> 0.78.0)
-  fuubar (~> 2.0.0)
+  fuubar (~> 2.2.0)
   gemnasium-gitlab-service (~> 0.2)
-  gemojione (~> 3.0)
+  gemojione (~> 3.3)
   gettext (~> 3.2.2)
   gettext_i18n_rails (~> 1.8.0)
   gettext_i18n_rails_js (~> 1.2.0)
-  gitaly (~> 0.6.0)
+  gitaly (~> 0.26.0)
   github-linguist (~> 4.7.0)
   gitlab-flowdock-git-hook (~> 1.0.1)
   gitlab-markup (~> 1.5.1)
-  gitlab_omniauth-ldap (~> 1.2.1)
+  gitlab_omniauth-ldap (~> 2.0.3)
   gollum-lib (~> 4.2)
   gollum-rugged_adapter (~> 0.4.4)
   gon (~> 6.1.0)
   google-api-client (~> 0.8.6)
-  grape (~> 0.19.0)
+  gpgme
+  grape (~> 0.19.2)
   grape-entity (~> 0.6.0)
-  haml_lint (~> 0.21.0)
+  grape-route-helpers (~> 2.0.0)
+  haml_lint (~> 0.26.0)
   hamlit (~> 2.6.1)
   hashie-forbidden_attributes
   health_check (~> 2.6.0)
@@ -947,28 +1024,30 @@ DEPENDENCIES
   jquery-rails (~> 4.1.0)
   json-schema (~> 2.6.2)
   jwt (~> 1.5.6)
-  kaminari (~> 0.17.0)
+  kaminari (~> 1.0)
   knapsack (~> 1.11.0)
   kubeclient (~> 2.2.0)
   letter_opener_web (~> 1.3.0)
   license_finder (~> 2.1.0)
   licensee (~> 8.7.0)
+  lograge (~> 0.5)
   loofah (~> 2.0.3)
   mail_room (~> 0.9.1)
   method_source (~> 0.8)
   minitest (~> 5.7.0)
   mousetrap-rails (~> 1.4.6)
-  mysql2 (~> 0.3.16)
-  net-ssh (~> 3.0.1)
-  nokogiri (~> 1.6.7, >= 1.6.7.2)
-  oauth2 (~> 1.3.0)
+  mysql2 (~> 0.4.5)
+  net-ldap
+  net-ssh (~> 4.1.0)
+  nokogiri (~> 1.8.0)
+  oauth2 (~> 1.4)
   octokit (~> 4.6.2)
   oj (~> 2.17.4)
   omniauth (~> 1.4.2)
   omniauth-auth0 (~> 1.4.1)
-  omniauth-authentiq (~> 0.3.0)
+  omniauth-authentiq (~> 0.3.1)
   omniauth-azure-oauth2 (~> 0.0.6)
-  omniauth-cas3 (~> 1.1.2)
+  omniauth-cas3 (~> 1.1.4)
   omniauth-facebook (~> 4.0.0)
   omniauth-github (~> 1.1.1)
   omniauth-gitlab (~> 1.0.2)
@@ -980,10 +1059,20 @@ DEPENDENCIES
   omniauth-twitter (~> 1.2.0)
   omniauth_crowd (~> 2.2.0)
   org-ruby (~> 0.9.12)
-  paranoia (~> 2.2)
+  paranoia (~> 2.3.1)
+  peek (~> 1.0.1)
+  peek-gc (~> 0.0.2)
+  peek-host (~> 1.0.0)
+  peek-mysql2 (~> 1.1.0)
+  peek-performance_bar (~> 1.3.0)
+  peek-pg (~> 1.3.0)
+  peek-rblineprof (~> 0.2.0)
+  peek-redis (~> 1.2.0)
+  peek-sidekiq (~> 1.0.3)
   pg (~> 0.18.2)
   poltergeist (~> 1.9.0)
-  premailer-rails (~> 1.9.0)
+  premailer-rails (~> 1.9.7)
+  prometheus-client-mmap (~> 0.7.0.beta14)
   pry-byebug (~> 3.4.1)
   pry-rails (~> 0.3.4)
   rack-attack (~> 4.4.1)
@@ -992,10 +1081,14 @@ DEPENDENCIES
   rack-proxy (~> 0.6.0)
   rails (= 4.2.8)
   rails-deprecated_sanitizer (~> 1.0.3)
-  rainbow (~> 2.1.0)
+  rails-i18n (~> 4.0.9)
+  rainbow (~> 2.2)
+  raindrops (~> 0.18)
   rblineprof (~> 0.3.6)
+  rbnacl (~> 3.2)
+  rbnacl-libsodium
   rdoc (~> 4.2)
-  re2 (~> 1.0.0)
+  re2 (~> 1.1.1)
   recaptcha (~> 3.0)
   redcarpet (~> 3.4)
   redis (~> 3.2)
@@ -1005,28 +1098,28 @@ DEPENDENCIES
   responders (~> 2.0)
   rouge (~> 2.0)
   rqrcode-rails3 (~> 0.1.7)
-  rspec-rails (~> 3.5.0)
+  rspec-rails (~> 3.6.0)
   rspec-retry (~> 0.4.5)
   rspec-set (~> 0.1.3)
   rspec_profiling (~> 0.0.5)
-  rubocop (~> 0.47.1)
-  rubocop-rspec (~> 1.15.0)
+  rubocop (~> 0.49.1)
+  rubocop-rspec (~> 1.15.1)
   ruby-fogbugz (~> 0.2.1)
   ruby-prof (~> 0.16.2)
-  ruby_parser (~> 3.8.4)
-  rufus-scheduler (~> 3.1.10)
-  rugged (~> 0.25.1.1)
+  ruby_parser (~> 3.8)
+  rufus-scheduler (~> 3.4)
+  rugged (~> 0.26.0)
   sanitize (~> 2.0)
   sass-rails (~> 5.0.6)
-  scss_lint (~> 0.47.0)
+  scss_lint (~> 0.54.0)
   seed-fu (~> 2.3.5)
   select2-rails (~> 3.5.9)
-  sentry-raven (~> 2.4.0)
+  sentry-raven (~> 2.5.3)
   settingslogic (~> 2.0.9)
   sham_rack (~> 1.3.6)
-  shoulda-matchers (~> 2.8.0)
+  shoulda-matchers (~> 3.1.2)
   sidekiq (~> 5.0)
-  sidekiq-cron (~> 0.4.4)
+  sidekiq-cron (~> 0.6.0)
   sidekiq-limit_fetch (~> 3.4)
   simplecov (~> 0.14.0)
   slack-notifier (~> 1.5.1)
@@ -1043,7 +1136,7 @@ DEPENDENCIES
   thin (~> 1.7.0)
   timecop (~> 0.8.0)
   toml-rb (~> 0.3.15)
-  truncato (~> 0.7.8)
+  truncato (~> 0.7.9)
   u2f (~> 0.2.1)
   uglifier (~> 2.7.2)
   underscore-rails (~> 1.8.0)
@@ -1054,9 +1147,9 @@ DEPENDENCIES
   version_sorter (~> 2.1.0)
   virtus (~> 1.0.1)
   vmstat (~> 2.3.0)
-  webmock (~> 1.24.0)
+  webmock (~> 2.3.2)
   webpack-rails (~> 0.9.10)
   wikicloth (= 0.8.1)
 
 BUNDLED WITH
-   1.15.0
+   1.15.3
diff --git a/PROCESS.md b/PROCESS.md
index 3b97a4e8c7..2b3d142bf7 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -128,7 +128,7 @@ information, see
 
 ### After the 7th
 
-Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
+Once the stable branch is frozen, only fixes for [regressions](#regressions)
 and security issues will be cherry-picked into the stable branch.
 Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
 These fixes will be shipped in the next RC for that release if it is before the 22nd.
@@ -158,6 +158,24 @@ release should have the correct milestone assigned _and_ have the label
 Merge requests without a milestone and this label will
 not be merged into any stable branches.
 
+### Regressions
+
+A regression for a particular monthly release is a bug that exists in that
+release, but wasn't present in the release before. This includes bugs in
+features that were only added in that monthly release. Every regression **must**
+have the milestone of the release it was introduced in - if a regression doesn't
+have a milestone, it might be 'just' a bug!
+
+For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
+then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
+reintroduces the bug, then this bug is still a regression in 10.5.
+
+Because GitLab.com runs release candidates of new releases, a regression can be
+reported in a release before its 'official' release date on the 22nd of the
+month. When we say 'the most recent monthly release', this can refer to either
+the version currently running on GitLab.com, or the most recent version
+available in the package repositories.
+
 ## Release retrospective and kickoff
 
 ### Retrospective
diff --git a/README.md b/README.md
index 59de828e1a..9309922ae3 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
 ## Test coverage
 
 - [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
-- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
+- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
 
 ## Canonical source
 
diff --git a/VERSION b/VERSION
index 57097e3b15..44711452c7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.2.10
+9.5.4
diff --git a/app/assets/images/i2p-step.svg b/app/assets/images/i2p-step.svg
new file mode 100644
index 0000000000..8886092ed8
--- /dev/null
+++ b/app/assets/images/i2p-step.svg
@@ -0,0 +1,4 @@
+
+
+  
+
diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png
new file mode 100644
index 0000000000..f98ca15d78
Binary files /dev/null and b/app/assets/images/new_nav.png differ
diff --git a/app/assets/images/new_repo.png b/app/assets/images/new_repo.png
new file mode 100644
index 0000000000..ed3af06ab1
Binary files /dev/null and b/app/assets/images/new_repo.png differ
diff --git a/app/assets/images/old_nav.png b/app/assets/images/old_nav.png
new file mode 100644
index 0000000000..23fae7aa19
Binary files /dev/null and b/app/assets/images/old_nav.png differ
diff --git a/app/assets/images/old_repo.png b/app/assets/images/old_repo.png
new file mode 100644
index 0000000000..c3c3b791ad
Binary files /dev/null and b/app/assets/images/old_repo.png differ
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index d816df831e..5d060165f4 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -5,7 +5,8 @@ import Cookies from 'js-cookie';
 
 class Activities {
   constructor() {
-    Pager.init(20, true, false, this.updateTooltips);
+    Pager.init(20, true, false, data => data, this.updateTooltips);
+
     $('.event-filter-link').on('click', (e) => {
       e.preventDefault();
       this.toggleFilter(e.currentTarget);
@@ -19,7 +20,7 @@ class Activities {
 
   reloadActivities() {
     $('.content_list').html('');
-    Pager.init(20, true, false, this.updateTooltips);
+    Pager.init(20, true, false, data => data, this.updateTooltips);
   }
 
   toggleFilter(sender) {
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
index 38a8317dbd..8f5e2e545e 100644
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -10,7 +10,7 @@ class AjaxLoadingSpinner {
     e.target.setAttribute('disabled', '');
     const iconElement = e.target.querySelector('i');
     // get first fa- icon
-    const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
+    const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0];
     iconElement.dataset.icon = originalIcon;
     AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
     $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index e5f36c8498..56f91e95bb 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,148 +1,190 @@
-/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */
+import $ from 'jquery';
 
-var Api = {
-  groupsPath: "/api/:version/groups.json",
-  groupPath: "/api/:version/groups/:id.json",
-  namespacesPath: "/api/:version/namespaces.json",
-  groupProjectsPath: "/api/:version/groups/:id/projects.json",
-  projectsPath: "/api/:version/projects.json?simple=true",
-  labelsPath: "/:namespace_path/:project_path/labels",
-  licensePath: "/api/:version/templates/licenses/:key",
-  gitignorePath: "/api/:version/templates/gitignores/:key",
-  gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
-  dockerfilePath: "/api/:version/templates/dockerfiles/:key",
-  issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
-  group: function(group_id, callback) {
-    var url = Api.buildUrl(Api.groupPath)
-      .replace(':id', group_id);
+const Api = {
+  groupsPath: '/api/:version/groups.json',
+  groupPath: '/api/:version/groups/:id.json',
+  namespacesPath: '/api/:version/namespaces.json',
+  groupProjectsPath: '/api/:version/groups/:id/projects.json',
+  projectsPath: '/api/:version/projects.json?simple=true',
+  labelsPath: '/:namespace_path/:project_path/labels',
+  licensePath: '/api/:version/templates/licenses/:key',
+  gitignorePath: '/api/:version/templates/gitignores/:key',
+  gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
+  dockerfilePath: '/api/:version/templates/dockerfiles/:key',
+  issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
+  usersPath: '/api/:version/users.json',
+  commitPath: '/api/:version/projects/:id/repository/commits',
+
+  group(groupId, callback) {
+    const url = Api.buildUrl(Api.groupPath)
+      .replace(':id', groupId);
     return $.ajax({
-      url: url,
-      dataType: "json"
-    }).done(function(group) {
-      return callback(group);
-    });
+      url,
+      dataType: 'json',
+    })
+      .done(group => callback(group));
   },
+
   // Return groups list. Filtered by query
-  groups: function(query, options, callback) {
-    var url = Api.buildUrl(Api.groupsPath);
+  groups(query, options, callback) {
+    const url = Api.buildUrl(Api.groupsPath);
     return $.ajax({
-      url: url,
-      data: $.extend({
-        search: query,
-        per_page: 20
-      }, options),
-      dataType: "json"
-    }).done(function(groups) {
-      return callback(groups);
-    });
-  },
-  // Return namespaces list. Filtered by query
-  namespaces: function(query, callback) {
-    var url = Api.buildUrl(Api.namespacesPath);
-    return $.ajax({
-      url: url,
-      data: {
-        search: query,
-        per_page: 20
-      },
-      dataType: "json"
-    }).done(function(namespaces) {
-      return callback(namespaces);
-    });
-  },
-  // Return projects list. Filtered by query
-  projects: function(query, options, callback) {
-    var url = Api.buildUrl(Api.projectsPath);
-    return $.ajax({
-      url: url,
-      data: $.extend({
+      url,
+      data: Object.assign({
         search: query,
         per_page: 20,
-        membership: true
       }, options),
-      dataType: "json"
-    }).done(function(projects) {
-      return callback(projects);
-    });
+      dataType: 'json',
+    })
+      .done(groups => callback(groups));
   },
-  newLabel: function(namespace_path, project_path, data, callback) {
-    var url = Api.buildUrl(Api.labelsPath)
-      .replace(':namespace_path', namespace_path)
-      .replace(':project_path', project_path);
+
+  // Return namespaces list. Filtered by query
+  namespaces(query, callback) {
+    const url = Api.buildUrl(Api.namespacesPath);
     return $.ajax({
-      url: url,
-      type: "POST",
-      data: { 'label': data },
-      dataType: "json"
-    }).done(function(label) {
-      return callback(label);
-    }).error(function(message) {
-      return callback(message.responseJSON);
-    });
-  },
-  // Return group projects list. Filtered by query
-  groupProjects: function(group_id, query, callback) {
-    var url = Api.buildUrl(Api.groupProjectsPath)
-      .replace(':id', group_id);
-    return $.ajax({
-      url: url,
+      url,
       data: {
         search: query,
-        per_page: 20
+        per_page: 20,
       },
-      dataType: "json"
-    }).done(function(projects) {
-      return callback(projects);
-    });
+      dataType: 'json',
+    }).done(namespaces => callback(namespaces));
   },
+
+  // Return projects list. Filtered by query
+  projects(query, options, callback) {
+    const url = Api.buildUrl(Api.projectsPath);
+    return $.ajax({
+      url,
+      data: Object.assign({
+        search: query,
+        per_page: 20,
+        membership: true,
+      }, options),
+      dataType: 'json',
+    })
+      .done(projects => callback(projects));
+  },
+
+  newLabel(namespacePath, projectPath, data, callback) {
+    const url = Api.buildUrl(Api.labelsPath)
+      .replace(':namespace_path', namespacePath)
+      .replace(':project_path', projectPath);
+    return $.ajax({
+      url,
+      type: 'POST',
+      data: { label: data },
+      dataType: 'json',
+    })
+      .done(label => callback(label))
+      .fail(message => callback(message.responseJSON));
+  },
+
+  // Return group projects list. Filtered by query
+  groupProjects(groupId, query, callback) {
+    const url = Api.buildUrl(Api.groupProjectsPath)
+      .replace(':id', groupId);
+    return $.ajax({
+      url,
+      data: {
+        search: query,
+        per_page: 20,
+      },
+      dataType: 'json',
+    })
+      .done(projects => callback(projects));
+  },
+
+  commitMultiple(id, data, callback) {
+    const url = Api.buildUrl(Api.commitPath)
+      .replace(':id', id);
+    return $.ajax({
+      url,
+      type: 'POST',
+      contentType: 'application/json; charset=utf-8',
+      data: JSON.stringify(data),
+      dataType: 'json',
+    })
+      .done(commitData => callback(commitData))
+      .fail(message => callback(message.responseJSON));
+  },
+
   // Return text for a specific license
-  licenseText: function(key, data, callback) {
-    var url = Api.buildUrl(Api.licensePath)
+  licenseText(key, data, callback) {
+    const url = Api.buildUrl(Api.licensePath)
       .replace(':key', key);
     return $.ajax({
-      url: url,
-      data: data
-    }).done(function(license) {
-      return callback(license);
-    });
+      url,
+      data,
+    })
+      .done(license => callback(license));
   },
-  gitignoreText: function(key, callback) {
-    var url = Api.buildUrl(Api.gitignorePath)
+
+  gitignoreText(key, callback) {
+    const url = Api.buildUrl(Api.gitignorePath)
       .replace(':key', key);
-    return $.get(url, function(gitignore) {
-      return callback(gitignore);
-    });
+    return $.get(url, gitignore => callback(gitignore));
   },
-  gitlabCiYml: function(key, callback) {
-    var url = Api.buildUrl(Api.gitlabCiYmlPath)
+
+  gitlabCiYml(key, callback) {
+    const url = Api.buildUrl(Api.gitlabCiYmlPath)
       .replace(':key', key);
-    return $.get(url, function(file) {
-      return callback(file);
-    });
+    return $.get(url, file => callback(file));
   },
-  dockerfileYml: function(key, callback) {
-    var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
+
+  dockerfileYml(key, callback) {
+    const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
     $.get(url, callback);
   },
-  issueTemplate: function(namespacePath, projectPath, key, type, callback) {
-    var url = Api.buildUrl(Api.issuableTemplatePath)
+
+  issueTemplate(namespacePath, projectPath, key, type, callback) {
+    const url = Api.buildUrl(Api.issuableTemplatePath)
       .replace(':key', key)
       .replace(':type', type)
       .replace(':project_path', projectPath)
       .replace(':namespace_path', namespacePath);
     $.ajax({
-      url: url,
-      dataType: 'json'
-    }).done(function(file) {
-      callback(null, file);
-    }).error(callback);
+      url,
+      dataType: 'json',
+    })
+      .done(file => callback(null, file))
+      .fail(callback);
   },
-  buildUrl: function(url) {
+
+  users(query, options) {
+    const url = Api.buildUrl(this.usersPath);
+    return Api.wrapAjaxCall({
+      url,
+      data: Object.assign({
+        search: query,
+        per_page: 20,
+      }, options),
+      dataType: 'json',
+    });
+  },
+
+  buildUrl(url) {
+    let urlRoot = '';
     if (gon.relative_url_root != null) {
-      url = gon.relative_url_root + url;
+      urlRoot = gon.relative_url_root;
     }
-    return url.replace(':version', gon.api_version);
-  }
+    return urlRoot + url.replace(':version', gon.api_version);
+  },
+
+  wrapAjaxCall(options) {
+    return new Promise((resolve, reject) => {
+      // jQuery 2 is not Promises/A+ compatible (missing catch)
+      $.ajax(options) // eslint-disable-line promise/catch-or-return
+      .then(data => resolve(data),
+        (jqXHR, textStatus, errorThrown) => {
+          const error = new Error(`${options.url}: ${errorThrown}`);
+          error.textStatus = textStatus;
+          reject(error);
+        },
+      );
+    });
+  },
 };
 
-window.Api = Api;
+export default Api;
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index adb45b0606..097f79a250 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,12 +1,8 @@
+/* eslint-disable class-methods-use-this */
 /* global Flash */
-
+import _ from 'underscore';
 import Cookies from 'js-cookie';
 
-import emojiMap from 'emojis/digests.json';
-import emojiAliases from 'emojis/aliases.json';
-import { glEmojiTag } from './behaviors/gl_emoji';
-import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
-
 const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
 const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
 const requestAnimationFrame = window.requestAnimationFrame ||
@@ -16,8 +12,6 @@ const requestAnimationFrame = window.requestAnimationFrame ||
 
 const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
 
-let categoryMap = null;
-
 const categoryLabelMap = {
   activity: 'Activity',
   people: 'People',
@@ -29,186 +23,144 @@ const categoryLabelMap = {
   flags: 'Flags',
 };
 
-function buildCategoryMap() {
-  return Object.keys(emojiMap).reduce((currentCategoryMap, emojiNameKey) => {
-    const emojiInfo = emojiMap[emojiNameKey];
-    if (currentCategoryMap[emojiInfo.category]) {
-      currentCategoryMap[emojiInfo.category].push(emojiNameKey);
+class AwardsHandler {
+  constructor(emoji) {
+    this.emoji = emoji;
+    this.eventListeners = [];
+    // If the user shows intent let's pre-build the menu
+    this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
+      const $menu = $('.emoji-menu');
+      if ($menu.length === 0) {
+        requestAnimationFrame(() => {
+          this.createEmojiMenu();
+        });
+      }
+    });
+    this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
+      e.stopPropagation();
+      e.preventDefault();
+      this.showEmojiMenu($(e.currentTarget));
+    });
+
+    this.registerEventListener('on', $('html'), 'click', (e) => {
+      const $target = $(e.target);
+      if (!$target.closest('.emoji-menu-content').length) {
+        $('.js-awards-block.current').removeClass('current');
+      }
+      if (!$target.closest('.emoji-menu').length) {
+        if ($('.emoji-menu').is(':visible')) {
+          $('.js-add-award.is-active').removeClass('is-active');
+          $('.emoji-menu').removeClass('is-visible');
+        }
+      }
+    });
+    this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
+      e.preventDefault();
+      const $target = $(e.currentTarget);
+      const $glEmojiElement = $target.find('gl-emoji');
+      const $spriteIconElement = $target.find('.icon');
+      const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
+
+      $target.closest('.js-awards-block').addClass('current');
+      this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
+    });
+  }
+
+  registerEventListener(method = 'on', element, ...args) {
+    element[method].call(element, ...args);
+    this.eventListeners.push({
+      element,
+      args,
+    });
+  }
+
+  showEmojiMenu($addBtn) {
+    if ($addBtn.hasClass('js-note-emoji')) {
+      $addBtn.closest('.note').find('.js-awards-block').addClass('current');
+    } else {
+      $addBtn.closest('.js-awards-block').addClass('current');
     }
 
-    return currentCategoryMap;
-  }, {
-    activity: [],
-    people: [],
-    nature: [],
-    food: [],
-    travel: [],
-    objects: [],
-    symbols: [],
-    flags: [],
-  });
-}
-
-function renderCategory(name, emojiList, opts = {}) {
-  return `
-    
- ${name} -
-
    - ${emojiList.map(emojiName => ` -
  • - -
  • - `).join('\n')} -
- `; -} - -function AwardsHandler() { - this.eventListeners = []; - this.aliases = emojiAliases; - // If the user shows intent let's pre-build the menu - this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => { const $menu = $('.emoji-menu'); - if ($menu.length === 0) { - requestAnimationFrame(() => { - this.createEmojiMenu(); + const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent(); + const $userAuthored = this.isUserAuthored($addBtn); + if ($menu.length) { + if ($menu.is('.is-visible')) { + $addBtn.removeClass('is-active'); + $menu.removeClass('is-visible'); + $('.js-emoji-menu-search').blur(); + } else { + $addBtn.addClass('is-active'); + this.positionMenu($menu, $addBtn); + $menu.addClass('is-visible'); + $('.js-emoji-menu-search').focus(); + } + } else { + $addBtn.addClass('is-loading is-active'); + this.createEmojiMenu(() => { + const $createdMenu = $('.emoji-menu'); + $addBtn.removeClass('is-loading'); + this.positionMenu($createdMenu, $addBtn); + return setTimeout(() => { + $createdMenu.addClass('is-visible'); + $('.js-emoji-menu-search').focus(); + }, 200); }); } - // Prebuild the categoryMap - categoryMap = categoryMap || buildCategoryMap(); - }); - this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => { - e.stopPropagation(); - e.preventDefault(); - this.showEmojiMenu($(e.currentTarget)); - }); - this.registerEventListener('on', $('html'), 'click', (e) => { - const $target = $(e.target); - if (!$target.closest('.emoji-menu-content').length) { - $('.js-awards-block.current').removeClass('current'); + $thumbsBtn.toggleClass('disabled', $userAuthored); + } + + // Create the emoji menu with the first category of emojis. + // Then render the remaining categories of emojis one by one to avoid jank. + createEmojiMenu(callback) { + if (this.isCreatingEmojiMenu) { + return; } - if (!$target.closest('.emoji-menu').length) { - if ($('.emoji-menu').is(':visible')) { - $('.js-add-award.is-active').removeClass('is-active'); - $('.emoji-menu').removeClass('is-visible'); - } + this.isCreatingEmojiMenu = true; + + // Render the first category + const categoryMap = this.emoji.getEmojiCategoryMap(); + const categoryNameKey = Object.keys(categoryMap)[0]; + const emojisInCategory = categoryMap[categoryNameKey]; + const firstCategory = this.renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); + + // Render the frequently used + const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); + let frequentlyUsedCatgegory = ''; + if (frequentlyUsedEmojis.length > 0) { + frequentlyUsedCatgegory = this.renderCategory('Frequently used', frequentlyUsedEmojis, { + menuListClass: 'frequent-emojis', + }); } - }); - this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - const $glEmojiElement = $target.find('gl-emoji'); - const $spriteIconElement = $target.find('.icon'); - const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); - $target.closest('.js-awards-block').addClass('current'); - this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); - }); -} + const emojiMenuMarkup = ` +
+ -AwardsHandler.prototype.registerEventListener = function registerEventListener(method = 'on', element, ...args) { - element[method].call(element, ...args); - this.eventListeners.push({ - element, - args, - }); -}; - -AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { - if ($addBtn.hasClass('js-note-emoji')) { - $addBtn.closest('.note').find('.js-awards-block').addClass('current'); - } else { - $addBtn.closest('.js-awards-block').addClass('current'); - } - - const $menu = $('.emoji-menu'); - const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent(); - const $userAuthored = this.isUserAuthored($addBtn); - if ($menu.length) { - if ($menu.is('.is-visible')) { - $addBtn.removeClass('is-active'); - $menu.removeClass('is-visible'); - $('.js-emoji-menu-search').blur(); - } else { - $addBtn.addClass('is-active'); - this.positionMenu($menu, $addBtn); - $menu.addClass('is-visible'); - $('.js-emoji-menu-search').focus(); - } - } else { - $addBtn.addClass('is-loading is-active'); - this.createEmojiMenu(() => { - const $createdMenu = $('.emoji-menu'); - $addBtn.removeClass('is-loading'); - this.positionMenu($createdMenu, $addBtn); - return setTimeout(() => { - $createdMenu.addClass('is-visible'); - $('.js-emoji-menu-search').focus(); - }, 200); - }); - } - - $thumbsBtn.toggleClass('disabled', $userAuthored); -}; - -// Create the emoji menu with the first category of emojis. -// Then render the remaining categories of emojis one by one to avoid jank. -AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { - if (this.isCreatingEmojiMenu) { - return; - } - this.isCreatingEmojiMenu = true; - - // Render the first category - categoryMap = categoryMap || buildCategoryMap(); - const categoryNameKey = Object.keys(categoryMap)[0]; - const emojisInCategory = categoryMap[categoryNameKey]; - const firstCategory = renderCategory(categoryLabelMap[categoryNameKey], emojisInCategory); - - // Render the frequently used - const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); - let frequentlyUsedCatgegory = ''; - if (frequentlyUsedEmojis.length > 0) { - frequentlyUsedCatgegory = renderCategory('Frequently used', frequentlyUsedEmojis, { - menuListClass: 'frequent-emojis', - }); - } - - const emojiMenuMarkup = ` -
- - -
- ${frequentlyUsedCatgegory} - ${firstCategory} +
+ ${frequentlyUsedCatgegory} + ${firstCategory} +
-
- `; + `; - document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup); + document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup); - this.addRemainingEmojiMenuCategories(); - this.setupSearch(); - if (callback) { - callback(); + this.addRemainingEmojiMenuCategories(); + this.setupSearch(); + if (callback) { + callback(); + } } -}; -AwardsHandler - .prototype - .addRemainingEmojiMenuCategories = function addRemainingEmojiMenuCategories() { + addRemainingEmojiMenuCategories() { if (this.isAddingRemainingEmojiMenuCategories) { return; } this.isAddingRemainingEmojiMenuCategories = true; - categoryMap = categoryMap || buildCategoryMap(); + const categoryMap = this.emoji.getEmojiCategoryMap(); // Avoid the jank and render the remaining categories separately // This will take more time, but makes UI more responsive @@ -220,7 +172,7 @@ AwardsHandler promiseChain.then(() => new Promise((resolve) => { const emojisInCategory = categoryMap[categoryNameKey]; - const categoryMarkup = renderCategory( + const categoryMarkup = this.renderCategory( categoryLabelMap[categoryNameKey], emojisInCategory, ); @@ -243,179 +195,186 @@ AwardsHandler emojiContentElement.insertAdjacentHTML('beforeend', '

We encountered an error while adding the remaining categories

'); throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`); }); - }; - -AwardsHandler.prototype.positionMenu = function positionMenu($menu, $addBtn) { - const position = $addBtn.data('position'); - // The menu could potentially be off-screen or in a hidden overflow element - // So we position the element absolute in the body - const css = { - top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`, - }; - if (position === 'right') { - css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`; - $menu.addClass('is-aligned-right'); - } else { - css.left = `${$addBtn.offset().left}px`; - $menu.removeClass('is-aligned-right'); } - return $menu.css(css); -}; -AwardsHandler.prototype.addAward = function addAward( - votesBlock, - awardUrl, - emoji, - checkMutuality, - callback, -) { - const normalizedEmoji = this.normalizeEmojiName(emoji); - const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); - this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { - this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); - return typeof callback === 'function' ? callback() : undefined; - }); - $('.emoji-menu').removeClass('is-visible'); - $('.js-add-award.is-active').removeClass('is-active'); -}; - -AwardsHandler.prototype.addAwardToEmojiBar = function addAwardToEmojiBar( - votesBlock, - emoji, - checkForMutuality, -) { - if (checkForMutuality || checkForMutuality === null) { - this.checkMutuality(votesBlock, emoji); + renderCategory(name, emojiList, opts = {}) { + return ` +
+ ${name} +
+
    + ${emojiList.map(emojiName => ` +
  • + +
  • + `).join('\n')} +
+ `; } - this.addEmojiToFrequentlyUsedList(emoji); - const normalizedEmoji = this.normalizeEmojiName(emoji); - const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); - if ($emojiButton.length > 0) { - if (this.isActive($emojiButton)) { - this.decrementCounter($emojiButton, normalizedEmoji); + + positionMenu($menu, $addBtn) { + const position = $addBtn.data('position'); + // The menu could potentially be off-screen or in a hidden overflow element + // So we position the element absolute in the body + const css = { + top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`, + }; + if (position === 'right') { + css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`; + $menu.addClass('is-aligned-right'); } else { - const counter = $emojiButton.find('.js-counter'); - counter.text(parseInt(counter.text(), 10) + 1); - $emojiButton.addClass('active'); - this.addYouToUserList(votesBlock, normalizedEmoji); - this.animateEmoji($emojiButton); + css.left = `${$addBtn.offset().left}px`; + $menu.removeClass('is-aligned-right'); } - } else { - votesBlock.removeClass('hidden'); - this.createEmoji(votesBlock, normalizedEmoji); - } -}; - -AwardsHandler.prototype.getVotesBlock = function getVotesBlock() { - const currentBlock = $('.js-awards-block.current'); - let resultantVotesBlock = currentBlock; - if (currentBlock.length === 0) { - resultantVotesBlock = $('.js-awards-block').eq(0); + return $menu.css(css); } - return resultantVotesBlock; -}; + addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { + const normalizedEmoji = this.emoji.normalizeEmojiName(emoji); + const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); + this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => { + this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); + return typeof callback === 'function' ? callback() : undefined; + }); + $('.emoji-menu').removeClass('is-visible'); + $('.js-add-award.is-active').removeClass('is-active'); + } -AwardsHandler.prototype.getAwardUrl = function getAwardUrl() { - return this.getVotesBlock().data('award-url'); -}; - -AwardsHandler.prototype.checkMutuality = function checkMutuality(votesBlock, emoji) { - const awardUrl = this.getAwardUrl(); - if (emoji === 'thumbsup' || emoji === 'thumbsdown') { - const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; - const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent(); - const isAlreadyVoted = $emojiButton.hasClass('active'); - if (isAlreadyVoted) { - this.addAward(votesBlock, awardUrl, mutualVote, false); + addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) { + if (checkForMutuality || checkForMutuality === null) { + this.checkMutuality(votesBlock, emoji); + } + this.addEmojiToFrequentlyUsedList(emoji); + const normalizedEmoji = this.emoji.normalizeEmojiName(emoji); + const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent(); + if ($emojiButton.length > 0) { + if (this.isActive($emojiButton)) { + this.decrementCounter($emojiButton, normalizedEmoji); + } else { + const counter = $emojiButton.find('.js-counter'); + counter.text(parseInt(counter.text(), 10) + 1); + $emojiButton.addClass('active'); + this.addYouToUserList(votesBlock, normalizedEmoji); + this.animateEmoji($emojiButton); + } + } else { + votesBlock.removeClass('hidden'); + this.createEmoji(votesBlock, normalizedEmoji); } } -}; -AwardsHandler.prototype.isActive = function isActive($emojiButton) { - return $emojiButton.hasClass('active'); -}; + getVotesBlock() { + const currentBlock = $('.js-awards-block.current'); + let resultantVotesBlock = currentBlock; + if (currentBlock.length === 0) { + resultantVotesBlock = $('.js-awards-block').eq(0); + } -AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) { - return $button.hasClass('js-user-authored'); -}; + return resultantVotesBlock; + } -AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) { - const counter = $('.js-counter', $emojiButton); - const counterNumber = parseInt(counter.text(), 10); - if (counterNumber > 1) { - counter.text(counterNumber - 1); - this.removeYouFromUserList($emojiButton); - } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { - $emojiButton.tooltip('destroy'); - counter.text('0'); - this.removeYouFromUserList($emojiButton); - if ($emojiButton.parents('.note').length) { + getAwardUrl() { + return this.getVotesBlock().data('award-url'); + } + + checkMutuality(votesBlock, emoji) { + const awardUrl = this.getAwardUrl(); + if (emoji === 'thumbsup' || emoji === 'thumbsdown') { + const mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; + const $emojiButton = votesBlock.find(`[data-name="${mutualVote}"]`).parent(); + const isAlreadyVoted = $emojiButton.hasClass('active'); + if (isAlreadyVoted) { + this.addAward(votesBlock, awardUrl, mutualVote, false); + } + } + } + + isActive($emojiButton) { + return $emojiButton.hasClass('active'); + } + + isUserAuthored($button) { + return $button.hasClass('js-user-authored'); + } + + decrementCounter($emojiButton, emoji) { + const counter = $('.js-counter', $emojiButton); + const counterNumber = parseInt(counter.text(), 10); + if (counterNumber > 1) { + counter.text(counterNumber - 1); + this.removeYouFromUserList($emojiButton); + } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { + $emojiButton.tooltip('destroy'); + counter.text('0'); + this.removeYouFromUserList($emojiButton); + if ($emojiButton.parents('.note').length) { + this.removeEmoji($emojiButton); + } + } else { this.removeEmoji($emojiButton); } - } else { - this.removeEmoji($emojiButton); - } - return $emojiButton.removeClass('active'); -}; - -AwardsHandler.prototype.removeEmoji = function removeEmoji($emojiButton) { - $emojiButton.tooltip('destroy'); - $emojiButton.remove(); - const $votesBlock = this.getVotesBlock(); - if ($votesBlock.find('.js-emoji-btn').length === 0) { - $votesBlock.addClass('hidden'); - } -}; - -AwardsHandler.prototype.getAwardTooltip = function getAwardTooltip($awardBlock) { - return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || ''; -}; - -AwardsHandler.prototype.toSentence = function toSentence(list) { - let sentence; - if (list.length <= 2) { - sentence = list.join(' and '); - } else { - sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`; + return $emojiButton.removeClass('active'); } - return sentence; -}; - -AwardsHandler.prototype.removeYouFromUserList = function removeYouFromUserList($emojiButton) { - const awardBlock = $emojiButton; - const originalTitle = this.getAwardTooltip(awardBlock); - const authors = originalTitle.split(FROM_SENTENCE_REGEX); - authors.splice(authors.indexOf('You'), 1); - return awardBlock - .closest('.js-emoji-btn') - .removeData('title') - .removeAttr('data-title') - .removeAttr('data-original-title') - .attr('title', this.toSentence(authors)) - .tooltip('fixTitle'); -}; - -AwardsHandler.prototype.addYouToUserList = function addYouToUserList(votesBlock, emoji) { - const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent(); - const origTitle = this.getAwardTooltip(awardBlock); - let users = []; - if (origTitle) { - users = origTitle.trim().split(FROM_SENTENCE_REGEX); + removeEmoji($emojiButton) { + $emojiButton.tooltip('destroy'); + $emojiButton.remove(); + const $votesBlock = this.getVotesBlock(); + if ($votesBlock.find('.js-emoji-btn').length === 0) { + $votesBlock.addClass('hidden'); + } } - users.unshift('You'); - return awardBlock - .attr('title', this.toSentence(users)) - .tooltip('fixTitle'); -}; -AwardsHandler - .prototype - .createAwardButtonForVotesBlock = function createAwardButtonForVotesBlock(votesBlock, emojiName) { + getAwardTooltip($awardBlock) { + return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || ''; + } + + toSentence(list) { + let sentence; + if (list.length <= 2) { + sentence = list.join(' and '); + } else { + sentence = `${list.slice(0, -1).join(', ')}, and ${list[list.length - 1]}`; + } + + return sentence; + } + + removeYouFromUserList($emojiButton) { + const awardBlock = $emojiButton; + const originalTitle = this.getAwardTooltip(awardBlock); + const authors = originalTitle.split(FROM_SENTENCE_REGEX); + authors.splice(authors.indexOf('You'), 1); + return awardBlock + .closest('.js-emoji-btn') + .removeData('title') + .removeAttr('data-title') + .removeAttr('data-original-title') + .attr('title', this.toSentence(authors)) + .tooltip('fixTitle'); + } + + addYouToUserList(votesBlock, emoji) { + const awardBlock = this.findEmojiIcon(votesBlock, emoji).parent(); + const origTitle = this.getAwardTooltip(awardBlock); + let users = []; + if (origTitle) { + users = origTitle.trim().split(FROM_SENTENCE_REGEX); + } + users.unshift('You'); + return awardBlock + .attr('title', this.toSentence(users)) + .tooltip('fixTitle'); + } + + createAwardButtonForVotesBlock(votesBlock, emojiName) { const buttonHtml = ` `; @@ -424,144 +383,136 @@ AwardsHandler this.animateEmoji($emojiButton); $('.award-control').tooltip(); votesBlock.removeClass('current'); - }; - -AwardsHandler.prototype.animateEmoji = function animateEmoji($emoji) { - const className = 'pulse animated once short'; - $emoji.addClass(className); - - this.registerEventListener('on', $emoji, animationEndEventString, (e) => { - $(e.currentTarget).removeClass(className); - }); -}; - -AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) { - if ($('.emoji-menu').length) { - this.createAwardButtonForVotesBlock(votesBlock, emoji); } - this.createEmojiMenu(() => { - this.createAwardButtonForVotesBlock(votesBlock, emoji); - }); -}; -AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) { - if (this.isUserAuthored($emojiButton)) { - this.userAuthored($emojiButton); - } else { - $.post(awardUrl, { - name: emoji, - }, (data) => { - if (data.ok) { - callback(); - } - }).fail(() => new Flash('Something went wrong on our end.')); + animateEmoji($emoji) { + const className = 'pulse animated once short'; + $emoji.addClass(className); + + this.registerEventListener('on', $emoji, animationEndEventString, (e) => { + $(e.currentTarget).removeClass(className); + }); } -}; -AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) { - return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); -}; + createEmoji(votesBlock, emoji) { + if ($('.emoji-menu').length) { + this.createAwardButtonForVotesBlock(votesBlock, emoji); + } + this.createEmojiMenu(() => { + this.createAwardButtonForVotesBlock(votesBlock, emoji); + }); + } -AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) { - const oldTitle = this.getAwardTooltip($emojiButton); - const newTitle = 'You cannot vote on your own issue, MR and note'; - gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show'); - // Restore tooltip back to award list - return setTimeout(() => { - $emojiButton.tooltip('hide'); - gl.utils.updateTooltipTitle($emojiButton, oldTitle); - }, 2800); -}; + postEmoji($emojiButton, awardUrl, emoji, callback) { + if (this.isUserAuthored($emojiButton)) { + this.userAuthored($emojiButton); + } else { + $.post(awardUrl, { + name: emoji, + }, (data) => { + if (data.ok) { + callback(); + } + }).fail(() => new Flash('Something went wrong on our end.')); + } + } -AwardsHandler.prototype.scrollToAwards = function scrollToAwards() { - const options = { - scrollTop: $('.awards').offset().top - 110, - }; - return $('body, html').animate(options, 200); -}; + findEmojiIcon(votesBlock, emoji) { + return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); + } -AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) { - return Object.prototype.hasOwnProperty.call(this.aliases, emoji) ? this.aliases[emoji] : emoji; -}; + userAuthored($emojiButton) { + const oldTitle = this.getAwardTooltip($emojiButton); + const newTitle = 'You cannot vote on your own issue, MR and note'; + gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show'); + // Restore tooltip back to award list + return setTimeout(() => { + $emojiButton.tooltip('hide'); + gl.utils.updateTooltipTitle($emojiButton, oldTitle); + }, 2800); + } -AwardsHandler - .prototype - .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) { - if (isEmojiNameValid(emoji)) { + scrollToAwards() { + const options = { + scrollTop: $('.awards').offset().top - 110, + }; + return $('body, html').animate(options, 200); + } + + addEmojiToFrequentlyUsedList(emoji) { + if (this.emoji.isEmojiNameValid(emoji)) { this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji)); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 }); } - }; - -AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() { - return this.frequentlyUsedEmojis || (() => { - const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(',')); - this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter( - inputName => isEmojiNameValid(inputName), - ); - - return this.frequentlyUsedEmojis; - })(); -}; - -AwardsHandler.prototype.setupSearch = function setupSearch() { - const $search = $('.js-emoji-menu-search'); - - this.registerEventListener('on', $search, 'input', (e) => { - const term = $(e.target).val().trim(); - this.searchEmojis(term); - }); - - const $menu = $('.emoji-menu'); - this.registerEventListener('on', $menu, transitionEndEventString, (e) => { - if (e.target === e.currentTarget) { - // Clear the search - this.searchEmojis(''); - } - }); -}; - -AwardsHandler.prototype.searchEmojis = function searchEmojis(term) { - const $search = $('.js-emoji-menu-search'); - $search.val(term); - - // Clean previous search results - $('ul.emoji-menu-search, h5.emoji-search-title').remove(); - if (term.length > 0) { - // Generate a search result block - const h5 = $('
').text('Search results'); - const foundEmojis = this.findMatchingEmojiElements(term).show(); - const ul = $('
    ').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); - $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); - $('.emoji-menu-content').append(h5).append(ul); - } else { - $('.emoji-menu-content').children().show(); } -}; -AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) { - const safeTerm = term.toLowerCase(); + getFrequentlyUsedEmojis() { + return this.frequentlyUsedEmojis || (() => { + const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(',')); + this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter( + inputName => this.emoji.isEmojiNameValid(inputName), + ); - const namesMatchingAlias = []; - Object.keys(emojiAliases).forEach((alias) => { - if (alias.indexOf(safeTerm) >= 0) { - namesMatchingAlias.push(emojiAliases[alias]); + return this.frequentlyUsedEmojis; + })(); + } + + setupSearch() { + const $search = $('.js-emoji-menu-search'); + + this.registerEventListener('on', $search, 'input', (e) => { + const term = $(e.target).val().trim(); + this.searchEmojis(term); + }); + + const $menu = $('.emoji-menu'); + this.registerEventListener('on', $menu, transitionEndEventString, (e) => { + if (e.target === e.currentTarget) { + // Clear the search + this.searchEmojis(''); + } + }); + } + + searchEmojis(term) { + const $search = $('.js-emoji-menu-search'); + $search.val(term); + + // Clean previous search results + $('ul.emoji-menu-search, h5.emoji-search-title').remove(); + if (term.length > 0) { + // Generate a search result block + const h5 = $('
    ').text('Search results'); + const foundEmojis = this.findMatchingEmojiElements(term).show(); + const ul = $('
      ').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); + $('.emoji-menu-content ul, .emoji-menu-content h5').hide(); + $('.emoji-menu-content').append(h5).append(ul); + } else { + $('.emoji-menu-content').children().show(); } - }); - const $matchingElements = namesMatchingAlias.concat(safeTerm) - .reduce( - ($result, searchTerm) => - $result.add($(`.emoji-menu-list:not(.frequent-emojis) [data-name*="${searchTerm}"]`)), - $([]), - ); - return $matchingElements.closest('li').clone(); -}; + } -AwardsHandler.prototype.destroy = function destroy() { - this.eventListeners.forEach((entry) => { - entry.element.off.call(entry.element, ...entry.args); - }); - $('.emoji-menu').remove(); -}; + findMatchingEmojiElements(query) { + const emojiMatches = this.emoji.filterEmojiNamesByAlias(query); + const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]'); + const $matchingElements = $emojiElements + .filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0); + return $matchingElements.closest('li').clone(); + } -export default AwardsHandler; + destroy() { + this.eventListeners.forEach((entry) => { + entry.element.off.call(entry.element, ...entry.args); + }); + $('.emoji-menu').remove(); + } +} + +let awardsHandlerPromise = null; +export default function loadAwardsHandler(reload = false) { + if (!awardsHandlerPromise || reload) { + awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji') + .then(Emoji => new AwardsHandler(Emoji)); + } + return awardsHandlerPromise; +} diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index 3bea460dcc..e00af4b2fa 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,23 +1,8 @@ import autosize from 'vendor/autosize'; -$(() => { - const $fields = $('.js-autosize'); +document.addEventListener('DOMContentLoaded', () => { + const autosizeEls = document.querySelectorAll('.js-autosize'); - $fields.on('autosize:resized', function resized() { - const $field = $(this); - $field.data('height', $field.outerHeight()); - }); - - $fields.on('resize.autosize', function resize() { - const $field = $(this); - if ($field.data('height') !== $field.outerHeight()) { - $field.data('height', $field.outerHeight()); - autosize.destroy($field); - $field.css('max-height', window.outerHeight); - } - }); - - autosize($fields); - autosize.update($fields); - $fields.css('resize', 'vertical'); + autosize(autosizeEls); + autosize.update(autosizeEls); }); diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index 23d91fdb25..7e98e04303 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -1,75 +1,9 @@ import installCustomElements from 'document-register-element'; -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; -import { getUnicodeSupportMap } from './gl_emoji/unicode_support_map'; -import { isEmojiUnicodeSupported } from './gl_emoji/is_emoji_unicode_supported'; +import isEmojiUnicodeSupported from '../emoji/support'; installCustomElements(window); -const generatedUnicodeSupportMap = getUnicodeSupportMap(); - -function emojiImageTag(name, src) { - return `:${name}:`; -} - -function assembleFallbackImageSrc(inputName) { - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; - - return fallbackImageSrc; -} -const glEmojiTagDefaults = { - sprite: false, - forceFallback: false, -}; -function glEmojiTag(inputName, options) { - const opts = Object.assign({}, glEmojiTagDefaults, options); - let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - let emojiInfo = emojiMap[name]; - // Fallback to question mark for unknown emojis - if (!emojiInfo) { - name = 'grey_question'; - emojiInfo = emojiMap[name]; - } - - const fallbackImageSrc = assembleFallbackImageSrc(name); - const fallbackSpriteClass = `emoji-${name}`; - - const classList = []; - if (opts.forceFallback && opts.sprite) { - classList.push('emoji-icon'); - classList.push(fallbackSpriteClass); - } - const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : ''; - const fallbackSpriteAttribute = opts.sprite ? `data-fallback-sprite-class="${fallbackSpriteClass}"` : ''; - let contents = emojiInfo.moji; - if (opts.forceFallback && !opts.sprite) { - contents = emojiImageTag(name, fallbackImageSrc); - } - - return ` - - ${contents} - - `; -} - -function installGlEmojiElement() { +export default function installGlEmojiElement() { const GlEmojiElementProto = Object.create(HTMLElement.prototype); GlEmojiElementProto.createdCallback = function createdCallback() { const emojiUnicode = this.textContent.trim(); @@ -88,19 +22,28 @@ function installGlEmojiElement() { const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0; if ( + emojiUnicode && isEmojiUnicode && - !isEmojiUnicodeSupported(generatedUnicodeSupportMap, emojiUnicode, unicodeVersion) + !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion) ) { // CSS sprite fallback takes precedence over image fallback if (hasCssSpriteFalback) { // IE 11 doesn't like adding multiple at once :( this.classList.add('emoji-icon'); this.classList.add(fallbackSpriteClass); - } else if (hasImageFallback) { - this.innerHTML = emojiImageTag(name, fallbackSrc); } else { - const src = assembleFallbackImageSrc(name); - this.innerHTML = emojiImageTag(name, src); + import(/* webpackChunkName: 'emoji' */ '../emoji') + .then(({ emojiImageTag, emojiFallbackImageSrc }) => { + if (hasImageFallback) { + this.innerHTML = emojiImageTag(name, fallbackSrc); + } else { + const src = emojiFallbackImageSrc(name); + this.innerHTML = emojiImageTag(name, src); + } + }) + .catch(() => { + // do nothing + }); } } }; @@ -109,9 +52,3 @@ function installGlEmojiElement() { prototype: GlEmojiElementProto, }); } - -export { - installGlEmojiElement, - glEmojiTag, - emojiImageTag, -}; diff --git a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js b/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js deleted file mode 100644 index be4aeb32c4..0000000000 --- a/app/assets/javascripts/behaviors/gl_emoji/is_emoji_name_valid.js +++ /dev/null @@ -1,11 +0,0 @@ -import emojiMap from 'emojis/digests.json'; -import emojiAliases from 'emojis/aliases.json'; - -function isEmojiNameValid(inputName) { - const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? - emojiAliases[inputName] : inputName; - - return name && emojiMap[name]; -} - -export default isEmojiNameValid; diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index 5b931e6cfa..44b2c974b9 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -1,7 +1,7 @@ import './autosize'; import './bind_in_out'; import './details_behavior'; -import { installGlEmojiElement } from './gl_emoji'; +import installGlEmojiElement from './gl_emoji'; import './quick_submit'; import './requires_input'; import './toggler_behavior'; diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 1f9e044808..bc69361646 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -40,7 +40,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { e.preventDefault(); const $form = $(e.target).closest('form'); - const $submitButton = $form.find('input[type=submit], button[type=submit]'); + const $submitButton = $form.find('input[type=submit], button[type=submit]').first(); if (!$submitButton.attr('disabled')) { $submitButton.trigger('click', [e]); diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index b20d108aa2..035a7e5c43 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import '../commons/bootstrap'; // Requires Input behavior @@ -48,7 +49,9 @@ function hideOrShowHelpBlock(form) { $(() => { const $form = $('form.js-requires-input'); - $form.requiresInput(); - hideOrShowHelpBlock($form); - $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + if ($form) { + $form.requiresInput(); + hideOrShowHelpBlock($form); + $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form)); + } }); diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 77e92ff8ca..b70b0a9bbf 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,4 +1,3 @@ - // Toggle button. Show/hide content inside parent container. // Button does not change visibility. If button has icon - it changes chevron style. // diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js index cdbfe36ca1..c17877a276 100644 --- a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js @@ -1,5 +1,3 @@ -/* global Flash */ - import sqljs from 'sql.js'; import { template as _template } from 'underscore'; @@ -15,19 +13,27 @@ const PREVIEW_TEMPLATE = _template(` class BalsamiqViewer { constructor(viewer) { this.viewer = viewer; - this.endpoint = this.viewer.dataset.endpoint; } - loadFile() { - const xhr = new XMLHttpRequest(); + loadFile(endpoint) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); - xhr.open('GET', this.endpoint, true); - xhr.responseType = 'arraybuffer'; + xhr.open('GET', endpoint, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = loadEvent => this.fileLoaded(loadEvent, resolve, reject); + xhr.onerror = reject; - xhr.onload = this.renderFile.bind(this); - xhr.onerror = BalsamiqViewer.onError; + xhr.send(); + }); + } - xhr.send(); + fileLoaded(loadEvent, resolve, reject) { + if (loadEvent.target.status !== 200) return reject(); + + this.renderFile(loadEvent); + + return resolve(); } renderFile(loadEvent) { @@ -103,12 +109,6 @@ class BalsamiqViewer { static parseTitle(resource) { return JSON.parse(resource.values[0][2]).name; } - - static onError() { - const flash = new Flash('Balsamiq file could not be loaded.'); - - return flash; - } } export default BalsamiqViewer; diff --git a/app/assets/javascripts/blob/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq_viewer.js index 1dacf84470..8641a6fdae 100644 --- a/app/assets/javascripts/blob/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq_viewer.js @@ -1,6 +1,22 @@ +/* global Flash */ + import BalsamiqViewer from './balsamiq/balsamiq_viewer'; -document.addEventListener('DOMContentLoaded', () => { - const balsamiqViewer = new BalsamiqViewer(document.getElementById('js-balsamiq-viewer')); - balsamiqViewer.loadFile(); -}); +function onError() { + const flash = new window.Flash('Balsamiq file could not be loaded.'); + + return flash; +} + +function loadBalsamiqFile() { + const viewer = document.getElementById('js-balsamiq-viewer'); + + if (!(viewer instanceof Element)) return; + + const endpoint = viewer.dataset.endpoint; + + const balsamiqViewer = new BalsamiqViewer(viewer); + balsamiqViewer.loadFile(endpoint).catch(onError); +} + +$(loadBalsamiqFile); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 4568b86f29..26d3419a16 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -1,9 +1,24 @@ /* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ /* global Dropzone */ +import '../lib/utils/url_utility'; +import { HIDDEN_CLASS } from '../lib/utils/constants'; + +function toggleLoading($el, $icon, loading) { + if (loading) { + $el.disable(); + $icon.removeClass(HIDDEN_CLASS); + } else { + $el.enable(); + $icon.addClass(HIDDEN_CLASS); + } +} export default class BlobFileDropzone { constructor(form, method) { const formDropzone = form.find('.dropzone'); + const submitButton = form.find('#submit-all'); + const submitButtonLoadingIcon = submitButton.find('.js-loading-icon'); + const dropzoneMessage = form.find('.dz-message'); Dropzone.autoDiscover = false; const dropzone = formDropzone.dropzone({ @@ -26,16 +41,24 @@ export default class BlobFileDropzone { }, init: function () { this.on('addedfile', function () { + toggleLoading(submitButton, submitButtonLoadingIcon, false); + dropzoneMessage.addClass(HIDDEN_CLASS); $('.dropzone-alerts').html('').hide(); }); + this.on('removedfile', function () { + toggleLoading(submitButton, submitButtonLoadingIcon, false); + dropzoneMessage.removeClass(HIDDEN_CLASS); + }); this.on('success', function (header, response) { - window.location.href = response.filePath; + $('#modal-upload-blob').modal('hide'); + window.gl.utils.visitUrl(response.filePath); }); this.on('maxfilesexceeded', function (file) { + dropzoneMessage.addClass(HIDDEN_CLASS); this.removeFile(file); }); this.on('sending', function (file, xhr, formData) { - formData.append('branch_name', form.find('input[name="branch_name"]').val()); + formData.append('branch_name', form.find('.js-branch-name').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('commit_message', form.find('.js-commit-message').val()); }); @@ -48,14 +71,15 @@ export default class BlobFileDropzone { }, }); - const submitButton = form.find('#submit-all')[0]; - submitButton.addEventListener('click', function (e) { + submitButton.on('click', (e) => { e.preventDefault(); e.stopPropagation(); if (dropzone[0].dropzone.getQueuedFiles().length === 0) { // eslint-disable-next-line no-alert alert('Please select a file'); + return false; } + toggleLoading(submitButton, submitButtonLoadingIcon, true); dropzone[0].dropzone.processQueue(); return false; }); diff --git a/app/assets/javascripts/blob/create_branch_dropdown.js b/app/assets/javascripts/blob/create_branch_dropdown.js deleted file mode 100644 index 95517f51b1..0000000000 --- a/app/assets/javascripts/blob/create_branch_dropdown.js +++ /dev/null @@ -1,88 +0,0 @@ -class CreateBranchDropdown { - constructor(el, targetBranchDropdown) { - this.targetBranchDropdown = targetBranchDropdown; - this.el = el; - this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back'); - this.cancelButton = this.el.querySelector('.js-cancel-branch-btn'); - this.newBranchField = this.el.querySelector('#new_branch_name'); - this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn'); - - this.newBranchCreateButton.setAttribute('disabled', ''); - - this.addBindings(); - this.cleanupWrapper = this.cleanup.bind(this); - document.addEventListener('beforeunload', this.cleanupWrapper); - } - - cleanup() { - this.cleanBindings(); - document.removeEventListener('beforeunload', this.cleanupWrapper); - } - - cleanBindings() { - this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper); - this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper); - this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper); - this.dropdownBack.removeEventListener('click', this.resetFormWrapper); - this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper); - this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper); - } - - addBindings() { - this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this); - this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this); - this.resetFormWrapper = this.resetForm.bind(this); - this.handleCancelClickWrapper = this.handleCancelClick.bind(this); - this.createBranchWrapper = this.createBranch.bind(this); - - this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper); - this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper); - this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper); - this.dropdownBack.addEventListener('click', this.resetFormWrapper); - this.cancelButton.addEventListener('click', this.handleCancelClickWrapper); - this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper); - } - - handleCancelClick(e) { - e.preventDefault(); - e.stopPropagation(); - - this.resetForm(); - this.dropdownBack.click(); - } - - handleNewBranchKeydown(e) { - const keyCode = e.which; - const ENTER_KEYCODE = 13; - if (keyCode === ENTER_KEYCODE) { - this.createBranch(e); - } - } - - enableBranchCreateButton() { - if (this.newBranchField.value !== '') { - this.newBranchCreateButton.removeAttribute('disabled'); - } else { - this.newBranchCreateButton.setAttribute('disabled', ''); - } - } - - resetForm() { - this.newBranchField.value = ''; - this.enableBranchCreateButtonWrapper(); - } - - createBranch(e) { - e.preventDefault(); - - if (this.newBranchCreateButton.getAttribute('disabled') === '') { - return; - } - const newBranchName = this.newBranchField.value; - this.targetBranchDropdown.setNewBranch(newBranchName); - this.resetForm(); - } -} - -window.gl = window.gl || {}; -gl.CreateBranchDropdown = CreateBranchDropdown; diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js index ab5b3751c4..5ae30990ae 100644 --- a/app/assets/javascripts/blob/file_template_selector.js +++ b/app/assets/javascripts/blob/file_template_selector.js @@ -1,5 +1,3 @@ -/* global Api */ - export default class FileTemplateSelector { constructor(mediator) { this.mediator = mediator; @@ -65,4 +63,3 @@ export default class FileTemplateSelector { this.reportSelection(opts); } } - diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 36fe8a7184..27312d718b 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -51,8 +51,9 @@ export default () => { methods: { loadFile() { this.$http.get(el.dataset.endpoint) + .then(response => response.json()) .then((res) => { - this.json = res.json(); + this.json = res; this.loading = false; }) .catch((e) => { diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js deleted file mode 100644 index d52d69b127..0000000000 --- a/app/assets/javascripts/blob/target_branch_dropdown.js +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-disable class-methods-use-this */ -const SELECT_ITEM_MSG = 'Select'; - -class TargetBranchDropDown { - constructor(dropdown) { - this.dropdown = dropdown; - this.$dropdown = $(dropdown); - this.fieldName = this.dropdown.getAttribute('data-field-name'); - this.form = this.dropdown.closest('form'); - this.createDropdown(); - } - - static bootstrap() { - const dropdowns = document.querySelectorAll('.js-project-branches-dropdown'); - [].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown)); - } - - createDropdown() { - const self = this; - this.$dropdown.glDropdown({ - selectable: true, - filterable: true, - search: { - fields: ['title'], - }, - data: (term, callback) => $.ajax({ - url: self.dropdown.getAttribute('data-refs-url'), - data: { - ref: self.dropdown.getAttribute('data-ref'), - show_all: true, - }, - dataType: 'json', - }).done(refs => callback(self.dropdownData(refs))), - toggleLabel(item, el) { - if (el.is('.is-active')) { - return item.text; - } - return SELECT_ITEM_MSG; - }, - clicked(options) { - options.e.preventDefault(); - self.onClick.call(self); - }, - fieldName: self.fieldName, - }); - return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this); - } - - onClick() { - this.enableSubmit(); - this.$dropdown.trigger('change.branch'); - } - - enableSubmit() { - const submitBtn = this.form.querySelector('[type="submit"]'); - if (this.branchInput && this.branchInput.value) { - submitBtn.removeAttribute('disabled'); - } else { - submitBtn.setAttribute('disabled', ''); - } - } - - dropdownData(refs) { - const branchList = this.dropdownItems(refs); - this.cachedRefs = refs; - this.addDefaultBranch(branchList); - this.addNewBranch(branchList); - return { Branches: branchList }; - } - - dropdownItems(refs) { - return refs.map(this.dropdownItem); - } - - dropdownItem(ref) { - return { id: ref, text: ref, title: ref }; - } - - addDefaultBranch(branchList) { - // when no branch is selected do nothing - if (!this.branchInput) { - return; - } - - const branchInputVal = this.branchInput.value; - const currentBranchIndex = this.searchBranch(branchList, branchInputVal); - - if (currentBranchIndex === -1) { - this.unshiftBranch(branchList, this.dropdownItem(branchInputVal)); - } - } - - addNewBranch(branchList) { - if (this.newBranch) { - this.unshiftBranch(branchList, this.newBranch); - } - } - - searchBranch(branchList, branchName) { - return _.findIndex(branchList, el => branchName === el.id); - } - - unshiftBranch(branchList, branch) { - const branchIndex = this.searchBranch(branchList, branch.id); - - if (branchIndex === -1) { - branchList.unshift(branch); - } - } - - setNewBranch(newBranchName) { - this.newBranch = this.dropdownItem(newBranchName); - this.refreshData(); - this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName)); - } - - refreshData() { - this.glDropdown.fullData = this.dropdownData(this.cachedRefs); - this.clearFilter(); - } - - clearFilter() { - // apply an empty filter in order to refresh the data - this.glDropdown.filter.filter(''); - this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = ''; - } - - selectBranch(index) { - const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index]; - - if (!branch.classList.contains('is-active')) { - branch.click(); - } else { - this.closeDropdown(); - } - } - - closeDropdown() { - this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click(); - } - - get branchInput() { - return this.form.querySelector(`input[name="${this.fieldName}"]`); - } - - get glDropdown() { - return this.$dropdown.data('glDropdown'); - } -} - -window.gl = window.gl || {}; -gl.TargetBranchDropDown = TargetBranchDropDown; diff --git a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js index f2f81af137..9c41e429c8 100644 --- a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js +++ b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js @@ -1,4 +1,4 @@ -/* global Api */ +import Api from '../../api'; import FileTemplateSelector from '../file_template_selector'; diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js index 3cb7b960aa..45fb614fe0 100644 --- a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js +++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js @@ -1,4 +1,4 @@ -/* global Api */ +import Api from '../../api'; import FileTemplateSelector from '../file_template_selector'; diff --git a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js index 7efda8e7f5..a894953cc8 100644 --- a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js +++ b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js @@ -1,4 +1,4 @@ -/* global Api */ +import Api from '../../api'; import FileTemplateSelector from '../file_template_selector'; diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js index 1d757332f6..b7c4da0f62 100644 --- a/app/assets/javascripts/blob/template_selectors/license_selector.js +++ b/app/assets/javascripts/blob/template_selectors/license_selector.js @@ -1,4 +1,4 @@ -/* global Api */ +import Api from '../../api'; import FileTemplateSelector from '../file_template_selector'; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 3eddf595f3..187fab084f 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,17 +1,38 @@ /* global Flash */ export default class BlobViewer { constructor() { + BlobViewer.initAuxiliaryViewer(); + + this.initMainViewers(); + } + + static initAuxiliaryViewer() { + const auxiliaryViewer = document.querySelector('.blob-viewer[data-type="auxiliary"]'); + if (!auxiliaryViewer) return; + + BlobViewer.loadViewer(auxiliaryViewer); + } + + initMainViewers() { + this.$fileHolder = $('.file-holder'); + if (!this.$fileHolder.length) return; + this.switcher = document.querySelector('.js-blob-viewer-switcher'); this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); - this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]'); - this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); - this.$fileHolder = $('.file-holder'); - let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type'); + this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]'); + this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]'); this.initBindings(); + this.switchToInitialViewer(); + } + + switchToInitialViewer() { + const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)'); + let initialViewerName = initialViewer.getAttribute('data-type'); + if (this.switcher && location.hash.indexOf('#L') === 0) { initialViewerName = 'simple'; } @@ -61,41 +82,13 @@ export default class BlobViewer { $(this.copySourceBtn).tooltip('fixTitle'); } - loadViewer(viewerParam) { - const viewer = viewerParam; - const url = viewer.getAttribute('data-url'); - - if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) { - return; - } - - viewer.setAttribute('data-loading', 'true'); - - $.ajax({ - url, - dataType: 'JSON', - }) - .fail(() => new Flash('Error loading source view')) - .done((data) => { - viewer.innerHTML = data.html; - $(viewer).renderGFM(); - - viewer.setAttribute('data-loaded', 'true'); - - this.$fileHolder.trigger('highlight:line'); - gl.utils.handleLocationHash(); - - this.toggleCopyButtonState(); - }); - } - switchToViewer(name) { - const newViewer = document.querySelector(`.blob-viewer[data-type='${name}']`); + const newViewer = this.$fileHolder[0].querySelector(`.blob-viewer[data-type='${name}']`); if (this.activeViewer === newViewer) return; const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active'); const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`); - const oldViewer = document.querySelector(`.blob-viewer:not([data-type='${name}'])`); + const oldViewer = this.$fileHolder[0].querySelector(`.blob-viewer:not([data-type='${name}'])`); if (oldButton) { oldButton.classList.remove('active'); @@ -116,6 +109,41 @@ export default class BlobViewer { this.toggleCopyButtonState(); - this.loadViewer(newViewer); + BlobViewer.loadViewer(newViewer) + .then((viewer) => { + $(viewer).renderGFM(); + + this.$fileHolder.trigger('highlight:line'); + gl.utils.handleLocationHash(); + + this.toggleCopyButtonState(); + }) + .catch(() => new Flash('Error loading viewer')); + } + + static loadViewer(viewerParam) { + const viewer = viewerParam; + const url = viewer.getAttribute('data-url'); + + return new Promise((resolve, reject) => { + if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) { + resolve(viewer); + return; + } + + viewer.setAttribute('data-loading', 'true'); + + $.ajax({ + url, + dataType: 'JSON', + }) + .fail(reject) + .done((data) => { + viewer.innerHTML = data.html; + viewer.setAttribute('data-loaded', 'true'); + + resolve(viewer); + }); + }); } } diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 1c64ccf536..b5500ac116 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone'; $(() => { const editBlobForm = $('.js-edit-blob-form'); const uploadBlobForm = $('.js-upload-blob-form'); + const deleteBlobForm = $('.js-delete-blob-form'); if (editBlobForm.length) { const urlRoot = editBlobForm.data('relative-url-root'); @@ -30,4 +31,8 @@ $(() => { '.btn-upload-file', ); } + + if (deleteBlobForm.length) { + new NewCommitForm(deleteBlobForm); + } }); diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 88eb425133..89c1418014 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -2,27 +2,27 @@ /* global BoardService */ /* global Flash */ +import _ from 'underscore'; import Vue from 'vue'; import VueResource from 'vue-resource'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; - -require('./models/issue'); -require('./models/label'); -require('./models/list'); -require('./models/milestone'); -require('./models/assignee'); -require('./stores/boards_store'); -require('./stores/modal_store'); -require('./services/board_service'); -require('./mixins/modal_mixins'); -require('./mixins/sortable_default_options'); -require('./filters/due_date_filters'); -require('./components/board'); -require('./components/board_sidebar'); -require('./components/new_list_dropdown'); -require('./components/modal/index'); -require('../vue_shared/vue_resource_interceptor'); +import './models/issue'; +import './models/label'; +import './models/list'; +import './models/milestone'; +import './models/assignee'; +import './stores/boards_store'; +import './stores/modal_store'; +import './services/board_service'; +import './mixins/modal_mixins'; +import './mixins/sortable_default_options'; +import './filters/due_date_filters'; +import './components/board'; +import './components/board_sidebar'; +import './components/new_list_dropdown'; +import './components/modal/index'; +import '../vue_shared/vue_resource_interceptor'; Vue.use(VueResource); @@ -71,6 +71,7 @@ $(() => { gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId); this.filterManager = new FilteredSearchBoards(Store.filter, true); + this.filterManager.setup(); // Listen for updateTokens event eventHub.$on('updateTokens', this.updateTokens); @@ -81,13 +82,16 @@ $(() => { mounted () { Store.disabled = this.disabled; gl.boardService.all() + .then(response => response.json()) .then((resp) => { - resp.json().forEach((board) => { + resp.forEach((board) => { const list = Store.addList(board, this.defaultAvatar); if (list.type === 'closed') { list.position = Infinity; list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' }; + } else if (list.type === 'backlog') { + list.position = -1; } }); @@ -95,7 +99,8 @@ $(() => { Store.addBlankState(); this.loading = false; - }).catch(() => new Flash('An error occurred. Please try again.')); + }) + .catch(() => new Flash('An error occurred. Please try again.')); }, methods: { updateTokens() { @@ -128,7 +133,7 @@ $(() => { }, computed: { disabled() { - return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length; + return !this.store.lists.filter(list => !list.preset).length; }, tooltipTitle() { if (this.disabled) { diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 0d23bdeeb9..adb7360327 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,11 +1,10 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var */ /* global Sortable */ import Vue from 'vue'; +import AccessorUtilities from '../../lib/utils/accessor'; import boardList from './board_list'; import boardBlankState from './board_blank_state'; - -require('./board_delete'); -require('./board_list'); +import './board_delete'; const Store = gl.issueBoards.BoardsStore; @@ -24,6 +23,10 @@ gl.issueBoards.Board = Vue.extend({ disabled: Boolean, issueLinkBase: String, rootPath: String, + boardId: { + type: String, + required: true, + }, }, data () { return { @@ -80,7 +83,16 @@ gl.issueBoards.Board = Vue.extend({ methods: { showNewIssueForm() { this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm; - } + }, + toggleExpanded(e) { + if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) { + this.list.isExpanded = !this.list.isExpanded; + + if (AccessorUtilities.isLocalStorageAccessSafe()) { + localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded); + } + } + }, }, mounted () { this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({ @@ -104,4 +116,11 @@ gl.issueBoards.Board = Vue.extend({ this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions); }, + created() { + if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) { + const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false'; + + this.list.isExpanded = !isCollapsed; + } + }, }); diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index 870e115bd1..edfe7c326d 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -1,5 +1,6 @@ /* global ListLabel */ +import _ from 'underscore'; import Cookies from 'js-cookie'; const Store = gl.issueBoards.BoardsStore; @@ -64,8 +65,9 @@ export default { // Save the labels gl.boardService.generateDefaultLists() - .then((resp) => { - resp.json().forEach((listObj) => { + .then(resp => resp.json()) + .then((data) => { + data.forEach((listObj) => { const list = Store.findList('title', listObj.title); list.id = listObj.id; diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js index f591134c54..079fb6438b 100644 --- a/app/assets/javascripts/boards/components/board_card.js +++ b/app/assets/javascripts/boards/components/board_card.js @@ -1,4 +1,4 @@ -require('./issue_card_inner'); +import './issue_card_inner'; const Store = gl.issueBoards.BoardsStore; diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index 49a775002c..bebca17fb1 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -2,6 +2,7 @@ import boardNewIssue from './board_new_issue'; import boardCard from './board_card'; import eventHub from '../eventhub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; const Store = gl.issueBoards.BoardsStore; @@ -44,6 +45,7 @@ export default { components: { boardCard, boardNewIssue, + loadingIcon, }, methods: { listHeight() { @@ -55,6 +57,9 @@ export default { scrollTop() { return this.$refs.list.scrollTop + this.listHeight(); }, + scrollToTop() { + this.$refs.list.scrollTop = 0; + }, loadNextPage() { const getIssues = this.list.nextPage(); const loadingDone = () => { @@ -106,6 +111,7 @@ export default { }, created() { eventHub.$on(`hide-issue-form-${this.list.id}`, this.toggleForm); + eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop); }, mounted() { const options = gl.issueBoards.getBoardSortableDefaultOptions({ @@ -148,6 +154,7 @@ export default { }, beforeDestroy() { eventHub.$off(`hide-issue-form-${this.list.id}`, this.toggleForm); + eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop); this.$refs.list.removeEventListener('scroll', this.onScroll); }, template: ` @@ -156,14 +163,13 @@ export default { class="board-list-loading text-center" aria-label="Loading issues" v-if="loading"> - +
- + + +
    - + + + Showing all issues diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js index 1ce95b6213..4af8b0c771 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -17,7 +17,7 @@ export default { methods: { submit(e) { e.preventDefault(); - if (this.title.trim() === '') return; + if (this.title.trim() === '') return Promise.resolve(); this.error = false; @@ -29,7 +29,10 @@ export default { assignees: [], }); - this.list.newIssue(issue) + eventHub.$emit(`scroll-board-list-${this.list.id}`); + this.cancel(); + + return this.list.newIssue(issue) .then(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$refs.submitButton).enable(); @@ -47,8 +50,6 @@ export default { // Show error message this.error = true; }); - - this.cancel(); }, cancel() { this.title = ''; @@ -75,6 +76,7 @@ export default { type="text" v-model="title" ref="input" + autocomplete="off" :id="list.id + '-title'" />