diff --git a/.gitattributes b/.gitattributes
index 7e800609e6c76c14893419965f1ce0ba400d9ace..17cbaa5eef5e0560c1036e745f1ebf62c078fa64 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
-CHANGELOG merge=union
\ No newline at end of file
+CHANGELOG merge=union
+*.js.es6 gitlab-language=javascript
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ff8aa351226a1ac8a8e1171ed309fddfbb604aa2..2eda2a6007d1e1bd01a8e97a83b6704a8f21a121 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,9 +1,5 @@
 image: "ruby:2.1"
 
-services:
-  - mysql:latest
-  - redis:alpine
-
 cache:
   key: "ruby21"
   paths:
@@ -32,9 +28,9 @@ stages:
 - prepare
 - test
 - post-test
+- pages
 
 # Prepare and merge knapsack tests
-
 .knapsack-state: &knapsack-state
   services: []
   variables:
@@ -45,6 +41,7 @@ stages:
     paths:
     - knapsack/
   artifacts:
+    expire_in: 31d
     paths:
     - knapsack/
 
@@ -68,8 +65,14 @@ update-knapsack:
 
 # Execute all testing suites
 
+.use-db: &use-db
+  services:
+    - mysql:latest
+    - redis:alpine
+
 .rspec-knapsack: &rspec-knapsack
   stage: test
+  <<: *use-db
   script:
     - bundle exec rake assets:precompile 2>/dev/null
     - JOB_NAME=( $CI_BUILD_NAME )
@@ -80,11 +83,14 @@ update-knapsack:
     - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
     - knapsack rspec
   artifacts:
+    expire_in: 31d
     paths:
     - knapsack/
+    - coverage/
 
 .spinach-knapsack: &spinach-knapsack
   stage: test
+  <<: *use-db
   script:
     - bundle exec rake assets:precompile 2>/dev/null
     - JOB_NAME=( $CI_BUILD_NAME )
@@ -95,8 +101,10 @@ update-knapsack:
     - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
     - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
   artifacts:
+    expire_in: 31d
     paths:
     - knapsack/
+    - coverage/
 
 rspec 0 20: *rspec-knapsack
 rspec 1 20: *rspec-knapsack
@@ -133,6 +141,7 @@ spinach 9 10: *spinach-knapsack
 # Execute all testing suites against Ruby 2.3
 .ruby-23: &ruby-23
   image: "ruby:2.3"
+  <<: *use-db
   only:
     - master
   cache:
@@ -148,7 +157,7 @@ spinach 9 10: *spinach-knapsack
 .spinach-knapsack-ruby23: &spinach-knapsack-ruby23
   <<: *spinach-knapsack
   <<: *ruby-23
-  
+
 rspec 0 20 ruby23: *rspec-knapsack-ruby23
 rspec 1 20 ruby23: *rspec-knapsack-ruby23
 rspec 2 20 ruby23: *rspec-knapsack-ruby23
@@ -183,27 +192,62 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23
 
 # Other generic tests
 
+.ruby-static-analysis: &ruby-static-analysis
+  variables:
+    SIMPLECOV: "false"
+    USE_DB: "false"
+    USE_BUNDLE_INSTALL: "true"
+
 .exec: &exec
+  <<: *ruby-static-analysis
   stage: test
   script:
     - bundle exec $CI_BUILD_NAME
 
-teaspoon: *exec
 rubocop: *exec
 rake scss_lint: *exec
 rake brakeman: *exec
 rake flog: *exec
 rake flay: *exec
-rake db:migrate:reset: *exec
 license_finder: *exec
+rake downtime_check: *exec
+
+rake db:migrate:reset:
+  stage: test
+  <<: *use-db
+  script:
+    - rake db:migrate:reset
+
+teaspoon:
+  stage: test
+  <<: *use-db
+  script:
+    - teaspoon
 
 bundler:audit:
   stage: test
+  <<: *ruby-static-analysis
   only:
     - master
   script:
     - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
 
+coverage:
+  stage: post-test
+  services: []
+  variables:
+    USE_DB: "false"
+    USE_BUNDLE_INSTALL: "true"
+  script:
+    - bundle exec scripts/merge-simplecov
+  artifacts:
+    name: coverage
+    expire_in: 31d
+    paths:
+    - coverage/index.html
+    - coverage/assets/
+
+
 # Notify slack in the end
 
 notify:slack:
@@ -216,3 +260,18 @@ notify:slack:
     - tags@gitlab-org/gitlab-ce
     - master@gitlab-org/gitlab-ee
     - tags@gitlab-org/gitlab-ee
+
+pages:
+  before_script: []
+  stage: pages
+  dependencies:
+    - coverage
+  script:
+    - mv public/ .public/
+    - mkdir public/
+    - mv coverage public/coverage-ruby
+  artifacts:
+    paths:
+      - public
+  only:
+    - master
diff --git a/.rubocop.yml b/.rubocop.yml
index db0bcfadcf4fe2f0b8d189f1e2ffa66560a2cea2..556a5d11a394a7152bced1f73bb63a8ba565f205 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -291,6 +291,10 @@ Style/MultilineMethodDefinitionBraceLayout:
 Style/MultilineOperationIndentation:
   Enabled: false
 
+# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
+Style/MultilineTernaryOperator:
+  Enabled: true
+
 # Favor unless over if for negative conditions (or control flow or).
 Style/NegatedIf:
   Enabled: true
@@ -506,6 +510,15 @@ Metrics/PerceivedComplexity:
 
 #################### Lint ################################
 
+# Checks for useless access modifiers.
+Lint/UselessAccessModifier:
+  Enabled: true
+
+# Checks for attempts to use `private` or `protected` to set the visibility
+# of a class method, which does not work.
+Lint/IneffectiveAccessModifier:
+  Enabled: false
+
 # Checks for ambiguous operators in the first argument of a method invocation
 # without parentheses.
 Lint/AmbiguousOperator:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 9310e71188978e5aa3578096fe61f53a4fd3dff9..76ae59527533d9fbfb39e89d6427da38d52cb96b 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -19,10 +19,6 @@ Lint/AssignmentInCondition:
 Lint/HandleExceptions:
   Enabled: false
 
-# Offense count: 21
-Lint/IneffectiveAccessModifier:
-  Enabled: false
-
 # Offense count: 2
 Lint/Loop:
   Enabled: false
@@ -48,10 +44,6 @@ Lint/UnusedBlockArgument:
 Lint/UnusedMethodArgument:
   Enabled: false
 
-# Offense count: 11
-Lint/UselessAccessModifier:
-  Enabled: false
-
 # Offense count: 12
 # Cop supports --auto-correct.
 Performance/PushSplat:
@@ -226,10 +218,6 @@ Style/LineEndConcatenation:
 Style/MethodCallParentheses:
   Enabled: false
 
-# Offense count: 3
-Style/MultilineTernaryOperator:
-  Enabled: false
-
 # Offense count: 62
 # Cop supports --auto-correct.
 Style/MutableConstant:
diff --git a/.simplecov b/.simplecov
deleted file mode 100644
index d979288df44bfa11d3bbc74b52bae975631a4dbf..0000000000000000000000000000000000000000
--- a/.simplecov
+++ /dev/null
@@ -1,4 +0,0 @@
-# .simplecov
-SimpleCov.start 'rails' do
-  merge_timeout 3600
-end
diff --git a/CHANGELOG b/CHANGELOG
index d8ef5866a1a1ae5af3435ae010e3a4659ebbe2bf..db2617dcbd7db63a0a59c69351fcec2c98c3b971 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,50 +1,168 @@
 Please view this file on the master branch, on stable branches it's out of date.
 
-v 8.10.0 (unreleased)
+v 8.11.0 (unreleased)
+  - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
+  - Improve diff performance by eliminating redundant checks for text blobs
+  - Convert switch icon into icon font (ClemMakesApps)
+  - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
+  - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
+  - Fix CI status icon link underline (ClemMakesApps)
+  - The Repository class is now instrumented
+  - Cache the commit author in RequestStore to avoid extra lookups in PostReceive
+  - Expand commit message width in repo view (ClemMakesApps)
+  - Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
+  - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
+  - Optimize maximum user access level lookup in loading of notes
+  - Add "No one can push" as an option for protected branches. !5081
+  - Environments have an url to link to
+  - Limit git rev-list output count to one in forced push check
+  - Clean up unused routes (Josef Strzibny)
+  - Add green outline to New Branch button. !5447 (winniehell)
+  - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
+  - Remove delay when hitting "Reply..." button on page with a lot of discussions
+  - Retrieve rendered HTML from cache in one request
+  - Fix renaming repository when name contains invalid chararacters under project settings
+  - Optimize checking if a user has read access to a list of issues !5370
+  - Nokogiri's various parsing methods are now instrumented
+  - Add simple identifier to public SSH keys (muteor)
+  - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
+  - Include old revision in merge request update hooks (Ben Boeckel)
+  - Add build event color in HipChat messages (David Eisner)
+  - Make fork counter always clickable. !5463 (winniehell)
+  - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
+  - The overhead of instrumented method calls has been reduced
+  - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
+  - Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
+  - Bump gitlab_git to speedup DiffCollection iterations
+  - Make branches sortable without push permission !5462 (winniehell)
+  - Check for Ci::Build artifacts at database level on pipeline partial
+  - Convert image diff background image to CSS (ClemMakesApps)
+  - Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
+  - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
+  - Fix search for notes which belongs to deleted objects
+  - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
+  - Allow branch names ending with .json for graph and network page !5579 (winniehell)
+  - Add the `sprockets-es6` gem
+  - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
+  - Profile requests when a header is passed
+  - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
+  - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
+  - Add commit stats in commit api. !5517 (dixpac)
+  - Add CI configuration button on project page
+  - Make error pages responsive (Takuya Noguchi)
+  - Change requests_profiles resource constraint to catch virtually any file
+  - Reduce number of queries made for merge_requests/:id/diffs
+  - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
+  - Fix RequestProfiler::Middleware error when code is reloaded in development
+  - Catch what warden might throw when profiling requests to re-throw it
+
+v 8.10.3
+  - Fix Import/Export issue importing milestones and labels not associated properly. !5426
+  - Fix timing problems running imports on production. !5523
+  - Add a log message when a project is scheduled for destruction for debugging. !5540
+  - Fix hooks missing on imported GitLab projects. !5549
+  - Properly abort a merge when merge conflicts occur. !5569
+  - Fix importer for GitHub Pull Requests when a branch was removed. !5573
+  - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584
+  - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
+
+v 8.10.2
+  - User can now search branches by name. !5144
+  - Page is now properly rendered after committing the first file and creating the first branch. !5399
+  - Add branch or tag icon to ref in builds page. !5434
+  - Fix backup restore. !5459
+  - Use project ID in repository cache to prevent stale data from persisting across projects. !5460
+  - Fix issue with autocomplete search not working with enter key. !5466
+  - Add iid to MR API response. !5468
+  - Disable MySQL foreign key checks before dropping all tables. !5472
+  - Ensure relative paths for video are rewritten as we do for images. !5474
+  - Ensure current user can retry a build before showing the 'Retry' button. !5476
+  - Add ENV variable to skip repository storages validations. !5478
+  - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486
+  - Don't show comment button in gutter of diffs on MR discussion tab. !5493
+  - Rescue Rugged::OSError (lock exists) when creating references. !5497
+  - Fix expand all diffs button in compare view. !5500
+  - Show release notes in tags list. !5503
+  - Fix a bug where forking a project from a repository storage to another would fail. !5509
+  - Fix missing schema update for `20160722221922`. !5512
+  - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516
+
+v 8.10.1
+  - Refactor repository storages documentation. !5428
+  - Gracefully handle case when keep-around references are corrupted or exist already. !5430
+  - Add detailed info on storage path mountpoints. !5437
+  - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444
+  - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446
+  - Ignore invalid trusted proxies in X-Forwarded-For header. !5454
+  - Add links to the real markdown.md file for all GFM examples. !5458
+
+v 8.10.0
   - Fix profile activity heatmap to show correct day name (eanplatter)
+  - Speed up ExternalWikiHelper#get_project_wiki_path
   - Expose {should,force}_remove_source_branch (Ben Boeckel)
+  - Add the functionality to be able to rename a file. !5049
   - Disable PostgreSQL statement timeout during migrations
-  - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
+  - Fix projects dropdown loading performance with a simplified api cal. !5113
   - Fix commit builds API, return all builds for all pipelines for given commit. !4849
   - Replace Haml with Hamlit to make view rendering faster. !3666
   - Refresh the branch cache after `git gc` runs
+  - Allow to disable request access button on projects/groups
   - Refactor repository paths handling to allow multiple git mount points
-  - Optimize system note visibility checking by memoizing the visible reference count !5070
+  - Optimize system note visibility checking by memoizing the visible reference count. !5070
   - Add Application Setting to configure default Repository Path for new projects
   - Delete award emoji when deleting a user
-  - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
+  - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell)
+  - Add an API for downloading latest successful build from a particular branch or tag. !5347
+  - Avoid data-integrity issue when cleaning up repository archive cache.
+  - Add link to profile to commit avatar. !5163 (winniehell)
   - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
-  - Align flash messages with left side of page content !4959 (winniehell)
-  - Display tooltip for "Copy to Clipboard" button !5164 (winniehell)
-  - Use default cursor for table header of project files !5165 (winniehell)
+  - Align flash messages with left side of page content. !4959 (winniehell)
+  - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell)
+  - Use default cursor for table header of project files. !5165 (winniehell)
   - Store when and yaml variables in builds table
-  - Display last commit of deleted branch in push events !4699 (winniehell)
-  - Escape file extension when parsing search results !5141 (winniehell)
+  - Display last commit of deleted branch in push events. !4699 (winniehell)
+  - Escape file extension when parsing search results. !5141 (winniehell)
+  - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004
+  - Add image border in Markdown preview. !5162 (winniehell)
   - Apply the trusted_proxies config to the rack request object for use with rack_attack
+  - Added the ability to block sign ups using a domain blacklist. !5259
   - Upgrade to Rails 4.2.7. !5236
+  - Extend exposed environment variables for CI builds
+  - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead
+  - Add API "deploy_keys" for admins to get all deploy keys
+  - Allow to pull code with deploy key from public projects
+  - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts)
   - Add Sidekiq queue duration to transaction metrics.
-  - Add a new column `artifacts_size` to table `ci_builds` !4964
+  - Add a new column `artifacts_size` to table `ci_builds`. !4964
   - Let Workhorse serve format-patch diffs
-  - Display tooltip for mentioned users and groups !5261 (winniehell)
+  - Display tooltip for mentioned users and groups. !5261 (winniehell)
   - Allow build email service to be tested
   - Added day name to contribution calendar tooltips
-  - Make images fit to the size of the viewport !4810
-  - Fix check for New Branch button on Issue page !4630 (winniehell)
+  - Refactor user authorization check for a single project to avoid querying all user projects
+  - Make images fit to the size of the viewport. !4810
+  - Fix check for New Branch button on Issue page. !4630 (winniehell)
+  - Fix GFM autocomplete not working on wiki pages
+  - Fixed enter key not triggering click on first row when searching in a dropdown
   - Fix MR-auto-close text added to description. !4836
   - Support U2F devices in Firefox. !5177
-  - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
+  - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
   - Add Spring EmojiOne updates.
+  - Added Rake task for tracking deployments. !5320
   - Fix fetching LFS objects for private CI projects
-  - Add syntax for multiline blockquote using `>>>` fence !3954
+  - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
+  - Add syntax for multiline blockquote using `>>>` fence. !3954
   - Fix viewing notification settings when a project is pending deletion
   - Updated compare dropdown menus to use GL dropdown
+  - Redirects back to issue after clicking login link
   - Eager load award emoji on notes
+  - Allow to define manual actions/builds on Pipelines and Environments
   - Fix pagination when sorting by columns with lots of ties (like priority)
-  - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
+  - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020
   - Updated project header design
   - Issuable collapsed assignee tooltip is now the users name
+  - Fix compare view not changing code view rendering style
   - Exclude email check from the standard health check
-  - Updated layout for Projects, Groups, Users on Admin area !4424
+  - Updated layout for Projects, Groups, Users on Admin area. !4424
   - Fix changing issue state columns in milestone view
   - Update health_check gem to version 2.1.0
   - Add notification settings dropdown for groups
@@ -52,21 +170,23 @@ v 8.10.0 (unreleased)
   - Wildcards for protected branches. !4665
   - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
   - API: Expose `due_date` for issues (Robert Schilling)
-  - API: Todos !3188 (Robert Schilling)
-  - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
+  - API: Todos. !3188 (Robert Schilling)
+  - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling)
+  - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling)
   - Add "Enabled Git access protocols" to Application Settings
   - Diffs will create button/diff form on demand no on server side
   - Reduce size of HTML used by diff comment forms
   - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard)
-  - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
+  - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt)
   - Only show New Snippet button to users that can create snippets.
   - PipelinesFinder uses git cache data
   - Track a user who created a pipeline
   - Actually render old and new sections of parallel diff next to each other
   - Throttle the update of `project.pushes_since_gc` to 1 minute.
-  - Allow expanding and collapsing files in diff view (!4990)
+  - Allow expanding and collapsing files in diff view. !4990
   - Collapse large diffs by default (!4990)
   - Fix mentioned users list on diff notes
+  - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes)
   - Fix creation of deployment on build that is retried, redeployed or rollback
   - Don't parse Rinku returned value to DocFragment when it didn't change the original html string.
   - Check for conflicts with existing Project's wiki path when creating a new project.
@@ -79,8 +199,10 @@ v 8.10.0 (unreleased)
   - ObjectRenderer retrieve renderer content using Rails.cache.read_multi
   - Better caching of git calls on ProjectsController#show.
   - Avoid to retrieve MR closes_issues as much as possible.
-  - Add API endpoint for a group issues !4520 (mahcsig)
-  - Add Bugzilla integration !4930 (iamtjg)
+  - Hide project name in project activities. !5068 (winniehell)
+  - Add API endpoint for a group issues. !4520 (mahcsig)
+  - Add Bugzilla integration. !4930 (iamtjg)
+  - Fix new snippet style bug (elliotec)
   - Instrument Rinku usage
   - Be explicit to define merge request discussion variables
   - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
@@ -91,6 +213,7 @@ v 8.10.0 (unreleased)
   - Don't render discussion notes when requesting diff tab through AJAX
   - Add basic system information like memory and disk usage to the admin panel
   - Don't garbage collect commits that have related DB records like comments
+  - Allow to setup event by channel on slack service
   - More descriptive message for git hooks and file locks
   - Aliases of award emoji should be stored as original name. !5060 (dixpac)
   - Handle custom Git hook result in GitLab UI
@@ -100,10 +223,10 @@ v 8.10.0 (unreleased)
   - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
   - Add date when user joined the team on the member page
   - Fix 404 redirect after validation fails importing a GitLab project
-  - Added setting to set new users by default as external !4545 (Dravere)
-  - Add min value for project limit field on user's form !3622 (jastkand)
+  - Added setting to set new users by default as external. !4545 (Dravere)
+  - Add min value for project limit field on user's form. !3622 (jastkand)
   - Reset project pushes_since_gc when we enqueue the git gc call
-  - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
+  - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt)
   - Collapsed diffs lines/size don't acumulate to overflow diffs.
   - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
   - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay)
@@ -111,16 +234,25 @@ v 8.10.0 (unreleased)
   - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention)
   - Redesign Builds and Pipelines pages
   - Change status color and icon for running builds
+  - Fix commenting issue in side by side diff view for unchanged lines
   - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.`
   - Project export filename now includes the project and namespace path
   - Fix last update timestamp on issues not preserved on gitlab.com and project imports
   - Fix issues importing projects from EE to CE
   - Fix creating group with space in group path
+  - Improve cron_jobs loading error messages. !5318 / !5360
+  - Prevent toggling sidebar when clipboard icon clicked
   - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
   - Limit the number of retries on error to 3 for exporting projects
   - Allow empty repositories on project import/export
   - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
   - Allow bulk (un)subscription from issues in issue index
+  - Fix MR diff encoding issues exporting GitLab projects
+  - Move builds settings out of project settings and rename Pipelines
+  - Add builds badge to Pipelines settings page
+  - Export and import avatar as part of project import/export
+  - Fix migration corrupting import data for old version upgrades
+  - Show tooltip on GitLab export link in new project page
 
 v 8.9.6
   - Fix importing of events under notes for GitLab projects. !5154
@@ -223,6 +355,7 @@ v 8.9.1
   - Remove width restriction for logo on sign-in page. !4888
   - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
   - Apply selected value as label. !4886
+  - Change Retry to Re-deploy on Deployments page
   - Fix temp file being deleted after the request while importing a GitLab project. !4894
   - Fix pagination when sorting by columns with lots of ties (like priority)
   - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 14ff05c9aa3b979d70bd201ae855464170b91f8e..a885e706810f9d9a8a41f614dcc0879a6066a2a7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,6 +41,8 @@ abbreviation.
 If you have read this guide and want to know how the GitLab [core team]
 operates please see [the GitLab contributing process](PROCESS.md).
 
+- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+
 ## Contributor license agreement
 
 By submitting code as an individual you agree to the
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 944880fa15e85084780c290b929924d3f8b6085f..e4604e3afd0dd7b7ebf8a8064658da22c365f6ef 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.2.0
+3.2.1
diff --git a/Gemfile b/Gemfile
index 8a30a819660d562dca91c071c3b1709e59f16607..5f247abd2fc99f794fba18099ef61e059be49280 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,6 +9,7 @@ gem 'responders', '~> 2.0'
 # Specify a sprockets version due to increased performance
 # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
 gem 'sprockets', '~> 3.6.0'
+gem 'sprockets-es6'
 
 # Default values for AR models
 gem 'default_value_for', '~> 3.0.0'
@@ -52,7 +53,7 @@ gem 'browser', '~> 2.2'
 
 # Extracting information from a git repository
 # Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.3.2'
+gem 'gitlab_git', '~> 10.4.2'
 
 # LDAP Auth
 # GitLab fork with several improvements to original library. For full list of changes
@@ -223,8 +224,8 @@ gem 'jquery-turbolinks', '~> 2.1.0'
 gem 'addressable',        '~> 2.3.8'
 gem 'bootstrap-sass',     '~> 3.3.0'
 gem 'font-awesome-rails', '~> 4.6.1'
-gem 'gemojione',          '~> 2.6'
-gem 'gon',                '~> 6.0.1'
+gem 'gemojione',          '~> 3.0'
+gem 'gon',                '~> 6.1.0'
 gem 'jquery-atwho-rails', '~> 1.3.2'
 gem 'jquery-rails',       '~> 4.1.0'
 gem 'jquery-ui-rails',    '~> 5.0.0'
@@ -252,7 +253,7 @@ group :development do
 
   gem 'letter_opener_web', '~> 1.3.0'
   gem 'rerun', '~> 0.11.0'
-  gem 'bullet', '~> 5.0.0', require: false
+  gem 'bullet', '~> 5.2.0', require: false
   gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
   gem 'web-console', '~> 2.0'
 
@@ -274,7 +275,7 @@ group :development, :test do
   gem 'awesome_print', '~> 1.2.0', require: false
   gem 'fuubar', '~> 2.0.0'
 
-  gem 'database_cleaner',   '~> 1.4.0'
+  gem 'database_cleaner',   '~> 1.5.0'
   gem 'factory_girl_rails', '~> 4.6.0'
   gem 'rspec-rails',        '~> 3.5.0'
   gem 'rspec-retry',        '~> 0.4.5'
@@ -302,7 +303,7 @@ group :development, :test do
   gem 'rubocop', '~> 0.41.2', require: false
   gem 'rubocop-rspec', '~> 1.5.0', require: false
   gem 'scss_lint', '~> 0.47.0', require: false
-  gem 'simplecov', '~> 0.11.0', require: false
+  gem 'simplecov', '0.12.0', require: false
   gem 'flog', '~> 4.3.2', require: false
   gem 'flay', '~> 2.6.1', require: false
   gem 'bundler-audit', '~> 0.5.0', require: false
@@ -333,6 +334,8 @@ gem 'mail_room', '~> 0.8'
 
 gem 'email_reply_parser', '~> 0.5.8'
 
+gem 'ruby-prof', '~> 0.15.9'
+
 ## CI
 gem 'activerecord-session_store', '~> 1.0.0'
 gem 'nested_form', '~> 0.3.2'
@@ -347,8 +350,5 @@ gem 'paranoia', '~> 2.0'
 gem 'health_check', '~> 2.1.0'
 
 # System information
-gem 'vmstat', '~> 2.1.0'
+gem 'vmstat', '~> 2.1.1'
 gem 'sys-filesystem', '~> 1.1.6'
-
-# Secure headers for Content Security Policy
-gem 'secure_headers', '~> 3.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index f3f4d895ae4a9b2707a4bc4dfbcfc9823e3e365d..7b4175ea824f68fa5078f4fca9272671a384d16d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -59,7 +59,7 @@ GEM
       oauth2 (~> 1.0)
     asciidoctor (1.5.3)
     ast (2.3.0)
-    attr_encrypted (3.0.1)
+    attr_encrypted (3.0.3)
       encryptor (~> 3.0.0)
     attr_required (1.0.0)
     autoprefixer-rails (6.2.3)
@@ -85,6 +85,10 @@ GEM
       faraday (~> 0.9)
       faraday_middleware (~> 0.10)
       nokogiri (~> 1.6)
+    babel-source (5.8.35)
+    babel-transpiler (0.7.0)
+      babel-source (>= 4.0, < 6)
+      execjs (~> 2.0)
     babosa (1.0.2)
     base32 (0.3.2)
     bcrypt (3.1.11)
@@ -100,9 +104,9 @@ GEM
     brakeman (3.3.2)
     browser (2.2.0)
     builder (3.2.2)
-    bullet (5.0.0)
+    bullet (5.2.0)
       activesupport (>= 3.0.0)
-      uniform_notifier (~> 1.9.0)
+      uniform_notifier (~> 1.10.0)
     bundler-audit (0.5.0)
       bundler (~> 1.2)
       thor (~> 0.18)
@@ -149,11 +153,11 @@ GEM
     d3_rails (3.5.11)
       railties (>= 3.1.0)
     daemons (1.2.3)
-    database_cleaner (1.4.1)
+    database_cleaner (1.5.3)
     debug_inspector (0.0.2)
     debugger-ruby_core_source (1.3.8)
-    default_value_for (3.0.1)
-      activerecord (>= 3.2.0, < 5.0)
+    default_value_for (3.0.2)
+      activerecord (>= 3.2.0, < 5.1)
     descendants_tracker (0.0.4)
       thread_safe (~> 0.3, >= 0.3.1)
     devise (4.1.1)
@@ -255,7 +259,7 @@ GEM
       ruby-progressbar (~> 1.4)
     gemnasium-gitlab-service (0.2.6)
       rugged (~> 0.21)
-    gemojione (2.6.1)
+    gemojione (3.0.1)
       json
     get_process_mem (0.2.0)
     gherkin-ruby (0.3.2)
@@ -274,7 +278,7 @@ GEM
       diff-lcs (~> 1.1)
       mime-types (>= 1.16, < 3)
       posix-spawn (~> 0.3)
-    gitlab_git (10.3.2)
+    gitlab_git (10.4.2)
       activesupport (~> 4.0)
       charlock_holmes (~> 0.7.3)
       github-linguist (~> 4.7.0)
@@ -299,7 +303,7 @@ GEM
     gollum-rugged_adapter (0.4.2)
       mime-types (>= 1.15)
       rugged (~> 0.24.0, >= 0.21.3)
-    gon (6.0.1)
+    gon (6.1.0)
       actionpack (>= 3.0)
       json
       multi_json
@@ -505,7 +509,7 @@ GEM
     rack-cors (0.4.0)
     rack-mount (0.8.3)
       rack (>= 1.0.0)
-    rack-oauth2 (1.2.1)
+    rack-oauth2 (1.2.3)
       activesupport (>= 2.3)
       attr_required (>= 0.0.5)
       httpclient (>= 2.4)
@@ -571,14 +575,14 @@ GEM
       redis-store (~> 1.1.0)
     redis-store (1.1.7)
       redis (>= 2.2)
-    request_store (1.3.0)
+    request_store (1.3.1)
     rerun (0.11.0)
       listen (~> 3.0)
     responders (2.1.1)
       railties (>= 4.2.0, < 5.1)
     rinku (2.0.0)
     rotp (2.1.2)
-    rouge (2.0.3)
+    rouge (2.0.5)
     rqrcode (0.7.0)
       chunky_png
     rqrcode-rails3 (0.1.7)
@@ -616,6 +620,7 @@ GEM
       rubocop (>= 0.40.0)
     ruby-fogbugz (0.2.1)
       crack (~> 0.4)
+    ruby-prof (0.15.9)
     ruby-progressbar (1.8.1)
     ruby-saml (1.3.0)
       nokogiri (>= 1.5.10)
@@ -645,8 +650,6 @@ GEM
     sdoc (0.3.20)
       json (>= 1.1.3)
       rdoc (~> 3.10)
-    secure_headers (3.3.2)
-      useragent
     seed-fu (2.3.6)
       activerecord (>= 3.1)
       activesupport (>= 3.1)
@@ -670,9 +673,9 @@ GEM
       rufus-scheduler (>= 2.0.24)
       sidekiq (>= 4.0.0)
     simple_oauth (0.1.9)
-    simplecov (0.11.2)
+    simplecov (0.12.0)
       docile (~> 1.1.0)
-      json (~> 1.8)
+      json (>= 1.8, < 3)
       simplecov-html (~> 0.10.0)
     simplecov-html (0.10.0)
     sinatra (1.4.7)
@@ -702,6 +705,10 @@ GEM
     sprockets (3.6.3)
       concurrent-ruby (~> 1.0)
       rack (> 1, < 3)
+    sprockets-es6 (0.9.0)
+      babel-source (>= 5.8.11)
+      babel-transpiler
+      sprockets (>= 3.0.0)
     sprockets-rails (3.1.1)
       actionpack (>= 4.0)
       activesupport (>= 4.0)
@@ -768,8 +775,7 @@ GEM
     unicorn-worker-killer (0.4.4)
       get_process_mem (~> 0)
       unicorn (>= 4, < 6)
-    uniform_notifier (1.9.0)
-    useragent (0.16.7)
+    uniform_notifier (1.10.0)
     uuid (2.3.8)
       macaddr (~> 1.0)
     version_sorter (2.0.0)
@@ -778,7 +784,7 @@ GEM
       coercible (~> 1.0)
       descendants_tracker (~> 0.0, >= 0.0.3)
       equalizer (~> 0.0, >= 0.0.9)
-    vmstat (2.1.0)
+    vmstat (2.1.1)
     warden (1.2.6)
       rack (>= 1.0)
     web-console (2.3.0)
@@ -824,7 +830,7 @@ DEPENDENCIES
   bootstrap-sass (~> 3.3.0)
   brakeman (~> 3.3.0)
   browser (~> 2.2)
-  bullet (~> 5.0.0)
+  bullet (~> 5.2.0)
   bundler-audit (~> 0.5.0)
   byebug (~> 8.2.1)
   capybara (~> 2.6.2)
@@ -836,7 +842,7 @@ DEPENDENCIES
   connection_pool (~> 2.0)
   creole (~> 0.5.0)
   d3_rails (~> 3.5.0)
-  database_cleaner (~> 1.4.0)
+  database_cleaner (~> 1.5.0)
   default_value_for (~> 3.0.0)
   devise (~> 4.0)
   devise-two-factor (~> 3.0.0)
@@ -860,16 +866,16 @@ DEPENDENCIES
   foreman (~> 0.78.0)
   fuubar (~> 2.0.0)
   gemnasium-gitlab-service (~> 0.2)
-  gemojione (~> 2.6)
+  gemojione (~> 3.0)
   github-linguist (~> 4.7.0)
   github-markup (~> 1.4)
   gitlab-flowdock-git-hook (~> 1.0.1)
-  gitlab_git (~> 10.3.2)
+  gitlab_git (~> 10.4.2)
   gitlab_meta (= 7.0)
   gitlab_omniauth-ldap (~> 1.2.1)
   gollum-lib (~> 4.2)
   gollum-rugged_adapter (~> 0.4.2)
-  gon (~> 6.0.1)
+  gon (~> 6.1.0)
   grape (~> 0.13.0)
   grape-entity (~> 0.4.2)
   hamlit (~> 2.5)
@@ -943,11 +949,11 @@ DEPENDENCIES
   rubocop (~> 0.41.2)
   rubocop-rspec (~> 1.5.0)
   ruby-fogbugz (~> 0.2.1)
+  ruby-prof (~> 0.15.9)
   sanitize (~> 2.0)
   sass-rails (~> 5.0.0)
   scss_lint (~> 0.47.0)
   sdoc (~> 0.3.20)
-  secure_headers (~> 3.3)
   seed-fu (~> 2.3.5)
   select2-rails (~> 3.5.9)
   sentry-raven (~> 1.1.0)
@@ -956,7 +962,7 @@ DEPENDENCIES
   shoulda-matchers (~> 2.8.0)
   sidekiq (~> 4.0)
   sidekiq-cron (~> 0.4.0)
-  simplecov (~> 0.11.0)
+  simplecov (= 0.12.0)
   sinatra (~> 1.4.4)
   six (~> 0.2.0)
   slack-notifier (~> 1.2.0)
@@ -967,6 +973,7 @@ DEPENDENCIES
   spring-commands-spinach (~> 1.1.0)
   spring-commands-teaspoon (~> 0.0.2)
   sprockets (~> 3.6.0)
+  sprockets-es6
   state_machines-activerecord (~> 0.4.0)
   sys-filesystem (~> 1.1.6)
   task_list (~> 1.0.2)
@@ -984,7 +991,7 @@ DEPENDENCIES
   unicorn-worker-killer (~> 0.4.2)
   version_sorter (~> 2.0.0)
   virtus (~> 1.0.1)
-  vmstat (~> 2.1.0)
+  vmstat (~> 2.1.1)
   web-console (~> 2.0)
   webmock (~> 1.21.0)
   wikicloth (= 0.8.1)
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index d3d36670693aa4c9b5b51a44e37542b077d2c9cd..1efb2a35f6d9f9a12c8741f2dcae0bafa5d04847 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -1,15 +1,35 @@
 # GitLab Maintenance Policy
 
-GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time.
+GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
+`(Major).(Minor).(Patch)` in a [pragmatic way].
 
-GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
+- **Major version**: Whenever there is something significant or any backwards
+  incompatible changes are introduced to the public API.
+- **Minor version**: When new, backwards compatible functionality is introduced
+  to the public API or a minor feature is introduced, or when a set of smaller
+  features is rolled out.
+- **Patch number**: When backwards compatible bug fixes are introduced that fix
+  incorrect behavior.
 
-- **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API.
-- **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out.
-- **Patch number**: When backwards compatible bug fixes are introduced that fix incorrect behavior.
+The current stable release will receive security patches and bug fixes
+(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable
+release where the minor version is increased numerically by increments of one
+(eg. `8.9 -> 8.10`).
 
-The current stable release will receive security patches and bug fixes (eg. `5.0` -> `5.0.1`).  Feature releases will mark the next supported stable release where the minor version is increased numerically by increments of one (eg. `5.0 -> 5.1`).
+Our current policy is to support one stable release at any given time, but for
+medium-level security issues, we may consider [backporting to the previous two
+monthly releases][rel-sec].
 
-We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable.
+We encourage everyone to run the latest stable release to ensure that you can
+easily upgrade to the most secure and feature-rich GitLab experience. In order
+to make sure you can easily run the most recent stable release, we are working
+hard to keep the update process simple and reliable.
 
-More information about the release procedures can be found in the doc/release directory.
+More information about the release procedures can be found in our
+[release-tools documentation][rel]. You may also want to read our
+[Responsible Disclosure Policy][disclosure].
+
+[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting
+[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/
+[disclosure]: https://about.gitlab.com/disclosure/
+[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e
diff --git a/PROCESS.md b/PROCESS.md
index fe3a963110df09793d694e8175318c2d18a2da15..8e1a3f7360f6508b35cf76ea8703dad873c72b32 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -8,6 +8,8 @@ treatment, etc.). And so that maintainers know what to expect from contributors
 (use the latest version, ensure that the issue is addressed, friendly treatment,
 etc.).
 
+- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+
 ## Common actions
 
 ### Issue team
diff --git a/VERSION b/VERSION
index 213504430f3e756aa83dc22dce99bd6d8176291f..542e7824102447bf9fe4091b802e0713848e87c4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.10.0-pre
+8.11.0-pre
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
index 6bacb0e92b6a4ec9b6b019a0c6cca0acaa33775e..6f1a34a559127963b82b87cf7bc00fa634545a78 100644
Binary files a/app/assets/images/emoji.png and b/app/assets/images/emoji.png differ
diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png
index 99588b566160a76f8a41eb160ed225b3bb609a00..dc9cae1d44cf0282dab99202430b4ae0c710d37a 100644
Binary files a/app/assets/images/emoji@2x.png and b/app/assets/images/emoji@2x.png differ
diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png
deleted file mode 100644
index c6b6c8d9521f64b00990ca5352c8ce269e9a3e4a..0000000000000000000000000000000000000000
Binary files a/app/assets/images/switch_icon.png and /dev/null differ
diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif
deleted file mode 100644
index 1a1c9c15ec71a58db869578399068cf313c51599..0000000000000000000000000000000000000000
Binary files a/app/assets/images/trans_bg.gif and /dev/null differ
diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..151455ce4a3a9f49c928593e864639e555d497cb
--- /dev/null
+++ b/app/assets/javascripts/LabelManager.js
@@ -0,0 +1,110 @@
+(function() {
+  this.LabelManager = (function() {
+    LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
+
+    function LabelManager(opts) {
+      var ref, ref1, ref2;
+      if (opts == null) {
+        opts = {};
+      }
+      this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels');
+      this.prioritizedLabels.sortable({
+        items: 'li',
+        placeholder: 'list-placeholder',
+        axis: 'y',
+        update: this.onPrioritySortUpdate.bind(this)
+      });
+      this.bindEvents();
+    }
+
+    LabelManager.prototype.bindEvents = function() {
+      return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
+    };
+
+    LabelManager.prototype.onTogglePriorityClick = function(e) {
+      var $btn, $label, $tooltip, _this, action;
+      e.preventDefault();
+      _this = e.data;
+      $btn = $(e.currentTarget);
+      $label = $("#" + ($btn.data('domId')));
+      action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
+      $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
+      $tooltip.tooltip('destroy');
+      return _this.toggleLabelPriority($label, action);
+    };
+
+    LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) {
+      var $from, $target, _this, url, xhr;
+      if (persistState == null) {
+        persistState = true;
+      }
+      _this = this;
+      url = $label.find('.js-toggle-priority').data('url');
+      $target = this.prioritizedLabels;
+      $from = this.otherLabels;
+      if (action === 'remove') {
+        $target = this.otherLabels;
+        $from = this.prioritizedLabels;
+      }
+      if ($from.find('li').length === 1) {
+        $from.find('.empty-message').removeClass('hidden');
+      }
+      if (!$target.find('li').length) {
+        $target.find('.empty-message').addClass('hidden');
+      }
+      $label.detach().appendTo($target);
+      if (!persistState) {
+        return;
+      }
+      if (action === 'remove') {
+        xhr = $.ajax({
+          url: url,
+          type: 'DELETE'
+        });
+        if (!$from.find('li').length) {
+          $from.find('.empty-message').removeClass('hidden');
+        }
+      } else {
+        xhr = this.savePrioritySort($label, action);
+      }
+      return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
+    };
+
+    LabelManager.prototype.onPrioritySortUpdate = function() {
+      var xhr;
+      xhr = this.savePrioritySort();
+      return xhr.fail(function() {
+        return new Flash(this.errorMessage, 'alert');
+      });
+    };
+
+    LabelManager.prototype.savePrioritySort = function() {
+      return $.post({
+        url: this.prioritizedLabels.data('url'),
+        data: {
+          label_ids: this.getSortedLabelsIds()
+        }
+      });
+    };
+
+    LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) {
+      var action;
+      action = originalAction === 'remove' ? 'add' : 'remove';
+      this.toggleLabelPriority($label, action, false);
+      return new Flash(this.errorMessage, 'alert');
+    };
+
+    LabelManager.prototype.getSortedLabelsIds = function() {
+      var sortedIds;
+      sortedIds = [];
+      this.prioritizedLabels.find('li').each(function() {
+        return sortedIds.push($(this).data('id'));
+      });
+      return sortedIds;
+    };
+
+    return LabelManager;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee
deleted file mode 100644
index 6d8faba40d73464a7d7931e06a2f349809245ce6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/LabelManager.js.coffee
+++ /dev/null
@@ -1,92 +0,0 @@
-class @LabelManager
-  errorMessage: 'Unable to update label prioritization at this time'
-
-  constructor: (opts = {}) ->
-    # Defaults
-    {
-      @togglePriorityButton = $('.js-toggle-priority')
-      @prioritizedLabels = $('.js-prioritized-labels')
-      @otherLabels = $('.js-other-labels')
-    } = opts
-
-    @prioritizedLabels.sortable(
-      items: 'li'
-      placeholder: 'list-placeholder'
-      axis: 'y'
-      update: @onPrioritySortUpdate.bind(@)
-    )
-
-    @bindEvents()
-
-  bindEvents: ->
-    @togglePriorityButton.on 'click', @, @onTogglePriorityClick
-
-  onTogglePriorityClick: (e) ->
-    e.preventDefault()
-    _this = e.data
-    $btn = $(e.currentTarget)
-    $label = $("##{$btn.data('domId')}")
-    action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
-
-    # Make sure tooltip will hide
-    $tooltip = $ "##{$btn.find('.has-tooltip:visible').attr('aria-describedby')}"
-    $tooltip.tooltip 'destroy'
-
-    _this.toggleLabelPriority($label, action)
-
-  toggleLabelPriority: ($label, action, persistState = true) ->
-    _this = @
-    url = $label.find('.js-toggle-priority').data 'url'
-
-    $target = @prioritizedLabels
-    $from = @otherLabels
-
-    # Optimistic update
-    if action is 'remove'
-      $target = @otherLabels
-      $from = @prioritizedLabels
-
-    if $from.find('li').length is 1
-      $from.find('.empty-message').removeClass('hidden')
-
-    if not $target.find('li').length
-      $target.find('.empty-message').addClass('hidden')
-
-    $label.detach().appendTo($target)
-
-    # Return if we are not persisting state
-    return unless persistState
-
-    if action is 'remove'
-      xhr = $.ajax url: url, type: 'DELETE'
-
-      # Restore empty message
-      $from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
-    else
-      xhr = @savePrioritySort($label, action)
-
-    xhr.fail @rollbackLabelPosition.bind(@, $label, action)
-
-  onPrioritySortUpdate: ->
-    xhr = @savePrioritySort()
-
-    xhr.fail ->
-      new Flash(@errorMessage, 'alert')
-
-  savePrioritySort: () ->
-    $.post
-      url: @prioritizedLabels.data('url')
-      data:
-        label_ids: @getSortedLabelsIds()
-
-  rollbackLabelPosition: ($label, originalAction)->
-    action = if originalAction is 'remove' then 'add' else 'remove'
-    @toggleLabelPriority($label, action, false)
-
-    new Flash(@errorMessage, 'alert')
-
-  getSortedLabelsIds: ->
-    sortedIds = []
-    @prioritizedLabels.find('li').each ->
-      sortedIds.push $(@).data 'id'
-    sortedIds
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ab3c2197d82f60cfd44757db75e8dd340018b92
--- /dev/null
+++ b/app/assets/javascripts/activities.js
@@ -0,0 +1,40 @@
+(function() {
+  this.Activities = (function() {
+    function Activities() {
+      Pager.init(20, true, false, this.updateTooltips);
+      $(".event-filter-link").on("click", (function(_this) {
+        return function(event) {
+          event.preventDefault();
+          _this.toggleFilter($(event.currentTarget));
+          return _this.reloadActivities();
+        };
+      })(this));
+    }
+
+    Activities.prototype.updateTooltips = function() {
+      return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
+    };
+
+    Activities.prototype.reloadActivities = function() {
+      $(".content_list").html('');
+      return Pager.init(20, true);
+    };
+
+    Activities.prototype.toggleFilter = function(sender) {
+      var event_filters, filter;
+      $('.event-filter .active').removeClass("active");
+      event_filters = $.cookie("event_filter");
+      filter = sender.attr("id").split("_")[0];
+      $.cookie("event_filter", (event_filters !== filter ? filter : ""), {
+        path: '/'
+      });
+      if (event_filters !== filter) {
+        return sender.closest('li').toggleClass("active");
+      }
+    };
+
+    return Activities;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
deleted file mode 100644
index ed5a5d0260ce70e06791f20424639fed0864aa49..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/activities.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-class @Activities
-  constructor: ->
-    Pager.init 20, true, false, @updateTooltips
-    $(".event-filter-link").on "click", (event) =>
-      event.preventDefault()
-      @toggleFilter($(event.currentTarget))
-      @reloadActivities()
-
-  updateTooltips: ->
-    gl.utils.localTimeAgo($('.js-timeago', '#activity'))
-
-  reloadActivities: ->
-    $(".content_list").html ''
-    Pager.init 20, true
-
-
-  toggleFilter: (sender) ->
-    $('.event-filter .active').removeClass "active"
-    event_filters = $.cookie("event_filter")
-    filter = sender.attr("id").split("_")[0]
-    $.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
-
-    if event_filters isnt filter
-      sender.closest('li').toggleClass "active"
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8460beb5d27cdba9f5f532c1e5104d81f1aa840
--- /dev/null
+++ b/app/assets/javascripts/admin.js
@@ -0,0 +1,64 @@
+(function() {
+  this.Admin = (function() {
+    function Admin() {
+      var modal, showBlacklistType;
+      $('input#user_force_random_password').on('change', function(elem) {
+        var elems;
+        elems = $('#user_password, #user_password_confirmation');
+        if ($(this).attr('checked')) {
+          return elems.val('').attr('disabled', true);
+        } else {
+          return elems.removeAttr('disabled');
+        }
+      });
+      $('body').on('click', '.js-toggle-colors-link', function(e) {
+        e.preventDefault();
+        return $('.js-toggle-colors-container').toggle();
+      });
+      $('.log-tabs a').click(function(e) {
+        e.preventDefault();
+        return $(this).tab('show');
+      });
+      $('.log-bottom').click(function(e) {
+        var visible_log;
+        e.preventDefault();
+        visible_log = $(".file-content:visible");
+        return visible_log.animate({
+          scrollTop: visible_log.find('ol').height()
+        }, "fast");
+      });
+      modal = $('.change-owner-holder');
+      $('.change-owner-link').bind("click", function(e) {
+        e.preventDefault();
+        $(this).hide();
+        return modal.show();
+      });
+      $('.change-owner-cancel-link').bind("click", function(e) {
+        e.preventDefault();
+        modal.hide();
+        return $('.change-owner-link').show();
+      });
+      $('li.project_member').bind('ajax:success', function() {
+        return Turbolinks.visit(location.href);
+      });
+      $('li.group_member').bind('ajax:success', function() {
+        return Turbolinks.visit(location.href);
+      });
+      showBlacklistType = function() {
+        if ($("input[name='blacklist_type']:checked").val() === 'file') {
+          $('.blacklist-file').show();
+          return $('.blacklist-raw').hide();
+        } else {
+          $('.blacklist-file').hide();
+          return $('.blacklist-raw').show();
+        }
+      };
+      $("input[name='blacklist_type']").click(showBlacklistType);
+      showBlacklistType();
+    }
+
+    return Admin;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
deleted file mode 100644
index b2b8e1b7ffbef93d375c3917cde6cd7f46bd2d6c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/admin.js.coffee
+++ /dev/null
@@ -1,40 +0,0 @@
-class @Admin
-  constructor: ->
-    $('input#user_force_random_password').on 'change', (elem) ->
-      elems = $('#user_password, #user_password_confirmation')
-
-      if $(@).attr 'checked'
-        elems.val('').attr 'disabled', true
-      else
-        elems.removeAttr 'disabled'
-
-    $('body').on 'click', '.js-toggle-colors-link', (e) ->
-      e.preventDefault()
-      $('.js-toggle-colors-container').toggle()
-
-    $('.log-tabs a').click (e) ->
-      e.preventDefault()
-      $(this).tab('show')
-
-    $('.log-bottom').click (e) ->
-      e.preventDefault()
-      visible_log = $(".file-content:visible")
-      visible_log.animate({ scrollTop: visible_log.find('ol').height() }, "fast")
-
-    modal = $('.change-owner-holder')
-
-    $('.change-owner-link').bind "click", (e) ->
-      e.preventDefault()
-      $(this).hide()
-      modal.show()
-
-    $('.change-owner-cancel-link').bind "click", (e) ->
-      e.preventDefault()
-      modal.hide()
-      $('.change-owner-link').show()
-
-    $('li.project_member').bind 'ajax:success', ->
-      Turbolinks.visit(location.href)
-
-    $('li.group_member').bind 'ajax:success', ->
-      Turbolinks.visit(location.href)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..49c2ac0dac3fb9fc469b52315616772a5f26e9be
--- /dev/null
+++ b/app/assets/javascripts/api.js
@@ -0,0 +1,136 @@
+(function() {
+  this.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: "/api/:version/projects/:id/labels",
+    licensePath: "/api/:version/licenses/:key",
+    gitignorePath: "/api/:version/gitignores/:key",
+    gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key",
+    group: function(group_id, callback) {
+      var url;
+      url = Api.buildUrl(Api.groupPath);
+      url = url.replace(':id', group_id);
+      return $.ajax({
+        url: url,
+        data: {
+          private_token: gon.api_token
+        },
+        dataType: "json"
+      }).done(function(group) {
+        return callback(group);
+      });
+    },
+    groups: function(query, skip_ldap, callback) {
+      var url;
+      url = Api.buildUrl(Api.groupsPath);
+      return $.ajax({
+        url: url,
+        data: {
+          private_token: gon.api_token,
+          search: query,
+          per_page: 20
+        },
+        dataType: "json"
+      }).done(function(groups) {
+        return callback(groups);
+      });
+    },
+    namespaces: function(query, callback) {
+      var url;
+      url = Api.buildUrl(Api.namespacesPath);
+      return $.ajax({
+        url: url,
+        data: {
+          private_token: gon.api_token,
+          search: query,
+          per_page: 20
+        },
+        dataType: "json"
+      }).done(function(namespaces) {
+        return callback(namespaces);
+      });
+    },
+    projects: function(query, order, callback) {
+      var url;
+      url = Api.buildUrl(Api.projectsPath);
+      return $.ajax({
+        url: url,
+        data: {
+          private_token: gon.api_token,
+          search: query,
+          order_by: order,
+          per_page: 20
+        },
+        dataType: "json"
+      }).done(function(projects) {
+        return callback(projects);
+      });
+    },
+    newLabel: function(project_id, data, callback) {
+      var url;
+      url = Api.buildUrl(Api.labelsPath);
+      url = url.replace(':id', project_id);
+      data.private_token = gon.api_token;
+      return $.ajax({
+        url: url,
+        type: "POST",
+        data: data,
+        dataType: "json"
+      }).done(function(label) {
+        return callback(label);
+      }).error(function(message) {
+        return callback(message.responseJSON);
+      });
+    },
+    groupProjects: function(group_id, query, callback) {
+      var url;
+      url = Api.buildUrl(Api.groupProjectsPath);
+      url = url.replace(':id', group_id);
+      return $.ajax({
+        url: url,
+        data: {
+          private_token: gon.api_token,
+          search: query,
+          per_page: 20
+        },
+        dataType: "json"
+      }).done(function(projects) {
+        return callback(projects);
+      });
+    },
+    licenseText: function(key, data, callback) {
+      var url;
+      url = Api.buildUrl(Api.licensePath).replace(':key', key);
+      return $.ajax({
+        url: url,
+        data: data
+      }).done(function(license) {
+        return callback(license);
+      });
+    },
+    gitignoreText: function(key, callback) {
+      var url;
+      url = Api.buildUrl(Api.gitignorePath).replace(':key', key);
+      return $.get(url, function(gitignore) {
+        return callback(gitignore);
+      });
+    },
+    gitlabCiYml: function(key, callback) {
+      var url;
+      url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key);
+      return $.get(url, function(file) {
+        return callback(file);
+      });
+    },
+    buildUrl: function(url) {
+      if (gon.relative_url_root != null) {
+        url = gon.relative_url_root + url;
+      }
+      return url.replace(':version', gon.api_version);
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
deleted file mode 100644
index 89b0ac697ed52d232b27203f3da4575c74fc03be..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/api.js.coffee
+++ /dev/null
@@ -1,122 +0,0 @@
-@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: "/api/:version/projects/:id/labels"
-  licensePath: "/api/:version/licenses/:key"
-  gitignorePath: "/api/:version/gitignores/:key"
-  gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
-
-  group: (group_id, callback) ->
-    url = Api.buildUrl(Api.groupPath)
-    url = url.replace(':id', group_id)
-
-    $.ajax(
-      url: url
-      data:
-        private_token: gon.api_token
-      dataType: "json"
-    ).done (group) ->
-      callback(group)
-
-  # Return groups list. Filtered by query
-  # Only active groups retrieved
-  groups: (query, skip_ldap, callback) ->
-    url = Api.buildUrl(Api.groupsPath)
-
-    $.ajax(
-      url: url
-      data:
-        private_token: gon.api_token
-        search: query
-        per_page: 20
-      dataType: "json"
-    ).done (groups) ->
-      callback(groups)
-
-  # Return namespaces list. Filtered by query
-  namespaces: (query, callback) ->
-    url = Api.buildUrl(Api.namespacesPath)
-
-    $.ajax(
-      url: url
-      data:
-        private_token: gon.api_token
-        search: query
-        per_page: 20
-      dataType: "json"
-    ).done (namespaces) ->
-      callback(namespaces)
-
-  # Return projects list. Filtered by query
-  projects: (query, order, callback) ->
-    url = Api.buildUrl(Api.projectsPath)
-
-    $.ajax(
-      url: url
-      data:
-        private_token: gon.api_token
-        search: query
-        order_by: order
-        per_page: 20
-      dataType: "json"
-    ).done (projects) ->
-      callback(projects)
-
-  newLabel: (project_id, data, callback) ->
-    url = Api.buildUrl(Api.labelsPath)
-    url = url.replace(':id', project_id)
-
-    data.private_token = gon.api_token
-    $.ajax(
-      url: url
-      type: "POST"
-      data: data
-      dataType: "json"
-    ).done (label) ->
-      callback(label)
-    .error (message) ->
-      callback(message.responseJSON)
-
-  # Return group projects list. Filtered by query
-  groupProjects: (group_id, query, callback) ->
-    url = Api.buildUrl(Api.groupProjectsPath)
-    url = url.replace(':id', group_id)
-
-    $.ajax(
-      url: url
-      data:
-        private_token: gon.api_token
-        search: query
-        per_page: 20
-      dataType: "json"
-    ).done (projects) ->
-      callback(projects)
-
-  # Return text for a specific license
-  licenseText: (key, data, callback) ->
-    url = Api.buildUrl(Api.licensePath).replace(':key', key)
-
-    $.ajax(
-      url: url
-      data: data
-    ).done (license) ->
-      callback(license)
-
-  gitignoreText: (key, callback) ->
-    url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
-
-    $.get url, (gitignore) ->
-      callback(gitignore)
-
-  gitlabCiYml: (key, callback) ->
-    url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
-
-    $.get url, (file) ->
-      callback(file)
-
-  buildUrl: (url) ->
-    url = gon.relative_url_root + url if gon.relative_url_root?
-    return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 0000000000000000000000000000000000000000..127e568adc97542f62e0e5c2ee387ab399a909a2
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,320 @@
+/*= require jquery2 */
+/*= require jquery-ui/autocomplete */
+/*= require jquery-ui/datepicker */
+/*= require jquery-ui/draggable */
+/*= require jquery-ui/effect-highlight */
+/*= require jquery-ui/sortable */
+/*= require jquery_ujs */
+/*= require jquery.cookie */
+/*= require jquery.endless-scroll */
+/*= require jquery.highlight */
+/*= require jquery.waitforimages */
+/*= require jquery.atwho */
+/*= require jquery.scrollTo */
+/*= require jquery.turbolinks */
+/*= require turbolinks */
+/*= require autosave */
+/*= require bootstrap/affix */
+/*= require bootstrap/alert */
+/*= require bootstrap/button */
+/*= require bootstrap/collapse */
+/*= require bootstrap/dropdown */
+/*= require bootstrap/modal */
+/*= require bootstrap/scrollspy */
+/*= require bootstrap/tab */
+/*= require bootstrap/transition */
+/*= require bootstrap/tooltip */
+/*= require bootstrap/popover */
+/*= require select2 */
+/*= require ace/ace */
+/*= require ace/ext-searchbox */
+/*= require underscore */
+/*= require dropzone */
+/*= require mousetrap */
+/*= require mousetrap/pause */
+/*= require shortcuts */
+/*= require shortcuts_navigation */
+/*= require shortcuts_dashboard_navigation */
+/*= require shortcuts_issuable */
+/*= require shortcuts_network */
+/*= require jquery.nicescroll */
+/*= require date.format */
+/*= require_directory ./behaviors */
+/*= require_directory ./blob */
+/*= require_directory ./commit */
+/*= require_directory ./extensions */
+/*= require_directory ./lib/utils */
+/*= require_directory ./u2f */
+/*= require_directory . */
+/*= require fuzzaldrin-plus */
+
+(function() {
+  window.slugify = function(text) {
+    return text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase();
+  };
+
+  window.ajaxGet = function(url) {
+    return $.ajax({
+      type: "GET",
+      url: url,
+      dataType: "script"
+    });
+  };
+
+  window.split = function(val) {
+    return val.split(/,\s*/);
+  };
+
+  window.extractLast = function(term) {
+    return split(term).pop();
+  };
+
+  window.rstrip = function(val) {
+    if (val) {
+      return val.replace(/\s+$/, '');
+    } else {
+      return val;
+    }
+  };
+
+  window.disableButtonIfEmptyField = function(field_selector, button_selector) {
+    var closest_submit, field;
+    field = $(field_selector);
+    closest_submit = field.closest('form').find(button_selector);
+    if (rstrip(field.val()) === "") {
+      closest_submit.disable();
+    }
+    return field.on('input', function() {
+      if (rstrip($(this).val()) === "") {
+        return closest_submit.disable();
+      } else {
+        return closest_submit.enable();
+      }
+    });
+  };
+
+  window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
+    var closest_submit, updateButtons;
+    closest_submit = form.find(button_selector);
+    updateButtons = function() {
+      var filled;
+      filled = true;
+      form.find('input').filter(form_selector).each(function() {
+        return filled = rstrip($(this).val()) !== "" || !$(this).attr('required');
+      });
+      if (filled) {
+        return closest_submit.enable();
+      } else {
+        return closest_submit.disable();
+      }
+    };
+    updateButtons();
+    return form.keyup(updateButtons);
+  };
+
+  window.sanitize = function(str) {
+    return str.replace(/<(?:.|\n)*?>/gm, '');
+  };
+
+  window.unbindEvents = function() {
+    return $(document).off('scroll');
+  };
+
+  window.shiftWindow = function() {
+    return scrollBy(0, -100);
+  };
+
+  document.addEventListener("page:fetch", unbindEvents);
+
+  window.addEventListener("hashchange", shiftWindow);
+
+  window.onload = function() {
+    if (location.hash) {
+      return setTimeout(shiftWindow, 100);
+    }
+  };
+
+  $(function() {
+    var $body, $document, $sidebarGutterToggle, $window, bootstrapBreakpoint, checkInitialSidebarSize, fitSidebarForSize, flash;
+    $document = $(document);
+    $window = $(window);
+    $body = $('body');
+    gl.utils.preventDisabledButtons();
+    bootstrapBreakpoint = bp.getBreakpointSize();
+    $(".nav-sidebar").niceScroll({
+      cursoropacitymax: '0.4',
+      cursorcolor: '#FFF',
+      cursorborder: "1px solid #FFF"
+    });
+    $(".js-select-on-focus").on("focusin", function() {
+      return $(this).select().one('mouseup', function(e) {
+        return e.preventDefault();
+      });
+    });
+    $('.remove-row').bind('ajax:success', function() {
+      return $(this).closest('li').fadeOut();
+    });
+    $('.js-remove-tr').bind('ajax:before', function() {
+      return $(this).hide();
+    });
+    $('.js-remove-tr').bind('ajax:success', function() {
+      return $(this).closest('tr').fadeOut();
+    });
+    $('select.select2').select2({
+      width: 'resolve',
+      dropdownAutoWidth: true
+    });
+    $('.js-select2').bind('select2-close', function() {
+      return setTimeout((function() {
+        $('.select2-container-active').removeClass('select2-container-active');
+        return $(':focus').blur();
+      }), 1);
+    });
+    $body.tooltip({
+      selector: '.has-tooltip, [data-toggle="tooltip"]',
+      placement: function(_, el) {
+        var $el;
+        $el = $(el);
+        return $el.data('placement') || 'bottom';
+      }
+    });
+    $('.trigger-submit').on('change', function() {
+      return $(this).parents('form').submit();
+    });
+    gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+    if ((flash = $(".flash-container")).length > 0) {
+      flash.click(function() {
+        return $(this).fadeOut();
+      });
+      flash.show();
+    }
+    $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
+      var buttons;
+      buttons = $('[type="submit"]', this);
+      switch (e.type) {
+        case 'ajax:beforeSend':
+        case 'submit':
+          return buttons.disable();
+        default:
+          return buttons.enable();
+      }
+    });
+    $(document).ajaxError(function(e, xhrObj, xhrSetting, xhrErrorText) {
+      var ref;
+      if (xhrObj.status === 401) {
+        return new Flash('You need to be logged in.', 'alert');
+      } else if ((ref = xhrObj.status) === 404 || ref === 500) {
+        return new Flash('Something went wrong on our end.', 'alert');
+      }
+    });
+    $('.account-box').hover(function() {
+      return $(this).toggleClass('hover');
+    });
+    $document.on('click', '.diff-content .js-show-suppressed-diff', function() {
+      var $container;
+      $container = $(this).parent();
+      $container.next('table').show();
+      return $container.remove();
+    });
+    $('.navbar-toggle').on('click', function() {
+      $('.header-content .title').toggle();
+      $('.header-content .header-logo').toggle();
+      $('.header-content .navbar-collapse').toggle();
+      return $('.navbar-toggle').toggleClass('active');
+    });
+    $body.on("click", ".js-toggle-diff-comments", function(e) {
+      $(this).toggleClass('active');
+      $(this).closest(".diff-file").find(".notes_holder").toggle();
+      return e.preventDefault();
+    });
+    $document.off("click", '.js-confirm-danger');
+    $document.on("click", '.js-confirm-danger', function(e) {
+      var btn, form, text;
+      e.preventDefault();
+      btn = $(e.target);
+      text = btn.data("confirm-danger-message");
+      form = btn.closest("form");
+      return new ConfirmDangerModal(form, text);
+    });
+    $document.on('click', 'button', function() {
+      return $(this).blur();
+    });
+    $('input[type="search"]').each(function() {
+      var $this;
+      $this = $(this);
+      $this.attr('value', $this.val());
+    });
+    $document.off('keyup', 'input[type="search"]').on('keyup', 'input[type="search"]', function(e) {
+      var $this;
+      $this = $(this);
+      return $this.attr('value', $this.val());
+    });
+    $sidebarGutterToggle = $('.js-sidebar-toggle');
+    $document.off('breakpoint:change').on('breakpoint:change', function(e, breakpoint) {
+      var $gutterIcon;
+      if (breakpoint === 'sm' || breakpoint === 'xs') {
+        $gutterIcon = $sidebarGutterToggle.find('i');
+        if ($gutterIcon.hasClass('fa-angle-double-right')) {
+          return $sidebarGutterToggle.trigger('click');
+        }
+      }
+    });
+    fitSidebarForSize = function() {
+      var oldBootstrapBreakpoint;
+      oldBootstrapBreakpoint = bootstrapBreakpoint;
+      bootstrapBreakpoint = bp.getBreakpointSize();
+      if (bootstrapBreakpoint !== oldBootstrapBreakpoint) {
+        return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+      }
+    };
+    checkInitialSidebarSize = function() {
+      bootstrapBreakpoint = bp.getBreakpointSize();
+      if (bootstrapBreakpoint === "xs" || "sm") {
+        return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
+      }
+    };
+    $window.off("resize.app").on("resize.app", function(e) {
+      return fitSidebarForSize();
+    });
+    gl.awardsHandler = new AwardsHandler();
+    checkInitialSidebarSize();
+    new Aside();
+    if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
+      $.cookie('pin_nav', 'false', {
+        path: '/',
+        expires: 365 * 10
+      });
+      $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
+      $('.navbar-fixed-top').removeClass('header-pinned-nav');
+    }
+    return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
+      var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
+      e.preventDefault();
+      $pinBtn = $(e.currentTarget);
+      $page = $('.page-with-sidebar');
+      $topNav = $('.navbar-fixed-top');
+      $tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
+      doPinNav = !$page.is('.page-sidebar-pinned');
+      tooltipText = 'Pin navigation';
+      $(this).toggleClass('is-active');
+      if (doPinNav) {
+        $page.addClass('page-sidebar-pinned');
+        $topNav.addClass('header-pinned-nav');
+      } else {
+        $tooltip.remove();
+        $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
+        $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
+      }
+      $.cookie('pin_nav', doPinNav, {
+        path: '/',
+        expires: 365 * 10
+      });
+      if ($.cookie('pin_nav') === 'true' || doPinNav) {
+        tooltipText = 'Unpin navigation';
+      }
+      $tooltip.find('.tooltip-inner').text(tooltipText);
+      return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
deleted file mode 100644
index eceff6d91d5e8bcc4c50d400dcf781763fc11497..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/application.js.coffee
+++ /dev/null
@@ -1,310 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require jquery2
-#= require jquery-ui/autocomplete
-#= require jquery-ui/datepicker
-#= require jquery-ui/draggable
-#= require jquery-ui/effect-highlight
-#= require jquery-ui/sortable
-#= require jquery_ujs
-#= require jquery.cookie
-#= require jquery.endless-scroll
-#= require jquery.highlight
-#= require jquery.waitforimages
-#= require jquery.atwho
-#= require jquery.scrollTo
-#= require jquery.turbolinks
-#= require turbolinks
-#= require autosave
-#= require bootstrap/affix
-#= require bootstrap/alert
-#= require bootstrap/button
-#= require bootstrap/collapse
-#= require bootstrap/dropdown
-#= require bootstrap/modal
-#= require bootstrap/scrollspy
-#= require bootstrap/tab
-#= require bootstrap/transition
-#= require bootstrap/tooltip
-#= require bootstrap/popover
-#= require select2
-#= require ace/ace
-#= require ace/ext-searchbox
-#= require underscore
-#= require dropzone
-#= require mousetrap
-#= require mousetrap/pause
-#= require shortcuts
-#= require shortcuts_navigation
-#= require shortcuts_dashboard_navigation
-#= require shortcuts_issuable
-#= require shortcuts_network
-#= require jquery.nicescroll
-#= require date.format
-#= require_directory ./behaviors
-#= require_directory ./blob
-#= require_directory ./commit
-#= require_directory ./extensions
-#= require_directory ./lib/utils
-#= require_directory ./u2f
-#= require_directory .
-#= require fuzzaldrin-plus
-
-window.slugify = (text) ->
-  text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
-
-window.ajaxGet = (url) ->
-  $.ajax({type: "GET", url: url, dataType: "script"})
-
-window.split = (val) ->
-  return val.split( /,\s*/ )
-
-window.extractLast = (term) ->
-  return split( term ).pop()
-
-window.rstrip = (val) ->
-  return if val then val.replace(/\s+$/, '') else val
-
-# Disable button if text field is empty
-window.disableButtonIfEmptyField = (field_selector, button_selector) ->
-  field = $(field_selector)
-  closest_submit = field.closest('form').find(button_selector)
-
-  closest_submit.disable() if rstrip(field.val()) is ""
-
-  field.on 'input', ->
-    if rstrip($(@).val()) is ""
-      closest_submit.disable()
-    else
-      closest_submit.enable()
-
-# Disable button if any input field with given selector is empty
-window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
-  closest_submit = form.find(button_selector)
-  updateButtons = ->
-    filled = true
-    form.find('input').filter(form_selector).each ->
-      filled = rstrip($(this).val()) != "" || !$(this).attr('required')
-
-    if filled
-      closest_submit.enable()
-    else
-      closest_submit.disable()
-
-  updateButtons()
-  form.keyup(updateButtons)
-
-window.sanitize = (str) ->
-  return str.replace(/<(?:.|\n)*?>/gm, '')
-
-window.unbindEvents = ->
-  $(document).off('scroll')
-
-window.shiftWindow = ->
-  scrollBy 0, -100
-
-document.addEventListener("page:fetch", unbindEvents)
-
-window.addEventListener "hashchange", shiftWindow
-
-window.onload = ->
-  # Scroll the window to avoid the topnav bar
-  # https://github.com/twitter/bootstrap/issues/1768
-  if location.hash
-    setTimeout shiftWindow, 100
-
-$ ->
-
-  $document = $(document)
-  $window   = $(window)
-  $body     = $('body')
-
-  gl.utils.preventDisabledButtons()
-  bootstrapBreakpoint = bp.getBreakpointSize()
-
-  $(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
-
-  # Click a .js-select-on-focus field, select the contents
-  $(".js-select-on-focus").on "focusin", ->
-    # Prevent a mouseup event from deselecting the input
-    $(this).select().one 'mouseup', (e) ->
-      e.preventDefault()
-
-  $('.remove-row').bind 'ajax:success', ->
-    $(this).closest('li').fadeOut()
-
-  $('.js-remove-tr').bind 'ajax:before', ->
-    $(this).hide()
-
-  $('.js-remove-tr').bind 'ajax:success', ->
-    $(this).closest('tr').fadeOut()
-
-  # Initialize select2 selects
-  $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
-
-  # Close select2 on escape
-  $('.js-select2').bind 'select2-close', ->
-    setTimeout ( ->
-      $('.select2-container-active').removeClass('select2-container-active')
-      $(':focus').blur()
-    ), 1
-
-  # Initialize tooltips
-  $body.tooltip(
-    selector: '.has-tooltip, [data-toggle="tooltip"]'
-    placement: (_, el) ->
-      $el = $(el)
-      $el.data('placement') || 'bottom'
-  )
-
-  # Form submitter
-  $('.trigger-submit').on 'change', ->
-    $(@).parents('form').submit()
-
-  gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
-
-  # Flash
-  if (flash = $(".flash-container")).length > 0
-    flash.click -> $(@).fadeOut()
-    flash.show()
-
-  # Disable form buttons while a form is submitting
-  $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
-    buttons = $('[type="submit"]', @)
-
-    switch e.type
-      when 'ajax:beforeSend', 'submit'
-        buttons.disable()
-      else
-        buttons.enable()
-
-  $(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) ->
-
-    if xhrObj.status is 401
-      new Flash 'You need to be logged in.', 'alert'
-
-    else if xhrObj.status in [ 404, 500 ]
-      new Flash 'Something went wrong on our end.', 'alert'
-
-
-  # Show/Hide the profile menu when hovering the account box
-  $('.account-box').hover -> $(@).toggleClass('hover')
-
-  # Commit show suppressed diff
-  $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
-    $container = $(@).parent()
-    $container.next('table').show()
-    $container.remove()
-
-  $('.navbar-toggle').on 'click', ->
-    $('.header-content .title').toggle()
-    $('.header-content .header-logo').toggle()
-    $('.header-content .navbar-collapse').toggle()
-    $('.navbar-toggle').toggleClass('active')
-
-  # Show/hide comments on diff
-  $body.on "click", ".js-toggle-diff-comments", (e) ->
-    $(@).toggleClass('active')
-    $(@).closest(".diff-file").find(".notes_holder").toggle()
-    e.preventDefault()
-
-  $document.off "click", '.js-confirm-danger'
-  $document.on "click", '.js-confirm-danger', (e) ->
-    e.preventDefault()
-    btn = $(e.target)
-    text = btn.data("confirm-danger-message")
-    form = btn.closest("form")
-    new ConfirmDangerModal(form, text)
-
-
-  $document.on 'click', 'button', ->
-    $(this).blur()
-
-  $('input[type="search"]').each ->
-    $this = $(this)
-    $this.attr 'value', $this.val()
-    return
-
-  $document
-    .off 'keyup', 'input[type="search"]'
-    .on 'keyup', 'input[type="search"]' , (e) ->
-      $this = $(this)
-      $this.attr 'value', $this.val()
-
-  $sidebarGutterToggle = $('.js-sidebar-toggle')
-
-  $document
-    .off 'breakpoint:change'
-    .on 'breakpoint:change', (e, breakpoint) ->
-      if breakpoint is 'sm' or breakpoint is 'xs'
-        $gutterIcon = $sidebarGutterToggle.find('i')
-        if $gutterIcon.hasClass('fa-angle-double-right')
-          $sidebarGutterToggle.trigger('click')
-
-  fitSidebarForSize = ->
-    oldBootstrapBreakpoint = bootstrapBreakpoint
-    bootstrapBreakpoint = bp.getBreakpointSize()
-    if bootstrapBreakpoint != oldBootstrapBreakpoint
-      $document.trigger('breakpoint:change', [bootstrapBreakpoint])
-
-  checkInitialSidebarSize = ->
-    bootstrapBreakpoint = bp.getBreakpointSize()
-    if bootstrapBreakpoint is "xs" or "sm"
-      $document.trigger('breakpoint:change', [bootstrapBreakpoint])
-
-  $window
-    .off "resize.app"
-    .on "resize.app", (e) ->
-      fitSidebarForSize()
-
-  gl.awardsHandler = new AwardsHandler()
-  checkInitialSidebarSize()
-  new Aside()
-
-  # Sidenav pinning
-  if $window.width() < 1024 and $.cookie('pin_nav') is 'true'
-    $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 })
-    $('.page-with-sidebar')
-      .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
-      .removeClass('page-sidebar-pinned')
-    $('.navbar-fixed-top').removeClass('header-pinned-nav')
-
-  $document
-    .off 'click', '.js-nav-pin'
-    .on 'click', '.js-nav-pin', (e) ->
-      e.preventDefault()
-
-      $pinBtn = $(e.currentTarget)
-      $page = $ '.page-with-sidebar'
-      $topNav = $ '.navbar-fixed-top'
-      $tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
-      doPinNav = not $page.is('.page-sidebar-pinned')
-      tooltipText = 'Pin navigation'
-
-      $(this).toggleClass 'is-active'
-
-      if doPinNav
-        $page.addClass('page-sidebar-pinned')
-        $topNav.addClass('header-pinned-nav')
-      else
-        $tooltip.remove() # Remove it immediately when collapsing the sidebar
-        $page.removeClass('page-sidebar-pinned')
-             .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
-        $topNav.removeClass('header-pinned-nav')
-               .toggleClass('header-collapsed header-expanded')
-
-      # Save settings
-      $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 }
-
-      if $.cookie('pin_nav') is 'true' or doPinNav
-        tooltipText = 'Unpin navigation'
-
-      # Update tooltip text immediately
-      $tooltip.find('.tooltip-inner').text(tooltipText)
-
-      # Persist tooltip title
-      $pinBtn.attr('title', tooltipText).tooltip('fixTitle')
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b546e79ee06235fa005de4ccd8ffeb6ffb0bbbf
--- /dev/null
+++ b/app/assets/javascripts/aside.js
@@ -0,0 +1,26 @@
+(function() {
+  this.Aside = (function() {
+    function Aside() {
+      $(document).off("click", "a.show-aside");
+      $(document).on("click", 'a.show-aside', function(e) {
+        var btn, icon;
+        e.preventDefault();
+        btn = $(e.currentTarget);
+        icon = btn.find('i');
+        if (icon.hasClass('fa-angle-left')) {
+          btn.parent().find('section').hide();
+          btn.parent().find('aside').fadeIn();
+          return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
+        } else {
+          btn.parent().find('aside').hide();
+          btn.parent().find('section').fadeIn();
+          return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
+        }
+      });
+    }
+
+    return Aside;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee
deleted file mode 100644
index 66ab505432672d66818ee09899c8ebc51f6f90ed..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/aside.js.coffee
+++ /dev/null
@@ -1,16 +0,0 @@
-class @Aside
-  constructor: ->
-    $(document).off "click", "a.show-aside"
-    $(document).on "click", 'a.show-aside', (e) ->
-      e.preventDefault()
-      btn = $(e.currentTarget)
-      icon = btn.find('i')
-
-      if icon.hasClass('fa-angle-left')
-        btn.parent().find('section').hide()
-        btn.parent().find('aside').fadeIn()
-        icon.removeClass('fa-angle-left').addClass('fa-angle-right')
-      else
-        btn.parent().find('aside').hide()
-        btn.parent().find('section').fadeIn()
-        icon.removeClass('fa-angle-right').addClass('fa-angle-left')
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
new file mode 100644
index 0000000000000000000000000000000000000000..7116512d6b7be2d5ba3e6062f8d6cb589572f20e
--- /dev/null
+++ b/app/assets/javascripts/autosave.js
@@ -0,0 +1,63 @@
+(function() {
+  this.Autosave = (function() {
+    function Autosave(field, key) {
+      this.field = field;
+      if (key.join != null) {
+        key = key.join("/");
+      }
+      this.key = "autosave/" + key;
+      this.field.data("autosave", this);
+      this.restore();
+      this.field.on("input", (function(_this) {
+        return function() {
+          return _this.save();
+        };
+      })(this));
+    }
+
+    Autosave.prototype.restore = function() {
+      var e, error, text;
+      if (window.localStorage == null) {
+        return;
+      }
+      try {
+        text = window.localStorage.getItem(this.key);
+      } catch (error) {
+        e = error;
+        return;
+      }
+      if ((text != null ? text.length : void 0) > 0) {
+        this.field.val(text);
+      }
+      return this.field.trigger("input");
+    };
+
+    Autosave.prototype.save = function() {
+      var text;
+      if (window.localStorage == null) {
+        return;
+      }
+      text = this.field.val();
+      if ((text != null ? text.length : void 0) > 0) {
+        try {
+          return window.localStorage.setItem(this.key, text);
+        } catch (undefined) {}
+      } else {
+        return this.reset();
+      }
+    };
+
+    Autosave.prototype.reset = function() {
+      if (window.localStorage == null) {
+        return;
+      }
+      try {
+        return window.localStorage.removeItem(this.key);
+      } catch (undefined) {}
+    };
+
+    return Autosave;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee
deleted file mode 100644
index 28f8e103664110f8ba3bf8c4b6b9a663cb06c734..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/autosave.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-class @Autosave
-  constructor: (field, key) ->
-    @field = field
-
-    key = key.join("/") if key.join?
-    @key = "autosave/#{key}"
-
-    @field.data "autosave", this
-
-    @restore()
-
-    @field.on "input", => @save()
-
-  restore: ->
-    return unless window.localStorage?
-
-    try
-      text = window.localStorage.getItem @key
-    catch e
-      return
-
-    @field.val text if text?.length > 0
-    @field.trigger "input"
-
-  save: ->
-    return unless window.localStorage?
-
-    text = @field.val()
-    if text?.length > 0
-      try
-        window.localStorage.setItem @key, text
-    else
-      @reset()
-
-  reset: ->
-    return unless window.localStorage?
-
-    try
-      window.localStorage.removeItem @key
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
deleted file mode 100644
index 37d0adaa625d29bb7f630da362dce6dd8eaa7377..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/awards_handler.coffee
+++ /dev/null
@@ -1,372 +0,0 @@
-class @AwardsHandler
-
-  constructor: ->
-
-    @aliases = gl.emojiAliases()
-
-    $(document)
-      .off 'click', '.js-add-award'
-      .on  'click', '.js-add-award', (e) =>
-        e.stopPropagation()
-        e.preventDefault()
-
-        @showEmojiMenu $(e.currentTarget)
-
-    $('html').on 'click', (e) ->
-      $target = $ e.target
-
-      unless $target.closest('.emoji-menu-content').length
-        $('.js-awards-block.current').removeClass 'current'
-
-      unless $target.closest('.emoji-menu').length
-        if $('.emoji-menu').is(':visible')
-          $('.js-add-award.is-active').removeClass 'is-active'
-          $('.emoji-menu').removeClass 'is-visible'
-
-    $(document)
-      .off 'click', '.js-emoji-btn'
-      .on  'click', '.js-emoji-btn', (e) =>
-        e.preventDefault()
-
-        $target = $ e.currentTarget
-        emoji   = $target.find('.icon').data 'emoji'
-
-        $target.closest('.js-awards-block').addClass 'current'
-        @addAward @getVotesBlock(), @getAwardUrl(), emoji
-
-
-  showEmojiMenu: ($addBtn) ->
-
-    $menu = $ '.emoji-menu'
-
-    if $addBtn.hasClass 'js-note-emoji'
-      $addBtn.closest('.note').find('.js-awards-block').addClass 'current'
-    else
-      $addBtn.closest('.js-awards-block').addClass 'current'
-
-    if $menu.length
-      $holder = $addBtn.closest('.js-award-holder')
-
-      if $menu.is '.is-visible'
-        $addBtn.removeClass 'is-active'
-        $menu.removeClass 'is-visible'
-        $('#emoji_search').blur()
-      else
-        $addBtn.addClass 'is-active'
-        @positionMenu($menu, $addBtn)
-
-        $menu.addClass 'is-visible'
-        $('#emoji_search').focus()
-    else
-      $addBtn.addClass 'is-loading is-active'
-      url = @getAwardMenuUrl()
-
-      @createEmojiMenu url, =>
-        $addBtn.removeClass 'is-loading'
-        $menu = $('.emoji-menu')
-        @positionMenu($menu, $addBtn)
-        @renderFrequentlyUsedBlock() unless @frequentEmojiBlockRendered
-
-        setTimeout =>
-          $menu.addClass 'is-visible'
-          $('#emoji_search').focus()
-          @setupSearch()
-        , 200
-
-
-  createEmojiMenu: (awardMenuUrl, callback) ->
-
-    $.get awardMenuUrl, (response) ->
-      $('body').append response
-      callback()
-
-
-  positionMenu: ($menu, $addBtn) ->
-
-    position = $addBtn.data('position')
-
-    # The menu could potentially be off-screen or in a hidden overflow element
-    # So we position the element absolute in the body
-    css =
-      top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px"
-
-    if position? and position is 'right'
-      css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px"
-      $menu.addClass 'is-aligned-right'
-    else
-      css.left = "#{$addBtn.offset().left}px"
-      $menu.removeClass 'is-aligned-right'
-
-    $menu.css(css)
-
-
-  addAward: (votesBlock, awardUrl, emoji, checkMutuality = true, callback) ->
-
-    emoji = @normilizeEmojiName emoji
-
-    @postEmoji awardUrl, emoji, =>
-      @addAwardToEmojiBar votesBlock, emoji, checkMutuality
-      callback?()
-
-    $('.emoji-menu').removeClass 'is-visible'
-
-
-  addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = true) ->
-
-    @checkMutuality votesBlock, emoji  if checkForMutuality
-    @addEmojiToFrequentlyUsedList emoji
-
-    emoji        = @normilizeEmojiName emoji
-    $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
-
-    if $emojiButton.length > 0
-      if @isActive $emojiButton
-        @decrementCounter $emojiButton, emoji
-      else
-        counter = $emojiButton.find '.js-counter'
-        counter.text parseInt(counter.text()) + 1
-        $emojiButton.addClass 'active'
-        @addMeToUserList votesBlock, emoji
-        @animateEmoji $emojiButton
-    else
-      votesBlock.removeClass 'hidden'
-      @createEmoji votesBlock, emoji
-
-
-  getVotesBlock: ->
-
-    currentBlock = $ '.js-awards-block.current'
-    return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
-
-
-  getAwardUrl: -> return @getVotesBlock().data 'award-url'
-
-
-  checkMutuality: (votesBlock, emoji) ->
-
-    awardUrl = @getAwardUrl()
-
-    if emoji in [ 'thumbsup', 'thumbsdown' ]
-      mutualVote     = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
-      $emojiButton   = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
-      isAlreadyVoted = $emojiButton.hasClass 'active'
-
-      if isAlreadyVoted
-        @showEmojiLoader $emojiButton
-        @addAward votesBlock, awardUrl, mutualVote, false, ->
-          $emojiButton.removeClass 'is-loading'
-
-
-  showEmojiLoader: ($emojiButton) ->
-
-    $loader = $emojiButton.find '.fa-spinner'
-
-    unless $loader.length
-      $emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'
-
-    $emojiButton.addClass 'is-loading'
-
-
-  isActive: ($emojiButton) -> $emojiButton.hasClass 'active'
-
-
-  decrementCounter: ($emojiButton, emoji) ->
-
-    counter       = $ '.js-counter', $emojiButton
-    counterNumber = parseInt counter.text(), 10
-
-    if counterNumber > 1
-      counter.text counterNumber - 1
-      @removeMeFromUserList $emojiButton, emoji
-    else if emoji is 'thumbsup' or emoji is 'thumbsdown'
-      $emojiButton.tooltip 'destroy'
-      counter.text '0'
-      @removeMeFromUserList $emojiButton, emoji
-      @removeEmoji $emojiButton if $emojiButton.parents('.note').length
-    else
-      @removeEmoji $emojiButton
-
-    $emojiButton.removeClass 'active'
-
-
-  removeEmoji: ($emojiButton) ->
-
-    $emojiButton.tooltip('destroy')
-    $emojiButton.remove()
-
-    $votesBlock = @getVotesBlock()
-
-    if $votesBlock.find('.js-emoji-btn').length is 0
-      $votesBlock.addClass 'hidden'
-
-
-  getAwardTooltip: ($awardBlock) ->
-
-    return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
-
-
-  removeMeFromUserList: ($emojiButton, emoji) ->
-
-    awardBlock    = $emojiButton
-    originalTitle = @getAwardTooltip awardBlock
-
-    authors = originalTitle.split ', '
-    authors.splice authors.indexOf('me'), 1
-
-    newAuthors = authors.join ', '
-
-    awardBlock
-      .closest '.js-emoji-btn'
-      .removeData 'original-title'
-      .attr 'data-original-title', newAuthors
-
-    @resetTooltip awardBlock
-
-
-  addMeToUserList: (votesBlock, emoji) ->
-
-    awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
-    origTitle  = @getAwardTooltip awardBlock
-    users      = []
-
-    if origTitle
-      users = origTitle.trim().split ', '
-
-    users.push 'me'
-    awardBlock.attr 'title', users.join ', '
-
-    @resetTooltip awardBlock
-
-
-  resetTooltip: (award) ->
-
-    award.tooltip 'destroy'
-
-    # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
-    cb = -> award.tooltip()
-    setTimeout cb, 200
-
-
-  createEmoji_: (votesBlock, emoji) ->
-
-    emojiCssClass = @resolveNameToCssClass emoji
-    buttonHtml    = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
-      <div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
-      <span class='award-control-text js-counter'>1</span>
-    </button>"
-
-    $emojiButton = $ buttonHtml
-    $emojiButton
-      .insertBefore votesBlock.find '.js-award-holder'
-      .find '.emoji-icon'
-      .data 'emoji', emoji
-
-    @animateEmoji $emojiButton
-    $('.award-control').tooltip()
-    votesBlock.removeClass 'current'
-
-
-  animateEmoji: ($emoji) ->
-
-    className = 'pulse animated'
-
-    $emoji.addClass className
-    setTimeout (-> $emoji.removeClass className), 321
-
-
-  createEmoji: (votesBlock, emoji) ->
-
-    if $('.emoji-menu').length
-      return @createEmoji_ votesBlock, emoji
-
-    @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
-
-
-  getAwardMenuUrl: -> return gon.award_menu_url
-
-
-  resolveNameToCssClass: (emoji) ->
-
-    emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
-
-    if emojiIcon.length > 0
-      unicodeName = emojiIcon.data 'unicode-name'
-    else
-      # Find by alias
-      unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
-
-    return "emoji-#{unicodeName}"
-
-
-  postEmoji: (awardUrl, emoji, callback) ->
-
-    $.post awardUrl, { name: emoji }, (data) ->
-      callback() if data.ok
-
-
-  findEmojiIcon: (votesBlock, emoji) ->
-
-    return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
-
-
-  scrollToAwards: ->
-
-    options = scrollTop: $('.awards').offset().top - 110
-    $('body, html').animate options, 200
-
-
-  normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
-
-
-  addEmojiToFrequentlyUsedList: (emoji) ->
-
-    frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
-    frequentlyUsedEmojis.push emoji
-    $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
-
-
-  getFrequentlyUsedEmojis: ->
-
-    frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
-    return _.compact _.uniq frequentlyUsedEmojis
-
-
-  renderFrequentlyUsedBlock: ->
-
-    if $.cookie 'frequently_used_emojis'
-      frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
-
-      ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>")
-
-      for emoji in frequentlyUsedEmojis
-        $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
-
-      $('.emoji-menu-content')
-        .prepend(ul)
-        .prepend($('<h5>').text('Frequently used'))
-
-    @frequentEmojiBlockRendered = true
-
-
-  setupSearch: ->
-
-    $('input.emoji-search').on 'keyup', (ev) =>
-      term = $(ev.target).val()
-
-      # Clean previous search results
-      $('ul.emoji-menu-search, h5.emoji-search').remove()
-
-      if term
-        # Generate a search result block
-        h5 = $('<h5>').text('Search results')
-        found_emojis = @searchEmojis(term).show()
-        ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis)
-        $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
-        $('.emoji-menu-content').append(h5).append(ul)
-      else
-        $('.emoji-menu-content').children().show()
-
-
-  searchEmojis: (term) ->
-
-    $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='#{term}']").closest('li').clone()
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea683b31f75a2ce1ef066984c01d70239af0fd74
--- /dev/null
+++ b/app/assets/javascripts/awards_handler.js
@@ -0,0 +1,380 @@
+(function() {
+  this.AwardsHandler = (function() {
+    function AwardsHandler() {
+      this.aliases = gl.emojiAliases();
+      $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
+        return function(e) {
+          e.stopPropagation();
+          e.preventDefault();
+          return _this.showEmojiMenu($(e.currentTarget));
+        };
+      })(this));
+      $('html').on('click', function(e) {
+        var $target;
+        $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');
+            return $('.emoji-menu').removeClass('is-visible');
+          }
+        }
+      });
+      $(document).off('click', '.js-emoji-btn').on('click', '.js-emoji-btn', (function(_this) {
+        return function(e) {
+          var $target, emoji;
+          e.preventDefault();
+          $target = $(e.currentTarget);
+          emoji = $target.find('.icon').data('emoji');
+          $target.closest('.js-awards-block').addClass('current');
+          return _this.addAward(_this.getVotesBlock(), _this.getAwardUrl(), emoji);
+        };
+      })(this));
+    }
+
+    AwardsHandler.prototype.showEmojiMenu = function($addBtn) {
+      var $holder, $menu, url;
+      $menu = $('.emoji-menu');
+      if ($addBtn.hasClass('js-note-emoji')) {
+        $addBtn.closest('.note').find('.js-awards-block').addClass('current');
+      } else {
+        $addBtn.closest('.js-awards-block').addClass('current');
+      }
+      if ($menu.length) {
+        $holder = $addBtn.closest('.js-award-holder');
+        if ($menu.is('.is-visible')) {
+          $addBtn.removeClass('is-active');
+          $menu.removeClass('is-visible');
+          return $('#emoji_search').blur();
+        } else {
+          $addBtn.addClass('is-active');
+          this.positionMenu($menu, $addBtn);
+          $menu.addClass('is-visible');
+          return $('#emoji_search').focus();
+        }
+      } else {
+        $addBtn.addClass('is-loading is-active');
+        url = this.getAwardMenuUrl();
+        return this.createEmojiMenu(url, (function(_this) {
+          return function() {
+            $addBtn.removeClass('is-loading');
+            $menu = $('.emoji-menu');
+            _this.positionMenu($menu, $addBtn);
+            if (!_this.frequentEmojiBlockRendered) {
+              _this.renderFrequentlyUsedBlock();
+            }
+            return setTimeout(function() {
+              $menu.addClass('is-visible');
+              $('#emoji_search').focus();
+              return _this.setupSearch();
+            }, 200);
+          };
+        })(this));
+      }
+    };
+
+    AwardsHandler.prototype.createEmojiMenu = function(awardMenuUrl, callback) {
+      return $.get(awardMenuUrl, function(response) {
+        $('body').append(response);
+        return callback();
+      });
+    };
+
+    AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
+      var css, position;
+      position = $addBtn.data('position');
+      css = {
+        top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
+      };
+      if ((position != null) && 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(votesBlock, awardUrl, emoji, checkMutuality, callback) {
+      if (checkMutuality == null) {
+        checkMutuality = true;
+      }
+      emoji = this.normilizeEmojiName(emoji);
+      this.postEmoji(awardUrl, emoji, (function(_this) {
+        return function() {
+          _this.addAwardToEmojiBar(votesBlock, emoji, checkMutuality);
+          return typeof callback === "function" ? callback() : void 0;
+        };
+      })(this));
+      return $('.emoji-menu').removeClass('is-visible');
+    };
+
+    AwardsHandler.prototype.addAwardToEmojiBar = function(votesBlock, emoji, checkForMutuality) {
+      var $emojiButton, counter;
+      if (checkForMutuality == null) {
+        checkForMutuality = true;
+      }
+      if (checkForMutuality) {
+        this.checkMutuality(votesBlock, emoji);
+      }
+      this.addEmojiToFrequentlyUsedList(emoji);
+      emoji = this.normilizeEmojiName(emoji);
+      $emojiButton = this.findEmojiIcon(votesBlock, emoji).parent();
+      if ($emojiButton.length > 0) {
+        if (this.isActive($emojiButton)) {
+          return this.decrementCounter($emojiButton, emoji);
+        } else {
+          counter = $emojiButton.find('.js-counter');
+          counter.text(parseInt(counter.text()) + 1);
+          $emojiButton.addClass('active');
+          this.addMeToUserList(votesBlock, emoji);
+          return this.animateEmoji($emojiButton);
+        }
+      } else {
+        votesBlock.removeClass('hidden');
+        return this.createEmoji(votesBlock, emoji);
+      }
+    };
+
+    AwardsHandler.prototype.getVotesBlock = function() {
+      var currentBlock;
+      currentBlock = $('.js-awards-block.current');
+      if (currentBlock.length) {
+        return currentBlock;
+      } else {
+        return $('.js-awards-block').eq(0);
+      }
+    };
+
+    AwardsHandler.prototype.getAwardUrl = function() {
+      return this.getVotesBlock().data('award-url');
+    };
+
+    AwardsHandler.prototype.checkMutuality = function(votesBlock, emoji) {
+      var $emojiButton, awardUrl, isAlreadyVoted, mutualVote;
+      awardUrl = this.getAwardUrl();
+      if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+        mutualVote = emoji === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
+        $emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
+        isAlreadyVoted = $emojiButton.hasClass('active');
+        if (isAlreadyVoted) {
+          this.showEmojiLoader($emojiButton);
+          return this.addAward(votesBlock, awardUrl, mutualVote, false, function() {
+            return $emojiButton.removeClass('is-loading');
+          });
+        }
+      }
+    };
+
+    AwardsHandler.prototype.showEmojiLoader = function($emojiButton) {
+      var $loader;
+      $loader = $emojiButton.find('.fa-spinner');
+      if (!$loader.length) {
+        $emojiButton.append('<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>');
+      }
+      return $emojiButton.addClass('is-loading');
+    };
+
+    AwardsHandler.prototype.isActive = function($emojiButton) {
+      return $emojiButton.hasClass('active');
+    };
+
+    AwardsHandler.prototype.decrementCounter = function($emojiButton, emoji) {
+      var counter, counterNumber;
+      counter = $('.js-counter', $emojiButton);
+      counterNumber = parseInt(counter.text(), 10);
+      if (counterNumber > 1) {
+        counter.text(counterNumber - 1);
+        this.removeMeFromUserList($emojiButton, emoji);
+      } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') {
+        $emojiButton.tooltip('destroy');
+        counter.text('0');
+        this.removeMeFromUserList($emojiButton, emoji);
+        if ($emojiButton.parents('.note').length) {
+          this.removeEmoji($emojiButton);
+        }
+      } else {
+        this.removeEmoji($emojiButton);
+      }
+      return $emojiButton.removeClass('active');
+    };
+
+    AwardsHandler.prototype.removeEmoji = function($emojiButton) {
+      var $votesBlock;
+      $emojiButton.tooltip('destroy');
+      $emojiButton.remove();
+      $votesBlock = this.getVotesBlock();
+      if ($votesBlock.find('.js-emoji-btn').length === 0) {
+        return $votesBlock.addClass('hidden');
+      }
+    };
+
+    AwardsHandler.prototype.getAwardTooltip = function($awardBlock) {
+      return $awardBlock.attr('data-original-title') || $awardBlock.attr('data-title') || '';
+    };
+
+    AwardsHandler.prototype.removeMeFromUserList = function($emojiButton, emoji) {
+      var authors, awardBlock, newAuthors, originalTitle;
+      awardBlock = $emojiButton;
+      originalTitle = this.getAwardTooltip(awardBlock);
+      authors = originalTitle.split(', ');
+      authors.splice(authors.indexOf('me'), 1);
+      newAuthors = authors.join(', ');
+      awardBlock.closest('.js-emoji-btn').removeData('original-title').attr('data-original-title', newAuthors);
+      return this.resetTooltip(awardBlock);
+    };
+
+    AwardsHandler.prototype.addMeToUserList = function(votesBlock, emoji) {
+      var awardBlock, origTitle, users;
+      awardBlock = this.findEmojiIcon(votesBlock, emoji).parent();
+      origTitle = this.getAwardTooltip(awardBlock);
+      users = [];
+      if (origTitle) {
+        users = origTitle.trim().split(', ');
+      }
+      users.push('me');
+      awardBlock.attr('title', users.join(', '));
+      return this.resetTooltip(awardBlock);
+    };
+
+    AwardsHandler.prototype.resetTooltip = function(award) {
+      var cb;
+      award.tooltip('destroy');
+      cb = function() {
+        return award.tooltip();
+      };
+      return setTimeout(cb, 200);
+    };
+
+    AwardsHandler.prototype.createEmoji_ = function(votesBlock, emoji) {
+      var $emojiButton, buttonHtml, emojiCssClass;
+      emojiCssClass = this.resolveNameToCssClass(emoji);
+      buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'> <div class='icon emoji-icon " + emojiCssClass + "' data-emoji='" + emoji + "'></div> <span class='award-control-text js-counter'>1</span> </button>";
+      $emojiButton = $(buttonHtml);
+      $emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('emoji', emoji);
+      this.animateEmoji($emojiButton);
+      $('.award-control').tooltip();
+      return votesBlock.removeClass('current');
+    };
+
+    AwardsHandler.prototype.animateEmoji = function($emoji) {
+      var className;
+      className = 'pulse animated';
+      $emoji.addClass(className);
+      return setTimeout((function() {
+        return $emoji.removeClass(className);
+      }), 321);
+    };
+
+    AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
+      if ($('.emoji-menu').length) {
+        return this.createEmoji_(votesBlock, emoji);
+      }
+      return this.createEmojiMenu(this.getAwardMenuUrl(), (function(_this) {
+        return function() {
+          return _this.createEmoji_(votesBlock, emoji);
+        };
+      })(this));
+    };
+
+    AwardsHandler.prototype.getAwardMenuUrl = function() {
+      return gon.award_menu_url;
+    };
+
+    AwardsHandler.prototype.resolveNameToCssClass = function(emoji) {
+      var emojiIcon, unicodeName;
+      emojiIcon = $(".emoji-menu-content [data-emoji='" + emoji + "']");
+      if (emojiIcon.length > 0) {
+        unicodeName = emojiIcon.data('unicode-name');
+      } else {
+        unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
+      }
+      return "emoji-" + unicodeName;
+    };
+
+    AwardsHandler.prototype.postEmoji = function(awardUrl, emoji, callback) {
+      return $.post(awardUrl, {
+        name: emoji
+      }, function(data) {
+        if (data.ok) {
+          return callback();
+        }
+      });
+    };
+
+    AwardsHandler.prototype.findEmojiIcon = function(votesBlock, emoji) {
+      return votesBlock.find(".js-emoji-btn [data-emoji='" + emoji + "']");
+    };
+
+    AwardsHandler.prototype.scrollToAwards = function() {
+      var options;
+      options = {
+        scrollTop: $('.awards').offset().top - 110
+      };
+      return $('body, html').animate(options, 200);
+    };
+
+    AwardsHandler.prototype.normilizeEmojiName = function(emoji) {
+      return this.aliases[emoji] || emoji;
+    };
+
+    AwardsHandler.prototype.addEmojiToFrequentlyUsedList = function(emoji) {
+      var frequentlyUsedEmojis;
+      frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+      frequentlyUsedEmojis.push(emoji);
+      return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
+        expires: 365
+      });
+    };
+
+    AwardsHandler.prototype.getFrequentlyUsedEmojis = function() {
+      var frequentlyUsedEmojis;
+      frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',');
+      return _.compact(_.uniq(frequentlyUsedEmojis));
+    };
+
+    AwardsHandler.prototype.renderFrequentlyUsedBlock = function() {
+      var emoji, frequentlyUsedEmojis, i, len, ul;
+      if ($.cookie('frequently_used_emojis')) {
+        frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
+        ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
+        for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) {
+          emoji = frequentlyUsedEmojis[i];
+          $(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul);
+        }
+        $('.emoji-menu-content').prepend(ul).prepend($('<h5>').text('Frequently used'));
+      }
+      return this.frequentEmojiBlockRendered = true;
+    };
+
+    AwardsHandler.prototype.setupSearch = function() {
+      return $('input.emoji-search').on('keyup', (function(_this) {
+        return function(ev) {
+          var found_emojis, h5, term, ul;
+          term = $(ev.target).val();
+          $('ul.emoji-menu-search, h5.emoji-search').remove();
+          if (term) {
+            h5 = $('<h5>').text('Search results');
+            found_emojis = _this.searchEmojis(term).show();
+            ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
+            $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
+            return $('.emoji-menu-content').append(h5).append(ul);
+          } else {
+            return $('.emoji-menu-content').children().show();
+          }
+        };
+      })(this));
+    };
+
+    AwardsHandler.prototype.searchEmojis = function(term) {
+      return $(".emoji-menu-list:not(.frequent-emojis) [data-emoji*='" + term + "']").closest('li').clone();
+    };
+
+    return AwardsHandler;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
new file mode 100644
index 0000000000000000000000000000000000000000..f977a1e8a7b072806b0bd7cdaacadfd24ee94262
--- /dev/null
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -0,0 +1,30 @@
+
+/*= require jquery.ba-resize */
+
+
+/*= require autosize */
+
+(function() {
+  $(function() {
+    var $fields;
+    $fields = $('.js-autosize');
+    $fields.on('autosize:resized', function() {
+      var $field;
+      $field = $(this);
+      return $field.data('height', $field.outerHeight());
+    });
+    $fields.on('resize.autosize', function() {
+      var $field;
+      $field = $(this);
+      if ($field.data('height') !== $field.outerHeight()) {
+        $field.data('height', $field.outerHeight());
+        autosize.destroy($field);
+        return $field.css('max-height', window.outerHeight);
+      }
+    });
+    autosize($fields);
+    autosize.update($fields);
+    return $fields.css('resize', 'vertical');
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/behaviors/autosize.js.coffee b/app/assets/javascripts/behaviors/autosize.js.coffee
deleted file mode 100644
index a072fe48a9843480226d4cbef1fd748fe481c89d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/autosize.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-#= require jquery.ba-resize
-#= require autosize
-
-$ ->
-  $fields = $('.js-autosize')
-
-  $fields.on 'autosize:resized', ->
-    $field = $(@)
-    $field.data('height', $field.outerHeight())
-
-  $fields.on 'resize.autosize', ->
-    $field = $(@)
-
-    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')
diff --git a/app/assets/javascripts/behaviors/details_behavior.coffee b/app/assets/javascripts/behaviors/details_behavior.coffee
deleted file mode 100644
index decab3e1bed29bc2c9f26804be2308f52298da34..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/details_behavior.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-$ ->
-  $("body").on "click", ".js-details-target", ->
-    container = $(@).closest(".js-details-container")
-    container.toggleClass("open")
-
-  # Show details content. Hides link after click.
-  #
-  # %div
-  #   %a.js-details-expand
-  #   %div.js-details-content
-  #
-  $("body").on "click", ".js-details-expand", (e) ->
-    $(@).next('.js-details-content').removeClass("hide")
-    $(@).hide()
-    e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
new file mode 100644
index 0000000000000000000000000000000000000000..3631d1b74ac17a6e9da357919cedc1c72f1a8861
--- /dev/null
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -0,0 +1,15 @@
+(function() {
+  $(function() {
+    $("body").on("click", ".js-details-target", function() {
+      var container;
+      container = $(this).closest(".js-details-container");
+      return container.toggleClass("open");
+    });
+    return $("body").on("click", ".js-details-expand", function(e) {
+      $(this).next('.js-details-content').removeClass("hide");
+      $(this).hide();
+      return e.preventDefault();
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
new file mode 100644
index 0000000000000000000000000000000000000000..3527d0a95fc584bda085641ce28a822f39640bde
--- /dev/null
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -0,0 +1,58 @@
+
+/*= require extensions/jquery */
+
+(function() {
+  var isMac, keyCodeIs;
+
+  isMac = function() {
+    return navigator.userAgent.match(/Macintosh/);
+  };
+
+  keyCodeIs = function(e, keyCode) {
+    if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
+      return false;
+    }
+    return e.keyCode === keyCode;
+  };
+
+  $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
+    var $form, $submit_button;
+    if (!keyCodeIs(e, 13)) {
+      return;
+    }
+    if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
+      return;
+    }
+    e.preventDefault();
+    $form = $(e.target).closest('form');
+    $submit_button = $form.find('input[type=submit], button[type=submit]');
+    if ($submit_button.attr('disabled')) {
+      return;
+    }
+    $submit_button.disable();
+    return $form.submit();
+  });
+
+  $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
+    var $this, title;
+    if (!keyCodeIs(e, 9)) {
+      return;
+    }
+    if (isMac()) {
+      title = "You can also press &#8984;-Enter";
+    } else {
+      title = "You can also press Ctrl-Enter";
+    }
+    $this = $(this);
+    return $this.tooltip({
+      container: 'body',
+      html: 'true',
+      placement: 'auto top',
+      title: title,
+      trigger: 'manual'
+    }).tooltip('show').one('blur', function() {
+      return $this.tooltip('hide');
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
deleted file mode 100644
index 3cb96bacaa741f2c8a5d62496a0f7f8d2dab78ad..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-# Quick Submit behavior
-#
-# When a child field of a form with a `js-quick-submit` class receives a
-# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
-# is submitted.
-#
-#= require extensions/jquery
-#
-# ### Example Markup
-#
-#   <form action="/foo" class="js-quick-submit">
-#     <input type="text" />
-#     <textarea></textarea>
-#     <input type="submit" value="Submit" />
-#   </form>
-#
-isMac = ->
-  navigator.userAgent.match(/Macintosh/)
-
-keyCodeIs = (e, keyCode) ->
-  return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
-  return e.keyCode == keyCode
-
-$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
-  return unless keyCodeIs(e, 13) # Enter
-
-  return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
-
-  e.preventDefault()
-
-  $form = $(e.target).closest('form')
-  $submit_button = $form.find('input[type=submit], button[type=submit]')
-
-  return if $submit_button.attr('disabled')
-
-  $submit_button.disable()
-  $form.submit()
-
-# If the user tabs to a submit button on a `js-quick-submit` form, display a
-# tooltip to let them know they could've used the hotkey
-$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
-  return unless keyCodeIs(e, 9) # Tab
-
-  if isMac()
-    title = "You can also press &#8984;-Enter"
-  else
-    title = "You can also press Ctrl-Enter"
-
-  $this = $(@)
-  $this.tooltip(
-    container: 'body'
-    html: 'true'
-    placement: 'auto top'
-    title: title
-    trigger: 'manual'
-  ).tooltip('show').one('blur', -> $this.tooltip('hide'))
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
new file mode 100644
index 0000000000000000000000000000000000000000..db0b36b24e9e53f081ed466fc30e6047541fdd84
--- /dev/null
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -0,0 +1,45 @@
+
+/*= require extensions/jquery */
+
+(function() {
+  $.fn.requiresInput = function() {
+    var $button, $form, fieldSelector, requireInput, required;
+    $form = $(this);
+    $button = $('button[type=submit], input[type=submit]', $form);
+    required = '[required=required]';
+    fieldSelector = "input" + required + ", select" + required + ", textarea" + required;
+    requireInput = function() {
+      var values;
+      values = _.map($(fieldSelector, $form), function(field) {
+        return field.value;
+      });
+      if (values.length && _.any(values, _.isEmpty)) {
+        return $button.disable();
+      } else {
+        return $button.enable();
+      }
+    };
+    requireInput();
+    return $form.on('change input', fieldSelector, requireInput);
+  };
+
+  $(function() {
+    var $form, hideOrShowHelpBlock;
+    $form = $('form.js-requires-input');
+    $form.requiresInput();
+    hideOrShowHelpBlock = function(form) {
+      var selected;
+      selected = $('.js-select-namespace option:selected');
+      if (selected.length && selected.data('options-parent') === 'groups') {
+        return form.find('.help-block').hide();
+      } else if (selected.length) {
+        return form.find('.help-block').show();
+      }
+    };
+    hideOrShowHelpBlock($form);
+    return $('.select2.js-select-namespace').change(function() {
+      return hideOrShowHelpBlock($form);
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee
deleted file mode 100644
index 0faa570ce13a9b98d3767ff9c554617eb6c8e8ce..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/requires_input.js.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-# Requires Input behavior
-#
-# When called on a form with input fields with the `required` attribute, the
-# form's submit button will be disabled until all required fields have values.
-#
-#= require extensions/jquery
-#
-# ### Example Markup
-#
-#   <form class="js-requires-input">
-#     <input type="text" required="required">
-#     <input type="submit" value="Submit">
-#   </form>
-#
-$.fn.requiresInput = ->
-  $form   = $(this)
-  $button = $('button[type=submit], input[type=submit]', $form)
-
-  required      = '[required=required]'
-  fieldSelector = "input#{required}, select#{required}, textarea#{required}"
-
-  requireInput = ->
-    # Collect the input values of *all* required fields
-    values = _.map $(fieldSelector, $form), (field) -> field.value
-
-    # Disable the button if any required fields are empty
-    if values.length && _.any(values, _.isEmpty)
-      $button.disable()
-    else
-      $button.enable()
-
-  # Set initial button state
-  requireInput()
-
-  $form.on 'change input', fieldSelector, requireInput
-
-$ ->
-  $form = $('form.js-requires-input')
-  $form.requiresInput()
-
-  # Hide or Show the help block when creating a new project
-  # based on the option selected
-  hideOrShowHelpBlock = (form) ->
-    selected = $('.js-select-namespace option:selected')
-    if selected.length and selected.data('options-parent') is 'groups'
-      return form.find('.help-block').hide()
-    else if selected.length
-      form.find('.help-block').show()
-
-  hideOrShowHelpBlock($form)
-
-  $('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee
deleted file mode 100644
index 177b6918270dcc1a58851fc46f10dcb5aae2f06e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/behaviors/toggler_behavior.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-$ ->
-  # Toggle button. Show/hide content inside parent container.
-  # Button does not change visibility. If button has icon - it changes chevron style.
-  #
-  # %div.js-toggle-container
-  #   %a.js-toggle-button
-  #   %div.js-toggle-content
-  #
-  $("body").on "click", ".js-toggle-button", (e) ->
-    $(@).find('i').
-      toggleClass('fa fa-chevron-down').
-      toggleClass('fa fa-chevron-up')
-    $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
-    e.preventDefault()
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b7b63489ea3fc5eb300a90ed84ada71f3af53fb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -0,0 +1,10 @@
+(function() {
+  $(function() {
+    return $("body").on("click", ".js-toggle-button", function(e) {
+      $(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up');
+      $(this).closest(".js-toggle-container").find(".js-toggle-content").toggle();
+      return e.preventDefault();
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js
new file mode 100644
index 0000000000000000000000000000000000000000..6875857496776896297e353b6e6ae38c37e54526
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js
@@ -0,0 +1,46 @@
+
+/*= require blob/template_selector */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.BlobCiYamlSelector = (function(superClass) {
+    extend(BlobCiYamlSelector, superClass);
+
+    function BlobCiYamlSelector() {
+      return BlobCiYamlSelector.__super__.constructor.apply(this, arguments);
+    }
+
+    BlobCiYamlSelector.prototype.requestFile = function(query) {
+      return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
+    };
+
+    return BlobCiYamlSelector;
+
+  })(TemplateSelector);
+
+  this.BlobCiYamlSelectors = (function() {
+    function BlobCiYamlSelectors(opts) {
+      var ref;
+      this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor;
+      this.$dropdowns.each((function(_this) {
+        return function(i, dropdown) {
+          var $dropdown;
+          $dropdown = $(dropdown);
+          return new BlobCiYamlSelector({
+            pattern: /(.gitlab-ci.yml)/,
+            data: $dropdown.data('data'),
+            wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+            dropdown: $dropdown,
+            editor: _this.editor
+          });
+        };
+      })(this));
+    }
+
+    return BlobCiYamlSelectors;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
deleted file mode 100644
index d9a03d055290a2e4c6cd35809b52149db802c07d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require blob/template_selector
-
-class @BlobCiYamlSelector extends TemplateSelector
-  requestFile: (query) ->
-    Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
-
-class @BlobCiYamlSelectors
-  constructor: (opts) ->
-    {
-      @$dropdowns = $('.js-gitlab-ci-yml-selector')
-      @editor
-    } = opts
-
-    @$dropdowns.each (i, dropdown) =>
-      $dropdown = $(dropdown)
-
-      new BlobCiYamlSelector(
-        pattern: /(.gitlab-ci.yml)/,
-        data: $dropdown.data('data'),
-        wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
-        dropdown: $dropdown,
-        editor: @editor
-      )
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4044f22db20f514b2adf7ebbc8dcb50ae08894f
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -0,0 +1,62 @@
+(function() {
+  this.BlobFileDropzone = (function() {
+    function BlobFileDropzone(form, method) {
+      var dropzone, form_dropzone, submitButton;
+      form_dropzone = form.find('.dropzone');
+      Dropzone.autoDiscover = false;
+      dropzone = form_dropzone.dropzone({
+        autoDiscover: false,
+        autoProcessQueue: false,
+        url: form.attr('action'),
+        method: method,
+        clickable: true,
+        uploadMultiple: false,
+        paramName: "file",
+        maxFilesize: gon.max_file_size || 10,
+        parallelUploads: 1,
+        maxFiles: 1,
+        addRemoveLinks: true,
+        previewsContainer: '.dropzone-previews',
+        headers: {
+          "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+        },
+        init: function() {
+          this.on('addedfile', function(file) {
+            $('.dropzone-alerts').html('').hide();
+          });
+          this.on('success', function(header, response) {
+            window.location.href = response.filePath;
+          });
+          this.on('maxfilesexceeded', function(file) {
+            this.removeFile(file);
+          });
+          return this.on('sending', function(file, xhr, formData) {
+            formData.append('target_branch', form.find('.js-target-branch').val());
+            formData.append('create_merge_request', form.find('.js-create-merge-request').val());
+            formData.append('commit_message', form.find('.js-commit-message').val());
+          });
+        },
+        error: function(file, errorMessage) {
+          var stripped;
+          stripped = $("<div/>").html(errorMessage).text();
+          $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show();
+          this.removeFile(file);
+        }
+      });
+      submitButton = form.find('#submit-all')[0];
+      submitButton.addEventListener('click', function(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
+          alert("Please select a file");
+        }
+        dropzone[0].dropzone.processQueue();
+        return false;
+      });
+    }
+
+    return BlobFileDropzone;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
deleted file mode 100644
index 9df932817f6aa785360c5b0a973f3d3895369be7..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ /dev/null
@@ -1,57 +0,0 @@
-class @BlobFileDropzone
-  constructor: (form, method) ->
-    form_dropzone = form.find('.dropzone')
-    Dropzone.autoDiscover = false
-    dropzone = form_dropzone.dropzone(
-      autoDiscover: false
-      autoProcessQueue: false
-      url: form.attr('action')
-      # Rails uses a hidden input field for PUT
-      # http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
-      method: method
-      clickable: true
-      uploadMultiple: false
-      paramName: "file"
-      maxFilesize: gon.max_file_size or 10
-      parallelUploads: 1
-      maxFiles: 1
-      addRemoveLinks: true
-      previewsContainer: '.dropzone-previews'
-      headers:
-        "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
-      init: ->
-        this.on 'addedfile', (file) ->
-          $('.dropzone-alerts').html('').hide()
-
-          return
-
-        this.on 'success', (header, response) ->
-          window.location.href = response.filePath
-          return
-
-        this.on 'maxfilesexceeded', (file) ->
-          @removeFile file
-          return
-
-        this.on 'sending', (file, xhr, formData) ->
-          formData.append('target_branch', form.find('.js-target-branch').val())
-          formData.append('create_merge_request', form.find('.js-create-merge-request').val())
-          formData.append('commit_message', form.find('.js-commit-message').val())
-          return
-
-      # Override behavior of adding error underneath preview
-      error: (file, errorMessage) ->
-        stripped = $("<div/>").html(errorMessage).text();
-        $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show()
-        @removeFile file
-        return
-    )
-
-    submitButton = form.find('#submit-all')[0]
-    submitButton.addEventListener 'click', (e) ->
-      e.preventDefault()
-      e.stopPropagation()
-      alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0
-      dropzone[0].dropzone.processQueue()
-      return false
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..54a09e919f8c4f74e0b8b17e4f29fe2eb01f953d
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_gitignore_selector.js
@@ -0,0 +1,23 @@
+
+/*= require blob/template_selector */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.BlobGitignoreSelector = (function(superClass) {
+    extend(BlobGitignoreSelector, superClass);
+
+    function BlobGitignoreSelector() {
+      return BlobGitignoreSelector.__super__.constructor.apply(this, arguments);
+    }
+
+    BlobGitignoreSelector.prototype.requestFile = function(query) {
+      return Api.gitignoreText(query.name, this.requestFileSuccess.bind(this));
+    };
+
+    return BlobGitignoreSelector;
+
+  })(TemplateSelector);
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee b/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
deleted file mode 100644
index 8d0e3f363d1738b53c96340a3a0f50e972175f23..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selector.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-#= require blob/template_selector
-
-class @BlobGitignoreSelector extends TemplateSelector
-  requestFile: (query) ->
-    Api.gitignoreText query.name, @requestFileSuccess.bind(@)
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e9500428b2e0f7131b5cdc9730fb3eecfcc7f99
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js
@@ -0,0 +1,25 @@
+(function() {
+  this.BlobGitignoreSelectors = (function() {
+    function BlobGitignoreSelectors(opts) {
+      var ref;
+      this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitignore-selector'), this.editor = opts.editor;
+      this.$dropdowns.each((function(_this) {
+        return function(i, dropdown) {
+          var $dropdown;
+          $dropdown = $(dropdown);
+          return new BlobGitignoreSelector({
+            pattern: /(.gitignore)/,
+            data: $dropdown.data('data'),
+            wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
+            dropdown: $dropdown,
+            editor: _this.editor
+          });
+        };
+      })(this));
+    }
+
+    return BlobGitignoreSelectors;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee b/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee
deleted file mode 100644
index a719ba251222f26000ee9e1cba9f117832d5cd45..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_gitignore_selectors.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @BlobGitignoreSelectors
-  constructor: (opts) ->
-    {
-      @$dropdowns = $('.js-gitignore-selector')
-      @editor
-    } = opts
-
-    @$dropdowns.each (i, dropdown) =>
-      $dropdown = $(dropdown)
-
-      new BlobGitignoreSelector(
-        pattern: /(.gitignore)/,
-        data: $dropdown.data('data'),
-        wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
-        dropdown: $dropdown,
-        editor: @editor
-      )
diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a8ef08f4e5c7b987f12a93ce8752de00ac4978f
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selector.js
@@ -0,0 +1,28 @@
+
+/*= require blob/template_selector */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.BlobLicenseSelector = (function(superClass) {
+    extend(BlobLicenseSelector, superClass);
+
+    function BlobLicenseSelector() {
+      return BlobLicenseSelector.__super__.constructor.apply(this, arguments);
+    }
+
+    BlobLicenseSelector.prototype.requestFile = function(query) {
+      var data;
+      data = {
+        project: this.dropdown.data('project'),
+        fullname: this.dropdown.data('fullname')
+      };
+      return Api.licenseText(query.id, data, this.requestFileSuccess.bind(this));
+    };
+
+    return BlobLicenseSelector;
+
+  })(TemplateSelector);
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee
deleted file mode 100644
index a3cc8dd844c0af229c1944c5cfa6e851e75534a4..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_license_selector.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-#= require blob/template_selector
-
-class @BlobLicenseSelector extends TemplateSelector
-  requestFile: (query) ->
-    data =
-      project: @dropdown.data('project')
-      fullname: @dropdown.data('fullname')
-
-    Api.licenseText query.id, data, @requestFileSuccess.bind(@)
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..39237705e8d63dfe5b3c545d631273c13511dfa8
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_license_selectors.js
@@ -0,0 +1,25 @@
+(function() {
+  this.BlobLicenseSelectors = (function() {
+    function BlobLicenseSelectors(opts) {
+      var ref;
+      this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor;
+      this.$dropdowns.each((function(_this) {
+        return function(i, dropdown) {
+          var $dropdown;
+          $dropdown = $(dropdown);
+          return new BlobLicenseSelector({
+            pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
+            data: $dropdown.data('data'),
+            wrapper: $dropdown.closest('.js-license-selector-wrap'),
+            dropdown: $dropdown,
+            editor: _this.editor
+          });
+        };
+      })(this));
+    }
+
+    return BlobLicenseSelectors;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.coffee b/app/assets/javascripts/blob/blob_license_selectors.js.coffee
deleted file mode 100644
index 6843873310881d3a4eeadee6f49ef2704c76ad11..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/blob_license_selectors.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @BlobLicenseSelectors
-  constructor: (opts) ->
-    {
-      @$dropdowns = $('.js-license-selector')
-      @editor
-    } = opts
-
-    @$dropdowns.each (i, dropdown) =>
-      $dropdown = $(dropdown)
-
-      new BlobLicenseSelector(
-        pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
-        data: $dropdown.data('data'),
-        wrapper: $dropdown.closest('.js-license-selector-wrap'),
-        dropdown: $dropdown,
-        editor: @editor
-      )
diff --git a/app/assets/javascripts/blob/edit_blob.js b/app/assets/javascripts/blob/edit_blob.js
new file mode 100644
index 0000000000000000000000000000000000000000..649c79daee8b13a8eeed7a7d055d594ab3f584b8
--- /dev/null
+++ b/app/assets/javascripts/blob/edit_blob.js
@@ -0,0 +1,66 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.EditBlob = (function() {
+    function EditBlob(assets_path, ace_mode) {
+      if (ace_mode == null) {
+        ace_mode = null;
+      }
+      this.editModeLinkClickHandler = bind(this.editModeLinkClickHandler, this);
+      ace.config.set("modePath", assets_path + "/ace");
+      ace.config.loadModule("ace/ext/searchbox");
+      this.editor = ace.edit("editor");
+      this.editor.focus();
+      if (ace_mode) {
+        this.editor.getSession().setMode("ace/mode/" + ace_mode);
+      }
+      $('form').submit((function(_this) {
+        return function() {
+          return $("#file-content").val(_this.editor.getValue());
+        };
+      })(this));
+      this.initModePanesAndLinks();
+      new BlobLicenseSelectors({
+        editor: this.editor
+      });
+      new BlobGitignoreSelectors({
+        editor: this.editor
+      });
+      new BlobCiYamlSelectors({
+        editor: this.editor
+      });
+    }
+
+    EditBlob.prototype.initModePanesAndLinks = function() {
+      this.$editModePanes = $(".js-edit-mode-pane");
+      this.$editModeLinks = $(".js-edit-mode a");
+      return this.$editModeLinks.click(this.editModeLinkClickHandler);
+    };
+
+    EditBlob.prototype.editModeLinkClickHandler = function(event) {
+      var currentLink, currentPane, paneId;
+      event.preventDefault();
+      currentLink = $(event.target);
+      paneId = currentLink.attr("href");
+      currentPane = this.$editModePanes.filter(paneId);
+      this.$editModeLinks.parent().removeClass("active hover");
+      currentLink.parent().addClass("active hover");
+      this.$editModePanes.hide();
+      currentPane.fadeIn(200);
+      if (paneId === "#preview") {
+        return $.post(currentLink.data("preview-url"), {
+          content: this.editor.getValue()
+        }, function(response) {
+          currentPane.empty().append(response);
+          return currentPane.syntaxHighlight();
+        });
+      } else {
+        return this.editor.focus();
+      }
+    };
+
+    return EditBlob;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
deleted file mode 100644
index 19e584519d7470f84ba811502bd564c1b0148085..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ /dev/null
@@ -1,42 +0,0 @@
-class @EditBlob
-  constructor: (assets_path, ace_mode = null) ->
-    ace.config.set "modePath", "#{assets_path}/ace"
-    ace.config.loadModule "ace/ext/searchbox"
-    @editor = ace.edit("editor")
-    @editor.focus()
-    @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
-
-    # Before a form submission, move the content from the Ace editor into the
-    # submitted textarea
-    $('form').submit =>
-      $("#file-content").val(@editor.getValue())
-
-    @initModePanesAndLinks()
-
-    new BlobLicenseSelectors { @editor }
-    new BlobGitignoreSelectors { @editor }
-    new BlobCiYamlSelectors { @editor }
-
-  initModePanesAndLinks: ->
-    @$editModePanes = $(".js-edit-mode-pane")
-    @$editModeLinks = $(".js-edit-mode a")
-    @$editModeLinks.click @editModeLinkClickHandler
-
-  editModeLinkClickHandler: (event) =>
-    event.preventDefault()
-    currentLink = $(event.target)
-    paneId = currentLink.attr("href")
-    currentPane = @$editModePanes.filter(paneId)
-    @$editModeLinks.parent().removeClass "active hover"
-    currentLink.parent().addClass "active hover"
-    @$editModePanes.hide()
-    currentPane.fadeIn 200
-    if paneId is "#preview"
-      $.post currentLink.data("preview-url"),
-        content: @editor.getValue()
-      , (response) ->
-        currentPane.empty().append response
-        currentPane.syntaxHighlight()
-
-    else
-      @editor.focus()
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..2cf0a6631b8d4e3ab8b90dc4283d940c3413b72d
--- /dev/null
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -0,0 +1,74 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.TemplateSelector = (function() {
+    function TemplateSelector(opts) {
+      var ref;
+      if (opts == null) {
+        opts = {};
+      }
+      this.onClick = bind(this.onClick, this);
+      this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
+      this.buildDropdown();
+      this.bindEvents();
+      this.onFilenameUpdate();
+    }
+
+    TemplateSelector.prototype.buildDropdown = function() {
+      return this.dropdown.glDropdown({
+        data: this.data,
+        filterable: true,
+        selectable: true,
+        toggleLabel: this.toggleLabel,
+        search: {
+          fields: ['name']
+        },
+        clicked: this.onClick,
+        text: function(item) {
+          return item.name;
+        }
+      });
+    };
+
+    TemplateSelector.prototype.bindEvents = function() {
+      return this.$input.on('keyup blur', (function(_this) {
+        return function(e) {
+          return _this.onFilenameUpdate();
+        };
+      })(this));
+    };
+
+    TemplateSelector.prototype.toggleLabel = function(item) {
+      return item.name;
+    };
+
+    TemplateSelector.prototype.onFilenameUpdate = function() {
+      var filenameMatches;
+      if (!this.$input.length) {
+        return;
+      }
+      filenameMatches = this.pattern.test(this.$input.val().trim());
+      if (!filenameMatches) {
+        this.wrapper.addClass('hidden');
+        return;
+      }
+      return this.wrapper.removeClass('hidden');
+    };
+
+    TemplateSelector.prototype.onClick = function(item, el, e) {
+      e.preventDefault();
+      return this.requestFile(item);
+    };
+
+    TemplateSelector.prototype.requestFile = function(item) {};
+
+    TemplateSelector.prototype.requestFileSuccess = function(file) {
+      this.editor.setValue(file.content, 1);
+      return this.editor.focus();
+    };
+
+    return TemplateSelector;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/blob/template_selector.js.coffee b/app/assets/javascripts/blob/template_selector.js.coffee
deleted file mode 100644
index 40c9169beac4e4393f70af5bf2382af456ff306a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/blob/template_selector.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @TemplateSelector
-  constructor: (opts = {}) ->
-    {
-      @dropdown,
-      @data,
-      @pattern,
-      @wrapper,
-      @editor,
-      @fileEndpoint,
-      @$input = $('#file_name')
-    } = opts
-
-    @buildDropdown()
-    @bindEvents()
-    @onFilenameUpdate()
-
-  buildDropdown: ->
-    @dropdown.glDropdown(
-      data: @data,
-      filterable: true,
-      selectable: true,
-      toggleLabel: @toggleLabel,
-      search:
-        fields: ['name']
-      clicked: @onClick
-      text: (item) ->
-        item.name
-    )
-
-  bindEvents: ->
-    @$input.on('keyup blur', (e) =>
-      @onFilenameUpdate()
-    )
-
-  toggleLabel: (item) ->
-    item.name
-
-  onFilenameUpdate: ->
-    return unless @$input.length
-
-    filenameMatches = @pattern.test(@$input.val().trim())
-
-    if not filenameMatches
-      @wrapper.addClass('hidden')
-      return
-
-    @wrapper.removeClass('hidden')
-
-  onClick: (item, el, e) =>
-    e.preventDefault()
-    @requestFile(item)
-
-  requestFile: (item) ->
-    # To be implemented on the extending class
-    # e.g.
-    # Api.gitignoreText item.name, @requestFileSuccess.bind(@)
-
-  requestFileSuccess: (file) ->
-    @editor.setValue(file.content, 1)
-    @editor.focus()
diff --git a/app/assets/javascripts/breakpoints.coffee b/app/assets/javascripts/breakpoints.coffee
deleted file mode 100644
index 5457430f9212056df4acd86e0fe354bef85e797c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/breakpoints.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-class @Breakpoints
-  instance = null;
-
-  class BreakpointInstance
-    BREAKPOINTS = ["xs", "sm", "md", "lg"]
-
-    constructor: ->
-      @setup()
-
-    setup: ->
-      allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
-        ".device-#{breakpoint}"
-      return if $(allDeviceSelector.join(",")).length
-
-      # Create all the elements
-      els = $.map BREAKPOINTS, (breakpoint) ->
-        "<div class='device-#{breakpoint} visible-#{breakpoint}'></div>"
-      $("body").append els.join('')
-
-    visibleDevice: ->
-      allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
-        ".device-#{breakpoint}"
-      $(allDeviceSelector.join(",")).filter(":visible")
-
-    getBreakpointSize: ->
-      $visibleDevice = @visibleDevice
-      # the page refreshed via turbolinks
-      if not $visibleDevice().length
-        @setup()
-      $visibleDevice = @visibleDevice()
-      return $visibleDevice.attr("class").split("visible-")[1]
-
-  @get: ->
-    return instance ?= new BreakpointInstance
-
-$ =>
-  @bp = Breakpoints.get()
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e0148e579817a38b91e81c3428d39d3bdae303b
--- /dev/null
+++ b/app/assets/javascripts/breakpoints.js
@@ -0,0 +1,68 @@
+(function() {
+  this.Breakpoints = (function() {
+    var BreakpointInstance, instance;
+
+    function Breakpoints() {}
+
+    instance = null;
+
+    BreakpointInstance = (function() {
+      var BREAKPOINTS;
+
+      BREAKPOINTS = ["xs", "sm", "md", "lg"];
+
+      function BreakpointInstance() {
+        this.setup();
+      }
+
+      BreakpointInstance.prototype.setup = function() {
+        var allDeviceSelector, els;
+        allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+          return ".device-" + breakpoint;
+        });
+        if ($(allDeviceSelector.join(",")).length) {
+          return;
+        }
+        els = $.map(BREAKPOINTS, function(breakpoint) {
+          return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
+        });
+        return $("body").append(els.join(''));
+      };
+
+      BreakpointInstance.prototype.visibleDevice = function() {
+        var allDeviceSelector;
+        allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
+          return ".device-" + breakpoint;
+        });
+        return $(allDeviceSelector.join(",")).filter(":visible");
+      };
+
+      BreakpointInstance.prototype.getBreakpointSize = function() {
+        var $visibleDevice;
+        $visibleDevice = this.visibleDevice;
+        if (!$visibleDevice().length) {
+          this.setup();
+        }
+        $visibleDevice = this.visibleDevice();
+        return $visibleDevice.attr("class").split("visible-")[1];
+      };
+
+      return BreakpointInstance;
+
+    })();
+
+    Breakpoints.get = function() {
+      return instance != null ? instance : instance = new BreakpointInstance;
+    };
+
+    return Breakpoints;
+
+  })();
+
+  $((function(_this) {
+    return function() {
+      return _this.bp = Breakpoints.get();
+    };
+  })(this));
+
+}).call(this);
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js
new file mode 100644
index 0000000000000000000000000000000000000000..fceeff3672851ec4e5d7fbda347ade7bfec3a8d3
--- /dev/null
+++ b/app/assets/javascripts/broadcast_message.js
@@ -0,0 +1,34 @@
+(function() {
+  $(function() {
+    var previewPath;
+    $('input#broadcast_message_color').on('input', function() {
+      var previewColor;
+      previewColor = $(this).val();
+      return $('div.broadcast-message-preview').css('background-color', previewColor);
+    });
+    $('input#broadcast_message_font').on('input', function() {
+      var previewColor;
+      previewColor = $(this).val();
+      return $('div.broadcast-message-preview').css('color', previewColor);
+    });
+    previewPath = $('textarea#broadcast_message_message').data('preview-path');
+    return $('textarea#broadcast_message_message').on('input', function() {
+      var message;
+      message = $(this).val();
+      if (message === '') {
+        return $('.js-broadcast-message-preview').text("Your message here");
+      } else {
+        return $.ajax({
+          url: previewPath,
+          type: "POST",
+          data: {
+            broadcast_message: {
+              message: message
+            }
+          }
+        });
+      }
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/broadcast_message.js.coffee b/app/assets/javascripts/broadcast_message.js.coffee
deleted file mode 100644
index a38a329c4c2345e32316a4f43cc635f3b4b85f52..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/broadcast_message.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-$ ->
-  $('input#broadcast_message_color').on 'input', ->
-    previewColor = $(@).val()
-    $('div.broadcast-message-preview').css('background-color', previewColor)
-
-  $('input#broadcast_message_font').on 'input', ->
-    previewColor = $(@).val()
-    $('div.broadcast-message-preview').css('color', previewColor)
-
-  previewPath = $('textarea#broadcast_message_message').data('preview-path')
-
-  $('textarea#broadcast_message_message').on 'input', ->
-    message = $(@).val()
-
-    if message == ''
-      $('.js-broadcast-message-preview').text("Your message here")
-    else
-      $.ajax(
-        url: previewPath
-        type: "POST"
-        data: { broadcast_message: { message: message } }
-      )
diff --git a/app/assets/javascripts/build.coffee b/app/assets/javascripts/build.coffee
deleted file mode 100644
index cf203ea43a0c7c9ecbf3c76ba4194d9553ca4499..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/build.coffee
+++ /dev/null
@@ -1,114 +0,0 @@
-class @Build
-  @interval: null
-  @state: null
-
-  constructor: (@page_url, @build_url, @build_status, @state) ->
-    clearInterval(Build.interval)
-
-    # Init breakpoint checker
-    @bp = Breakpoints.get()
-    @hideSidebar()
-    $('.js-build-sidebar').niceScroll()
-    $(document)
-      .off 'click', '.js-sidebar-build-toggle'
-      .on 'click', '.js-sidebar-build-toggle', @toggleSidebar
-
-    $(window)
-      .off 'resize.build'
-      .on 'resize.build', @hideSidebar
-
-    @updateArtifactRemoveDate()
-
-    if $('#build-trace').length
-      @getInitialBuildTrace()
-      @initScrollButtonAffix()
-
-    if @build_status is "running" or @build_status is "pending"
-      #
-      # Bind autoscroll button to follow build output
-      #
-      $('#autoscroll-button').on 'click', ->
-        state = $(this).data("state")
-        if "enabled" is state
-          $(this).data "state", "disabled"
-          $(this).text "enable autoscroll"
-        else
-          $(this).data "state", "enabled"
-          $(this).text "disable autoscroll"
-
-      #
-      # Check for new build output if user still watching build page
-      # Only valid for runnig build when output changes during time
-      #
-      Build.interval = setInterval =>
-        if window.location.href.split("#").first() is @page_url
-          @getBuildTrace()
-      , 4000
-
-  getInitialBuildTrace: ->
-    $.ajax
-      url: @build_url
-      dataType: 'json'
-      success: (build_data) ->
-        $('.js-build-output').html build_data.trace_html
-
-        if build_data.status is 'success' or build_data.status is 'failed'
-          $('.js-build-refresh').remove()
-
-  getBuildTrace: ->
-    $.ajax
-      url: "#{@page_url}/trace.json?state=#{encodeURIComponent(@state)}"
-      dataType: "json"
-      success: (log) =>
-        if log.state
-          @state = log.state
-
-        if log.status is "running"
-          if log.append
-            $('.js-build-output').append log.html
-          else
-            $('.js-build-output').html log.html
-          @checkAutoscroll()
-        else if log.status isnt @build_status
-          Turbolinks.visit @page_url
-
-  checkAutoscroll: ->
-    $("html,body").scrollTop $("#build-trace").height()  if "enabled" is $("#autoscroll-button").data("state")
-
-  initScrollButtonAffix: ->
-    $buildScroll = $('#js-build-scroll')
-    $body = $('body')
-    $buildTrace = $('#build-trace')
-
-    $buildScroll.affix(
-      offset:
-        bottom: ->
-          $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
-    )
-
-  shouldHideSidebar: ->
-    bootstrapBreakpoint = @bp.getBreakpointSize()
-
-    bootstrapBreakpoint is 'xs' or bootstrapBreakpoint is 'sm'
-
-  toggleSidebar: =>
-    if @shouldHideSidebar()
-      $('.js-build-sidebar')
-        .toggleClass 'right-sidebar-expanded right-sidebar-collapsed'
-
-  hideSidebar: =>
-    if @shouldHideSidebar()
-      $('.js-build-sidebar')
-        .removeClass 'right-sidebar-expanded'
-        .addClass 'right-sidebar-collapsed'
-    else
-      $('.js-build-sidebar')
-        .removeClass 'right-sidebar-collapsed'
-        .addClass 'right-sidebar-expanded'
-
-  updateArtifactRemoveDate: ->
-    $date = $('.js-artifacts-remove')
-
-    if $date.length
-      date = $date.text()
-      $date.text $.timefor(new Date(date), ' ')
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
new file mode 100644
index 0000000000000000000000000000000000000000..3d9b824d40618af5a509fe56f1653576e5029bcd
--- /dev/null
+++ b/app/assets/javascripts/build.js
@@ -0,0 +1,139 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Build = (function() {
+    Build.interval = null;
+
+    Build.state = null;
+
+    function Build(page_url, build_url, build_status, state1) {
+      this.page_url = page_url;
+      this.build_url = build_url;
+      this.build_status = build_status;
+      this.state = state1;
+      this.hideSidebar = bind(this.hideSidebar, this);
+      this.toggleSidebar = bind(this.toggleSidebar, this);
+      clearInterval(Build.interval);
+      this.bp = Breakpoints.get();
+      this.hideSidebar();
+      $('.js-build-sidebar').niceScroll();
+      $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
+      $(window).off('resize.build').on('resize.build', this.hideSidebar);
+      this.updateArtifactRemoveDate();
+      if ($('#build-trace').length) {
+        this.getInitialBuildTrace();
+        this.initScrollButtonAffix();
+      }
+      if (this.build_status === "running" || this.build_status === "pending") {
+        $('#autoscroll-button').on('click', function() {
+          var state;
+          state = $(this).data("state");
+          if ("enabled" === state) {
+            $(this).data("state", "disabled");
+            return $(this).text("enable autoscroll");
+          } else {
+            $(this).data("state", "enabled");
+            return $(this).text("disable autoscroll");
+          }
+        });
+        Build.interval = setInterval((function(_this) {
+          return function() {
+            if (window.location.href.split("#").first() === _this.page_url) {
+              return _this.getBuildTrace();
+            }
+          };
+        })(this), 4000);
+      }
+    }
+
+    Build.prototype.getInitialBuildTrace = function() {
+      return $.ajax({
+        url: this.build_url,
+        dataType: 'json',
+        success: function(build_data) {
+          $('.js-build-output').html(build_data.trace_html);
+          if (build_data.status === 'success' || build_data.status === 'failed') {
+            return $('.js-build-refresh').remove();
+          }
+        }
+      });
+    };
+
+    Build.prototype.getBuildTrace = function() {
+      return $.ajax({
+        url: this.page_url + "/trace.json?state=" + (encodeURIComponent(this.state)),
+        dataType: "json",
+        success: (function(_this) {
+          return function(log) {
+            if (log.state) {
+              _this.state = log.state;
+            }
+            if (log.status === "running") {
+              if (log.append) {
+                $('.js-build-output').append(log.html);
+              } else {
+                $('.js-build-output').html(log.html);
+              }
+              return _this.checkAutoscroll();
+            } else if (log.status !== _this.build_status) {
+              return Turbolinks.visit(_this.page_url);
+            }
+          };
+        })(this)
+      });
+    };
+
+    Build.prototype.checkAutoscroll = function() {
+      if ("enabled" === $("#autoscroll-button").data("state")) {
+        return $("html,body").scrollTop($("#build-trace").height());
+      }
+    };
+
+    Build.prototype.initScrollButtonAffix = function() {
+      var $body, $buildScroll, $buildTrace;
+      $buildScroll = $('#js-build-scroll');
+      $body = $('body');
+      $buildTrace = $('#build-trace');
+      return $buildScroll.affix({
+        offset: {
+          bottom: function() {
+            return $body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top);
+          }
+        }
+      });
+    };
+
+    Build.prototype.shouldHideSidebar = function() {
+      var bootstrapBreakpoint;
+      bootstrapBreakpoint = this.bp.getBreakpointSize();
+      return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+    };
+
+    Build.prototype.toggleSidebar = function() {
+      if (this.shouldHideSidebar()) {
+        return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed');
+      }
+    };
+
+    Build.prototype.hideSidebar = function() {
+      if (this.shouldHideSidebar()) {
+        return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+      } else {
+        return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+      }
+    };
+
+    Build.prototype.updateArtifactRemoveDate = function() {
+      var $date, date;
+      $date = $('.js-artifacts-remove');
+      if ($date.length) {
+        date = $date.text();
+        return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' '));
+      }
+    };
+
+    return Build;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
new file mode 100644
index 0000000000000000000000000000000000000000..f345ba0abe624892d8ffd0b700d20a531e0a1e83
--- /dev/null
+++ b/app/assets/javascripts/build_artifacts.js
@@ -0,0 +1,27 @@
+(function() {
+  this.BuildArtifacts = (function() {
+    function BuildArtifacts() {
+      this.disablePropagation();
+      this.setupEntryClick();
+    }
+
+    BuildArtifacts.prototype.disablePropagation = function() {
+      $('.top-block').on('click', '.download', function(e) {
+        return e.stopPropagation();
+      });
+      return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
+        return e.stopImmediatePropagation();
+      });
+    };
+
+    BuildArtifacts.prototype.setupEntryClick = function() {
+      return $('.tree-holder').on('click', 'tr[data-link]', function(e) {
+        return window.location = this.dataset.link;
+      });
+    };
+
+    return BuildArtifacts;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/build_artifacts.js.coffee b/app/assets/javascripts/build_artifacts.js.coffee
deleted file mode 100644
index 5ae6cba56c8ee020e69da29806799b3b25569482..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/build_artifacts.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-class @BuildArtifacts
-  constructor: () ->
-    @disablePropagation()
-    @setupEntryClick()
-
-  disablePropagation: ->
-    $('.top-block').on 'click', '.download',  (e) ->
-      e.stopPropagation()
-    $('.tree-holder').on 'click', 'tr[data-link] a', (e) ->
-      e.stopImmediatePropagation()
-
-  setupEntryClick: ->
-    $('.tree-holder').on 'click', 'tr[data-link]', (e) ->
-      window.location = @dataset.link
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
new file mode 100644
index 0000000000000000000000000000000000000000..23cf5b519f457f438ef29218223378eef66ce649
--- /dev/null
+++ b/app/assets/javascripts/commit.js
@@ -0,0 +1,13 @@
+(function() {
+  this.Commit = (function() {
+    function Commit() {
+      $('.files .diff-file').each(function() {
+        return new CommitFile(this);
+      });
+    }
+
+    return Commit;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
deleted file mode 100644
index 0566e239191e2a57ff6628c69ee3d6f5d8d55b16..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commit.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @Commit
-  constructor: ->
-    $('.files .diff-file').each ->
-      new CommitFile(this)
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
new file mode 100644
index 0000000000000000000000000000000000000000..be24ee56aad57e37b6af5373deb1a9a8e2013973
--- /dev/null
+++ b/app/assets/javascripts/commit/file.js
@@ -0,0 +1,13 @@
+(function() {
+  this.CommitFile = (function() {
+    function CommitFile(file) {
+      if ($('.image', file).length) {
+        new ImageFile(file);
+      }
+    }
+
+    return CommitFile;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
deleted file mode 100644
index 83e793863b60623e06d5866d94ca54f2b958608a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commit/file.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @CommitFile
-
-  constructor: (file) ->
-    if $('.image', file).length
-      new ImageFile(file)
diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js
new file mode 100644
index 0000000000000000000000000000000000000000..c0d0b2d049fae7feb9eeb7ce431df914a518a6ac
--- /dev/null
+++ b/app/assets/javascripts/commit/image-file.js
@@ -0,0 +1,175 @@
+(function() {
+  this.ImageFile = (function() {
+    var prepareFrames;
+
+    ImageFile.availWidth = 900;
+
+    ImageFile.viewModes = ['two-up', 'swipe'];
+
+    function ImageFile(file) {
+      this.file = file;
+      this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
+        return function(deletedWidth, deletedHeight) {
+          return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
+            if (width === deletedWidth && height === deletedHeight) {
+              return _this.initViewModes();
+            } else {
+              return _this.initView('two-up');
+            }
+          });
+        };
+      })(this));
+    }
+
+    ImageFile.prototype.initViewModes = function() {
+      var viewMode;
+      viewMode = ImageFile.viewModes[0];
+      $('.view-modes', this.file).removeClass('hide');
+      $('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
+        return function(event) {
+          if (!$(event.currentTarget).hasClass('active')) {
+            return _this.activateViewMode(event.currentTarget.className);
+          }
+        };
+      })(this));
+      return this.activateViewMode(viewMode);
+    };
+
+    ImageFile.prototype.activateViewMode = function(viewMode) {
+      $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
+      return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
+        return function() {
+          $(".view." + viewMode, _this.file).fadeIn(200);
+          return _this.initView(viewMode);
+        };
+      })(this));
+    };
+
+    ImageFile.prototype.initView = function(viewMode) {
+      return this.views[viewMode].call(this);
+    };
+
+    prepareFrames = function(view) {
+      var maxHeight, maxWidth;
+      maxWidth = 0;
+      maxHeight = 0;
+      $('.frame', view).each((function(_this) {
+        return function(index, frame) {
+          var height, width;
+          width = $(frame).width();
+          height = $(frame).height();
+          maxWidth = width > maxWidth ? width : maxWidth;
+          return maxHeight = height > maxHeight ? height : maxHeight;
+        };
+      })(this)).css({
+        width: maxWidth,
+        height: maxHeight
+      });
+      return [maxWidth, maxHeight];
+    };
+
+    ImageFile.prototype.views = {
+      'two-up': function() {
+        return $('.two-up.view .wrap', this.file).each((function(_this) {
+          return function(index, wrap) {
+            $('img', wrap).each(function() {
+              var currentWidth;
+              currentWidth = $(this).width();
+              if (currentWidth > ImageFile.availWidth / 2) {
+                return $(this).width(ImageFile.availWidth / 2);
+              }
+            });
+            return _this.requestImageInfo($('img', wrap), function(width, height) {
+              $('.image-info .meta-width', wrap).text(width + "px");
+              $('.image-info .meta-height', wrap).text(height + "px");
+              return $('.image-info', wrap).removeClass('hide');
+            });
+          };
+        })(this));
+      },
+      'swipe': function() {
+        var maxHeight, maxWidth;
+        maxWidth = 0;
+        maxHeight = 0;
+        return $('.swipe.view', this.file).each((function(_this) {
+          return function(index, view) {
+            var ref;
+            ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+            $('.swipe-frame', view).css({
+              width: maxWidth + 16,
+              height: maxHeight + 28
+            });
+            $('.swipe-wrap', view).css({
+              width: maxWidth + 1,
+              height: maxHeight + 2
+            });
+            return $('.swipe-bar', view).css({
+              left: 0
+            }).draggable({
+              axis: 'x',
+              containment: 'parent',
+              drag: function(event) {
+                return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+              },
+              stop: function(event) {
+                return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+              }
+            });
+          };
+        })(this));
+      },
+      'onion-skin': function() {
+        var dragTrackWidth, maxHeight, maxWidth;
+        maxWidth = 0;
+        maxHeight = 0;
+        dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
+        return $('.onion-skin.view', this.file).each((function(_this) {
+          return function(index, view) {
+            var ref;
+            ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
+            $('.onion-skin-frame', view).css({
+              width: maxWidth + 16,
+              height: maxHeight + 28
+            });
+            $('.swipe-wrap', view).css({
+              width: maxWidth + 1,
+              height: maxHeight + 2
+            });
+            return $('.dragger', view).css({
+              left: dragTrackWidth
+            }).draggable({
+              axis: 'x',
+              containment: 'parent',
+              drag: function(event) {
+                return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+              },
+              stop: function(event) {
+                return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+              }
+            });
+          };
+        })(this));
+      }
+    };
+
+    ImageFile.prototype.requestImageInfo = function(img, callback) {
+      var domImg;
+      domImg = img.get(0);
+      if (domImg) {
+        if (domImg.complete) {
+          return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
+        } else {
+          return img.on('load', (function(_this) {
+            return function() {
+              return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
+            };
+          })(this));
+        }
+      }
+    };
+
+    return ImageFile;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
deleted file mode 100644
index 9c723f51e5405512968647934bbebc0405516f77..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ /dev/null
@@ -1,127 +0,0 @@
-class @ImageFile
-
-  # Width where images must fits in, for 2-up this gets divided by 2
-  @availWidth = 900
-  @viewModes = ['two-up', 'swipe']
-
-  constructor: (@file) ->
-    # Determine if old and new file has same dimensions, if not show 'two-up' view
-    this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) =>
-      this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) =>
-        if width == deletedWidth && height == deletedHeight
-          this.initViewModes()
-        else
-          this.initView('two-up')
-
-  initViewModes: ->
-    viewMode = ImageFile.viewModes[0]
-
-    $('.view-modes', @file).removeClass 'hide'
-    $('.view-modes-menu', @file).on 'click', 'li', (event) =>
-      unless $(event.currentTarget).hasClass('active')
-        this.activateViewMode(event.currentTarget.className)
-
-    this.activateViewMode(viewMode)
-
-  activateViewMode: (viewMode) ->
-    $('.view-modes-menu li', @file)
-      .removeClass('active')
-      .filter(".#{viewMode}").addClass 'active'
-    $(".view:visible:not(.#{viewMode})", @file).fadeOut 200, =>
-      $(".view.#{viewMode}", @file).fadeIn(200)
-      this.initView viewMode
-
-  initView: (viewMode) ->
-    this.views[viewMode].call(this)
-
-  prepareFrames = (view) ->
-    maxWidth = 0
-    maxHeight = 0
-    $('.frame', view).each (index, frame) =>
-      width = $(frame).width()
-      height = $(frame).height()
-      maxWidth = if width > maxWidth then width else maxWidth
-      maxHeight = if height > maxHeight then height else maxHeight
-    .css
-      width: maxWidth
-      height: maxHeight
-    
-    [maxWidth, maxHeight]
-
-  views: 
-    'two-up': ->
-      $('.two-up.view .wrap', @file).each (index, wrap) =>
-        $('img', wrap).each ->
-          currentWidth = $(this).width()
-          if currentWidth > ImageFile.availWidth / 2
-            $(this).width ImageFile.availWidth / 2
-
-        this.requestImageInfo $('img', wrap), (width, height) ->
-          $('.image-info .meta-width', wrap).text "#{width}px"
-          $('.image-info .meta-height', wrap).text "#{height}px"
-          $('.image-info', wrap).removeClass('hide')
-
-    'swipe': ->
-      maxWidth = 0
-      maxHeight = 0
-
-      $('.swipe.view', @file).each (index, view) =>
-
-        [maxWidth, maxHeight] = prepareFrames(view)
-
-        $('.swipe-frame', view).css
-          width: maxWidth + 16
-          height: maxHeight + 28
-
-        $('.swipe-wrap', view).css
-          width: maxWidth + 1
-          height: maxHeight + 2
-
-        $('.swipe-bar', view).css
-          left: 0
-        .draggable
-          axis: 'x'
-          containment: 'parent'
-          drag: (event) ->
-            $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
-          stop: (event) ->
-            $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
-
-    'onion-skin': ->
-      maxWidth = 0
-      maxHeight = 0
-
-      dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width()
-
-      $('.onion-skin.view', @file).each (index, view) =>
-
-        [maxWidth, maxHeight] = prepareFrames(view)
-
-        $('.onion-skin-frame', view).css
-          width: maxWidth + 16
-          height: maxHeight + 28
-
-        $('.swipe-wrap', view).css
-          width: maxWidth + 1
-          height: maxHeight + 2
-        
-        $('.dragger', view).css
-          left: dragTrackWidth
-        .draggable
-          axis: 'x'
-          containment: 'parent'
-          drag: (event) ->
-            $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
-          stop: (event) ->
-            $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
-        
-      
-
-  requestImageInfo: (img, callback) ->
-    domImg = img.get(0)
-    if domImg
-      if domImg.complete
-        callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
-      else
-        img.on 'load', =>
-          callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
new file mode 100644
index 0000000000000000000000000000000000000000..37f168c51902ba2b5b01efb0614815e5fdd3a166
--- /dev/null
+++ b/app/assets/javascripts/commits.js
@@ -0,0 +1,58 @@
+(function() {
+  this.CommitsList = (function() {
+    function CommitsList() {}
+
+    CommitsList.timer = null;
+
+    CommitsList.init = function(limit) {
+      $("body").on("click", ".day-commits-table li.commit", function(event) {
+        if (event.target.nodeName !== "A") {
+          location.href = $(this).attr("url");
+          e.stopPropagation();
+          return false;
+        }
+      });
+      Pager.init(limit, false);
+      this.content = $("#commits-list");
+      this.searchField = $("#commits-search");
+      return this.initSearch();
+    };
+
+    CommitsList.initSearch = function() {
+      this.timer = null;
+      return this.searchField.keyup((function(_this) {
+        return function() {
+          clearTimeout(_this.timer);
+          return _this.timer = setTimeout(_this.filterResults, 500);
+        };
+      })(this));
+    };
+
+    CommitsList.filterResults = function() {
+      var commitsUrl, form, search;
+      form = $(".commits-search-form");
+      search = CommitsList.searchField.val();
+      commitsUrl = form.attr("action") + '?' + form.serialize();
+      CommitsList.content.fadeTo('fast', 0.5);
+      return $.ajax({
+        type: "GET",
+        url: form.attr("action"),
+        data: form.serialize(),
+        complete: function() {
+          return CommitsList.content.fadeTo('fast', 1.0);
+        },
+        success: function(data) {
+          CommitsList.content.html(data.html);
+          return history.replaceState({
+            page: commitsUrl
+          }, document.title, commitsUrl);
+        },
+        dataType: "json"
+      });
+    };
+
+    return CommitsList;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
deleted file mode 100644
index 0acb4c1955ecc484a9c5a3c5d5dd0f98b0e3c5b5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/commits.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-class @CommitsList
-  @timer = null
-
-  @init: (limit) ->
-    $("body").on "click", ".day-commits-table li.commit", (event) ->
-      if event.target.nodeName != "A"
-        location.href = $(this).attr("url")
-        e.stopPropagation()
-        return false
-
-    Pager.init limit, false
-
-    @content = $("#commits-list")
-    @searchField = $("#commits-search")
-    @initSearch()
-
-  @initSearch: ->
-    @timer = null
-    @searchField.keyup =>
-      clearTimeout(@timer)
-      @timer = setTimeout(@filterResults, 500)
-
-  @filterResults: =>
-    form = $(".commits-search-form")
-    search = @searchField.val()
-    commitsUrl = form.attr("action") + '?' + form.serialize()
-    @content.fadeTo('fast', 0.5)
-
-    $.ajax
-      type: "GET"
-      url: form.attr("action")
-      data: form.serialize()
-      complete: =>
-        @content.fadeTo('fast', 1.0)
-      success: (data) =>
-        @content.html(data.html)
-        # Change url so if user reload a page - search results are saved
-        history.replaceState {page: commitsUrl}, document.title, commitsUrl
-      dataType: "json"
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
new file mode 100644
index 0000000000000000000000000000000000000000..342ac0e8e69d0279811f302aba81f6ef184db15f
--- /dev/null
+++ b/app/assets/javascripts/compare.js
@@ -0,0 +1,91 @@
+(function() {
+  this.Compare = (function() {
+    function Compare(opts) {
+      this.opts = opts;
+      this.source_loading = $(".js-source-loading");
+      this.target_loading = $(".js-target-loading");
+      $('.js-compare-dropdown').each((function(_this) {
+        return function(i, dropdown) {
+          var $dropdown;
+          $dropdown = $(dropdown);
+          return $dropdown.glDropdown({
+            selectable: true,
+            fieldName: $dropdown.data('field-name'),
+            filterable: true,
+            id: function(obj, $el) {
+              return $el.data('id');
+            },
+            toggleLabel: function(obj, $el) {
+              return $el.text().trim();
+            },
+            clicked: function(e, el) {
+              if ($dropdown.is('.js-target-branch')) {
+                return _this.getTargetHtml();
+              } else if ($dropdown.is('.js-source-branch')) {
+                return _this.getSourceHtml();
+              } else if ($dropdown.is('.js-target-project')) {
+                return _this.getTargetProject();
+              }
+            }
+          });
+        };
+      })(this));
+      this.initialState();
+    }
+
+    Compare.prototype.initialState = function() {
+      this.getSourceHtml();
+      return this.getTargetHtml();
+    };
+
+    Compare.prototype.getTargetProject = function() {
+      return $.ajax({
+        url: this.opts.targetProjectUrl,
+        data: {
+          target_project_id: $("input[name='merge_request[target_project_id]']").val()
+        },
+        beforeSend: function() {
+          return $('.mr_target_commit').empty();
+        },
+        success: function(html) {
+          return $('.js-target-branch-dropdown .dropdown-content').html(html);
+        }
+      });
+    };
+
+    Compare.prototype.getSourceHtml = function() {
+      return this.sendAjax(this.opts.sourceBranchUrl, this.source_loading, '.mr_source_commit', {
+        ref: $("input[name='merge_request[source_branch]']").val()
+      });
+    };
+
+    Compare.prototype.getTargetHtml = function() {
+      return this.sendAjax(this.opts.targetBranchUrl, this.target_loading, '.mr_target_commit', {
+        target_project_id: $("input[name='merge_request[target_project_id]']").val(),
+        ref: $("input[name='merge_request[target_branch]']").val()
+      });
+    };
+
+    Compare.prototype.sendAjax = function(url, loading, target, data) {
+      var $target;
+      $target = $(target);
+      return $.ajax({
+        url: url,
+        data: data,
+        beforeSend: function() {
+          loading.show();
+          return $target.empty();
+        },
+        success: function(html) {
+          loading.hide();
+          $target.html(html);
+          return $('.js-timeago', $target).timeago();
+        }
+      });
+    };
+
+    return Compare;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee
deleted file mode 100644
index f20992ead3ec6851819c5564499bcb08f87a95bf..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/compare.js.coffee
+++ /dev/null
@@ -1,67 +0,0 @@
-class @Compare
-  constructor: (@opts) ->
-    @source_loading = $ ".js-source-loading"
-    @target_loading = $ ".js-target-loading"
-
-    $('.js-compare-dropdown').each (i, dropdown) =>
-      $dropdown = $(dropdown)
-
-      $dropdown.glDropdown(
-        selectable: true
-        fieldName: $dropdown.data 'field-name'
-        filterable: true
-        id: (obj, $el) ->
-          $el.data 'id'
-        toggleLabel: (obj, $el) ->
-          $el.text().trim()
-        clicked: (e, el) =>
-          if $dropdown.is '.js-target-branch'
-            @getTargetHtml()
-          else if $dropdown.is '.js-source-branch'
-            @getSourceHtml()
-          else if $dropdown.is '.js-target-project'
-            @getTargetProject()
-      )
-
-    @initialState()
-
-  initialState: ->
-    @getSourceHtml()
-    @getTargetHtml()
-
-  getTargetProject: ->
-    $.ajax(
-      url: @opts.targetProjectUrl
-      data:
-        target_project_id:  $("input[name='merge_request[target_project_id]']").val()
-      beforeSend: ->
-        $('.mr_target_commit').empty()
-      success: (html) ->
-        $('.js-target-branch-dropdown .dropdown-content').html html
-    )
-
-  getSourceHtml: ->
-    @sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
-      ref: $("input[name='merge_request[source_branch]']").val()
-    )
-
-  getTargetHtml: ->
-    @sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
-      target_project_id: $("input[name='merge_request[target_project_id]']").val()
-      ref: $("input[name='merge_request[target_branch]']").val()
-    )
-
-  sendAjax: (url, loading, target, data) ->
-    $target = $(target)
-
-    $.ajax(
-      url: url
-      data: data
-      beforeSend: ->
-        loading.show()
-        $target.empty()
-      success: (html) ->
-        loading.hide()
-        $target.html html
-        $('.js-timeago', $target).timeago()
-    )
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e3a28cd163ba9b299e7e984066eccf4335d05f4
--- /dev/null
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -0,0 +1,51 @@
+(function() {
+  this.CompareAutocomplete = (function() {
+    function CompareAutocomplete() {
+      this.initDropdown();
+    }
+
+    CompareAutocomplete.prototype.initDropdown = function() {
+      return $('.js-compare-dropdown').each(function() {
+        var $dropdown, selected;
+        $dropdown = $(this);
+        selected = $dropdown.data('selected');
+        return $dropdown.glDropdown({
+          data: function(term, callback) {
+            return $.ajax({
+              url: $dropdown.data('refs-url'),
+              data: {
+                ref: $dropdown.data('ref')
+              }
+            }).done(function(refs) {
+              return callback(refs);
+            });
+          },
+          selectable: true,
+          filterable: true,
+          filterByText: true,
+          fieldName: $dropdown.attr('name'),
+          filterInput: 'input[type="text"]',
+          renderRow: function(ref) {
+            var link;
+            if (ref.header != null) {
+              return $('<li />').addClass('dropdown-header').text(ref.header);
+            } else {
+              link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+              return $('<li />').append(link);
+            }
+          },
+          id: function(obj, $el) {
+            return $el.attr('data-ref');
+          },
+          toggleLabel: function(obj, $el) {
+            return $el.text().trim();
+          }
+        });
+      });
+    };
+
+    return CompareAutocomplete;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/compare_autocomplete.js.coffee b/app/assets/javascripts/compare_autocomplete.js.coffee
deleted file mode 100644
index 7ad9fd9763702af7cef72080d1d0a89f9a51e9bb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/compare_autocomplete.js.coffee
+++ /dev/null
@@ -1,41 +0,0 @@
-class @CompareAutocomplete
-  constructor: ->
-    @initDropdown()
-
-  initDropdown: ->
-    $('.js-compare-dropdown').each ->
-      $dropdown = $(@)
-      selected = $dropdown.data('selected')
-
-      $dropdown.glDropdown(
-        data: (term, callback) ->
-          $.ajax(
-            url: $dropdown.data('refs-url')
-            data:
-              ref: $dropdown.data('ref')
-          ).done (refs) ->
-            callback(refs)
-        selectable: true
-        filterable: true
-        filterByText: true
-        fieldName: $dropdown.attr('name')
-        filterInput: 'input[type="text"]'
-        renderRow: (ref) ->
-          if ref.header?
-            $('<li />')
-              .addClass('dropdown-header')
-              .text(ref.header)
-          else
-            link = $('<a />')
-              .attr('href', '#')
-              .addClass(if ref is selected then 'is-active' else '')
-              .text(ref)
-              .attr('data-ref', escape(ref))
-
-            $('<li />')
-              .append(link)
-        id: (obj, $el) ->
-          $el.attr('data-ref')
-        toggleLabel: (obj, $el) ->
-          $el.text().trim()
-      )
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
new file mode 100644
index 0000000000000000000000000000000000000000..708ab08ffacbd083449289d684fa3f7b573bd657
--- /dev/null
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -0,0 +1,32 @@
+(function() {
+  this.ConfirmDangerModal = (function() {
+    function ConfirmDangerModal(form, text) {
+      var project_path, submit;
+      this.form = form;
+      $('.js-confirm-text').text(text || '');
+      $('.js-confirm-danger-input').val('');
+      $('#modal-confirm-danger').modal('show');
+      project_path = $('.js-confirm-danger-match').text();
+      submit = $('.js-confirm-danger-submit');
+      submit.disable();
+      $('.js-confirm-danger-input').off('input');
+      $('.js-confirm-danger-input').on('input', function() {
+        if (rstrip($(this).val()) === project_path) {
+          return submit.enable();
+        } else {
+          return submit.disable();
+        }
+      });
+      $('.js-confirm-danger-submit').off('click');
+      $('.js-confirm-danger-submit').on('click', (function(_this) {
+        return function() {
+          return _this.form.submit();
+        };
+      })(this));
+    }
+
+    return ConfirmDangerModal;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
deleted file mode 100644
index 66e34dd4a088e783dbe69966d56ac5582d37c2fa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/confirm_danger_modal.js.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-class @ConfirmDangerModal
-  constructor: (form, text) ->
-    @form = form
-    $('.js-confirm-text').text(text || '')
-    $('.js-confirm-danger-input').val('')
-    $('#modal-confirm-danger').modal('show')
-    project_path = $('.js-confirm-danger-match').text()
-    submit = $('.js-confirm-danger-submit')
-    submit.disable()
-
-    $('.js-confirm-danger-input').off 'input'
-    $('.js-confirm-danger-input').on 'input', ->
-      if rstrip($(@).val()) is project_path
-        submit.enable()
-      else
-        submit.disable()
-
-    $('.js-confirm-danger-submit').off 'click'
-    $('.js-confirm-danger-submit').on 'click', =>
-      @form.submit()
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..c82798cc6a58b079e268969b646db5dffedba3b2
--- /dev/null
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -0,0 +1,42 @@
+
+/*= require clipboard */
+
+(function() {
+  var genericError, genericSuccess, showTooltip;
+
+  genericSuccess = function(e) {
+    showTooltip(e.trigger, 'Copied!');
+    e.clearSelection();
+    return $(e.trigger).blur();
+  };
+
+  genericError = function(e) {
+    var key;
+    if (/Mac/i.test(navigator.userAgent)) {
+      key = '&#8984;';
+    } else {
+      key = 'Ctrl';
+    }
+    return showTooltip(e.trigger, "Press " + key + "-C to copy");
+  };
+
+  showTooltip = function(target, title) {
+    return $(target).tooltip({
+      container: 'body',
+      html: 'true',
+      placement: 'auto bottom',
+      title: title,
+      trigger: 'manual'
+    }).tooltip('show').one('mouseleave', function() {
+      return $(this).tooltip('hide');
+    });
+  };
+
+  $(function() {
+    var clipboard;
+    clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
+    clipboard.on('success', genericSuccess);
+    return clipboard.on('error', genericError);
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
deleted file mode 100644
index 24301e01b10de7394f68498b8f1c1627274acba3..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-#= require clipboard
-
-genericSuccess = (e) ->
-  showTooltip(e.trigger, 'Copied!')
-
-  # Clear the selection and blur the trigger so it loses its border
-  e.clearSelection()
-  $(e.trigger).blur()
-
-# Safari doesn't support `execCommand`, so instead we inform the user to
-# copy manually.
-#
-# See http://clipboardjs.com/#browser-support
-genericError = (e) ->
-  if /Mac/i.test(navigator.userAgent)
-    key = '&#8984;' # Command
-  else
-    key = 'Ctrl'
-
-  showTooltip(e.trigger, "Press #{key}-C to copy")
-
-showTooltip = (target, title) ->
-  $(target).
-    tooltip(
-      container: 'body'
-      html: 'true'
-      placement: 'auto bottom'
-      title: title
-      trigger: 'manual'
-    ).
-    tooltip('show').
-    one('mouseleave', -> $(this).tooltip('hide'))
-
-$ ->
-  clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]'
-  clipboard.on 'success', genericSuccess
-  clipboard.on 'error',   genericError
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
new file mode 100644
index 0000000000000000000000000000000000000000..298f3852085da45dd299b2cbd05ed6fa398502cc
--- /dev/null
+++ b/app/assets/javascripts/diff.js
@@ -0,0 +1,77 @@
+(function() {
+  this.Diff = (function() {
+    var UNFOLD_COUNT;
+
+    UNFOLD_COUNT = 20;
+
+    function Diff() {
+      $('.files .diff-file').singleFileDiff();
+      this.filesCommentButton = $('.files .diff-file').filesCommentButton();
+      $(document).off('click', '.js-unfold');
+      $(document).on('click', '.js-unfold', (function(_this) {
+        return function(event) {
+          var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
+          target = $(event.target);
+          unfoldBottom = target.hasClass('js-unfold-bottom');
+          unfold = true;
+          ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1];
+          offset = line_number - old_line;
+          if (unfoldBottom) {
+            line_number += 1;
+            since = line_number;
+            to = line_number + UNFOLD_COUNT;
+          } else {
+            ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1];
+            line_number -= 1;
+            to = line_number;
+            if (line_number - UNFOLD_COUNT > prev_new_line + 1) {
+              since = line_number - UNFOLD_COUNT;
+            } else {
+              since = prev_new_line + 1;
+              unfold = false;
+            }
+          }
+          link = target.parents('.diff-file').attr('data-blob-diff-path');
+          params = {
+            since: since,
+            to: to,
+            bottom: unfoldBottom,
+            offset: offset,
+            unfold: unfold,
+            indent: 1
+          };
+          return $.get(link, params, function(response) {
+            return target.parent().replaceWith(response);
+          });
+        };
+      })(this));
+    }
+
+    Diff.prototype.lineNumbers = function(line) {
+      var i, l, len, line_number, line_numbers, lines, results;
+      if (!line.children().length) {
+        return [0, 0];
+      }
+      lines = line.children().slice(0, 2);
+      line_numbers = (function() {
+        var i, len, results;
+        results = [];
+        for (i = 0, len = lines.length; i < len; i++) {
+          l = lines[i];
+          results.push($(l).attr('data-linenumber'));
+        }
+        return results;
+      })();
+      results = [];
+      for (i = 0, len = line_numbers.length; i < len; i++) {
+        line_number = line_numbers[i];
+        results.push(parseInt(line_number));
+      }
+      return results;
+    };
+
+    return Diff;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
deleted file mode 100644
index c132cc8c542d2c54783c3808b4b054362065718e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/diff.js.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-class @Diff
-  UNFOLD_COUNT = 20
-  constructor: ->
-    $('.files .diff-file').singleFileDiff()
-    @filesCommentButton = $('.files .diff-file').filesCommentButton()
-
-    $(document).off('click', '.js-unfold')
-    $(document).on('click', '.js-unfold', (event) =>
-      target = $(event.target)
-      unfoldBottom = target.hasClass('js-unfold-bottom')
-      unfold = true
-
-      [old_line, line_number] = @lineNumbers(target.parent())
-      offset = line_number - old_line
-
-      if unfoldBottom
-        line_number += 1
-        since = line_number
-        to = line_number + UNFOLD_COUNT
-      else
-        [prev_old_line, prev_new_line] = @lineNumbers(target.parent().prev())
-        line_number -= 1
-        to = line_number
-        if line_number - UNFOLD_COUNT > prev_new_line + 1
-          since = line_number - UNFOLD_COUNT
-        else
-          since = prev_new_line + 1
-          unfold = false
-
-      link = target.parents('.diff-file').attr('data-blob-diff-path')
-      params =
-        since: since
-        to: to
-        bottom: unfoldBottom
-        offset: offset
-        unfold: unfold
-        # indent is used to compensate for single space indent to fit
-        # '+' and '-' prepended to diff lines,
-        # see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
-        indent: 1
-
-      $.get(link, params, (response) ->
-        target.parent().replaceWith(response)
-      )
-    )
-
-  lineNumbers: (line) ->
-    return ([0, 0]) unless line.children().length
-    lines = line.children().slice(0, 2)
-    line_numbers = ($(l).attr('data-linenumber') for l in lines)
-    (parseInt(line_number) for line_number in line_numbers)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e6901962c6b3354581bd38491048c64bc70331a
--- /dev/null
+++ b/app/assets/javascripts/dispatcher.js
@@ -0,0 +1,260 @@
+(function() {
+  var Dispatcher;
+
+  $(function() {
+    return new Dispatcher();
+  });
+
+  Dispatcher = (function() {
+    function Dispatcher() {
+      this.initSearch();
+      this.initPageScripts();
+    }
+
+    Dispatcher.prototype.initPageScripts = function() {
+      var page, path, shortcut_handler;
+      page = $('body').attr('data-page');
+      if (!page) {
+        return false;
+      }
+      path = page.split(':');
+      shortcut_handler = null;
+      switch (page) {
+        case 'projects:issues:index':
+          Issuable.init();
+          new IssuableBulkActions();
+          shortcut_handler = new ShortcutsNavigation();
+          break;
+        case 'projects:issues:show':
+          new Issue();
+          shortcut_handler = new ShortcutsIssuable();
+          new ZenMode();
+          break;
+        case 'projects:milestones:show':
+        case 'groups:milestones:show':
+        case 'dashboard:milestones:show':
+          new Milestone();
+          break;
+        case 'dashboard:todos:index':
+          new Todos();
+          break;
+        case 'projects:milestones:new':
+        case 'projects:milestones:edit':
+          new ZenMode();
+          new DueDateSelect();
+          new GLForm($('.milestone-form'));
+          break;
+        case 'groups:milestones:new':
+          new ZenMode();
+          break;
+        case 'projects:compare:show':
+          new Diff();
+          break;
+        case 'projects:issues:new':
+        case 'projects:issues:edit':
+          shortcut_handler = new ShortcutsNavigation();
+          new GLForm($('.issue-form'));
+          new IssuableForm($('.issue-form'));
+          break;
+        case 'projects:merge_requests:new':
+        case 'projects:merge_requests:edit':
+          new Diff();
+          shortcut_handler = new ShortcutsNavigation();
+          new GLForm($('.merge-request-form'));
+          new IssuableForm($('.merge-request-form'));
+          break;
+        case 'projects:tags:new':
+          new ZenMode();
+          new GLForm($('.tag-form'));
+          break;
+        case 'projects:releases:edit':
+          new ZenMode();
+          new GLForm($('.release-form'));
+          break;
+        case 'projects:merge_requests:show':
+          new Diff();
+          shortcut_handler = new ShortcutsIssuable(true);
+          new ZenMode();
+          new MergedButtons();
+          break;
+        case 'projects:merge_requests:commits':
+        case 'projects:merge_requests:builds':
+          new MergedButtons();
+          break;
+        case "projects:merge_requests:diffs":
+          new Diff();
+          new ZenMode();
+          new MergedButtons();
+          break;
+        case 'projects:merge_requests:index':
+          shortcut_handler = new ShortcutsNavigation();
+          Issuable.init();
+          break;
+        case 'dashboard:activity':
+          new Activities();
+          break;
+        case 'dashboard:projects:starred':
+          new Activities();
+          break;
+        case 'projects:commit:show':
+          new Commit();
+          new Diff();
+          new ZenMode();
+          shortcut_handler = new ShortcutsNavigation();
+          break;
+        case 'projects:commits:show':
+        case 'projects:activity':
+          shortcut_handler = new ShortcutsNavigation();
+          break;
+        case 'projects:show':
+          shortcut_handler = new ShortcutsNavigation();
+          new NotificationsForm();
+          if ($('#tree-slider').length) {
+            new TreeView();
+          }
+          break;
+        case 'groups:activity':
+          new Activities();
+          break;
+        case 'groups:show':
+          shortcut_handler = new ShortcutsNavigation();
+          new NotificationsForm();
+          new NotificationsDropdown();
+          break;
+        case 'groups:group_members:index':
+          new GroupMembers();
+          new UsersSelect();
+          break;
+        case 'projects:project_members:index':
+          new ProjectMembers();
+          new UsersSelect();
+          break;
+        case 'groups:new':
+        case 'groups:edit':
+        case 'admin:groups:edit':
+        case 'admin:groups:new':
+          new GroupAvatar();
+          break;
+        case 'projects:tree:show':
+          shortcut_handler = new ShortcutsNavigation();
+          new TreeView();
+          break;
+        case 'projects:find_file:show':
+          shortcut_handler = true;
+          break;
+        case 'projects:blob:show':
+        case 'projects:blame:show':
+          new LineHighlighter();
+          shortcut_handler = new ShortcutsNavigation();
+          new ShortcutsBlob(true);
+          break;
+        case 'projects:labels:new':
+        case 'projects:labels:edit':
+          new Labels();
+          break;
+        case 'projects:labels:index':
+          if ($('.prioritized-labels').length) {
+            new LabelManager();
+          }
+          break;
+        case 'projects:network:show':
+          shortcut_handler = true;
+          break;
+        case 'projects:forks:new':
+          new ProjectFork();
+          break;
+        case 'projects:artifacts:browse':
+          new BuildArtifacts();
+          break;
+        case 'projects:group_links:index':
+          new GroupsSelect();
+          break;
+        case 'search:show':
+          new Search();
+          break;
+        case 'projects:protected_branches:index':
+          new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true);
+          new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false);
+          break;
+      }
+      switch (path.first()) {
+        case 'admin':
+          new Admin();
+          switch (path[1]) {
+            case 'groups':
+              new UsersSelect();
+              break;
+            case 'projects':
+              new NamespaceSelects();
+          }
+          break;
+        case 'dashboard':
+        case 'root':
+          shortcut_handler = new ShortcutsDashboardNavigation();
+          break;
+        case 'profiles':
+          new NotificationsForm();
+          new NotificationsDropdown();
+          break;
+        case 'projects':
+          new Project();
+          new ProjectAvatar();
+          switch (path[1]) {
+            case 'compare':
+              new CompareAutocomplete();
+              break;
+            case 'edit':
+              shortcut_handler = new ShortcutsNavigation();
+              new ProjectNew();
+              break;
+            case 'new':
+              new ProjectNew();
+              break;
+            case 'show':
+              new ProjectNew();
+              new ProjectShow();
+              new NotificationsDropdown();
+              break;
+            case 'wikis':
+              new Wikis();
+              shortcut_handler = new ShortcutsNavigation();
+              new ZenMode();
+              new GLForm($('.wiki-form'));
+              break;
+            case 'snippets':
+              shortcut_handler = new ShortcutsNavigation();
+              if (path[2] === 'show') {
+                new ZenMode();
+              }
+              break;
+            case 'labels':
+            case 'graphs':
+            case 'compare':
+            case 'pipelines':
+            case 'forks':
+            case 'milestones':
+            case 'project_members':
+            case 'deploy_keys':
+            case 'builds':
+            case 'hooks':
+            case 'services':
+            case 'protected_branches':
+              shortcut_handler = new ShortcutsNavigation();
+          }
+      }
+      if (!shortcut_handler) {
+        return new Shortcuts();
+      }
+    };
+
+    Dispatcher.prototype.initSearch = function() {
+      if ($('.search').length) {
+        return new SearchAutocomplete();
+      }
+    };
+
+    return Dispatcher;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
deleted file mode 100644
index afaa6407b05df0222487fa6145c11b899aec9e22..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ /dev/null
@@ -1,171 +0,0 @@
-$ ->
-  new Dispatcher()
-
-class Dispatcher
-  constructor: () ->
-    @initSearch()
-    @initPageScripts()
-
-  initPageScripts: ->
-    page = $('body').attr('data-page')
-
-    unless page
-      return false
-
-    path = page.split(':')
-    shortcut_handler = null
-    switch page
-      when 'projects:issues:index'
-        Issuable.init()
-        new IssuableBulkActions()
-        shortcut_handler = new ShortcutsNavigation()
-      when 'projects:issues:show'
-        new Issue()
-        shortcut_handler = new ShortcutsIssuable()
-        new ZenMode()
-      when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
-        new Milestone()
-      when 'dashboard:todos:index'
-        new Todos()
-      when 'projects:milestones:new', 'projects:milestones:edit'
-        new ZenMode()
-        new DueDateSelect()
-        new GLForm($('.milestone-form'))
-      when 'groups:milestones:new'
-        new ZenMode()
-      when 'projects:compare:show'
-        new Diff()
-      when 'projects:issues:new','projects:issues:edit'
-        shortcut_handler = new ShortcutsNavigation()
-        new GLForm($('.issue-form'))
-        new IssuableForm($('.issue-form'))
-      when 'projects:merge_requests:new', 'projects:merge_requests:edit'
-        new Diff()
-        shortcut_handler = new ShortcutsNavigation()
-        new GLForm($('.merge-request-form'))
-        new IssuableForm($('.merge-request-form'))
-      when 'projects:tags:new'
-        new ZenMode()
-        new GLForm($('.tag-form'))
-      when 'projects:releases:edit'
-        new ZenMode()
-        new GLForm($('.release-form'))
-      when 'projects:merge_requests:show'
-        new Diff()
-        shortcut_handler = new ShortcutsIssuable(true)
-        new ZenMode()
-        new MergedButtons()
-      when 'projects:merge_requests:commits', 'projects:merge_requests:builds'
-        new MergedButtons()
-      when "projects:merge_requests:diffs"
-        new Diff()
-        new ZenMode()
-        new MergedButtons()
-      when 'projects:merge_requests:index'
-        shortcut_handler = new ShortcutsNavigation()
-        Issuable.init()
-      when 'dashboard:activity'
-        new Activities()
-      when 'dashboard:projects:starred'
-        new Activities()
-      when 'projects:commit:show'
-        new Commit()
-        new Diff()
-        new ZenMode()
-        shortcut_handler = new ShortcutsNavigation()
-      when 'projects:commits:show', 'projects:activity'
-        shortcut_handler = new ShortcutsNavigation()
-      when 'projects:show'
-        shortcut_handler = new ShortcutsNavigation()
-
-        new NotificationsForm()
-        new TreeView() if $('#tree-slider').length
-      when 'groups:activity'
-        new Activities()
-      when 'groups:show'
-        shortcut_handler = new ShortcutsNavigation()
-        new NotificationsForm()
-        new NotificationsDropdown()
-      when 'groups:group_members:index'
-        new GroupMembers()
-        new UsersSelect()
-      when 'projects:project_members:index'
-        new ProjectMembers()
-        new UsersSelect()
-      when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
-        new GroupAvatar()
-      when 'projects:tree:show'
-        shortcut_handler = new ShortcutsNavigation()
-        new TreeView()
-      when 'projects:find_file:show'
-        shortcut_handler = true
-      when 'projects:blob:show', 'projects:blame:show'
-        new LineHighlighter()
-        shortcut_handler = new ShortcutsNavigation()
-        new ShortcutsBlob true
-      when 'projects:labels:new', 'projects:labels:edit'
-        new Labels()
-      when 'projects:labels:index'
-        new LabelManager() if $('.prioritized-labels').length
-      when 'projects:network:show'
-        # Ensure we don't create a particular shortcut handler here. This is
-        # already created, where the network graph is created.
-        shortcut_handler = true
-      when 'projects:forks:new'
-        new ProjectFork()
-      when 'projects:artifacts:browse'
-        new BuildArtifacts()
-      when 'projects:group_links:index'
-        new GroupsSelect()
-      when 'search:show'
-        new Search()
-
-    switch path.first()
-      when 'admin'
-        new Admin()
-        switch path[1]
-          when 'groups'
-            new UsersSelect()
-          when 'projects'
-            new NamespaceSelects()
-      when 'dashboard', 'root'
-        shortcut_handler = new ShortcutsDashboardNavigation()
-      when 'profiles'
-        new NotificationsForm()
-        new NotificationsDropdown()
-      when 'projects'
-        new Project()
-        new ProjectAvatar()
-        switch path[1]
-          when 'compare'
-            new CompareAutocomplete()
-          when 'edit'
-            shortcut_handler = new ShortcutsNavigation()
-            new ProjectNew()
-          when 'new'
-            new ProjectNew()
-          when 'show'
-            new ProjectNew()
-            new ProjectShow()
-            new NotificationsDropdown()
-          when 'wikis'
-            new Wikis()
-            shortcut_handler = new ShortcutsNavigation()
-            new ZenMode()
-            new GLForm($('.wiki-form'))
-          when 'snippets'
-            shortcut_handler = new ShortcutsNavigation()
-            new ZenMode() if path[2] == 'show'
-          when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \
-          'milestones', 'project_members', 'deploy_keys', 'builds', \
-          'hooks', 'services', 'protected_branches'
-            shortcut_handler = new ShortcutsNavigation()
-
-    # If we haven't installed a custom shortcut handler, install the default one
-    if not shortcut_handler
-      new Shortcuts()
-
-  initSearch: ->
-
-    # Only when search form is present
-    new SearchAutocomplete() if $('.search').length
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
new file mode 100644
index 0000000000000000000000000000000000000000..288cce04f878284377e68dd0320226d9bba46509
--- /dev/null
+++ b/app/assets/javascripts/dropzone_input.js
@@ -0,0 +1,219 @@
+
+/*= require markdown_preview */
+
+(function() {
+  this.DropzoneInput = (function() {
+    function DropzoneInput(form) {
+      var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
+      Dropzone.autoDiscover = false;
+      alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
+      alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
+      divHover = "<div class=\"div-dropzone-hover\"></div>";
+      divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
+      divAlert = "<div class=\"" + alertClass + "\"></div>";
+      iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
+      iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
+      uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
+      btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
+      project_uploads_path = window.project_uploads_path || null;
+      max_file_size = gon.max_file_size || 10;
+      form_textarea = $(form).find(".js-gfm-input");
+      form_textarea.wrap("<div class=\"div-dropzone\"></div>");
+      form_textarea.on('paste', (function(_this) {
+        return function(event) {
+          return handlePaste(event);
+        };
+      })(this));
+      $mdArea = $(form_textarea).closest('.md-area');
+      $(form).setupMarkdownPreview();
+      form_dropzone = $(form).find('.div-dropzone');
+      form_dropzone.parent().addClass("div-dropzone-wrapper");
+      form_dropzone.append(divHover);
+      form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
+      form_dropzone.append(divSpinner);
+      form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
+      form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
+      form_dropzone.find(".div-dropzone-spinner").css({
+        "opacity": 0,
+        "display": "none"
+      });
+      dropzone = form_dropzone.dropzone({
+        url: project_uploads_path,
+        dictDefaultMessage: "",
+        clickable: true,
+        paramName: "file",
+        maxFilesize: max_file_size,
+        uploadMultiple: false,
+        headers: {
+          "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+        },
+        previewContainer: false,
+        processing: function() {
+          return $(".div-dropzone-alert").alert("close");
+        },
+        dragover: function() {
+          $mdArea.addClass('is-dropzone-hover');
+          form.find(".div-dropzone-hover").css("opacity", 0.7);
+        },
+        dragleave: function() {
+          $mdArea.removeClass('is-dropzone-hover');
+          form.find(".div-dropzone-hover").css("opacity", 0);
+        },
+        drop: function() {
+          $mdArea.removeClass('is-dropzone-hover');
+          form.find(".div-dropzone-hover").css("opacity", 0);
+          form_textarea.focus();
+        },
+        success: function(header, response) {
+          pasteText(response.link.markdown);
+        },
+        error: function(temp) {
+          var checkIfMsgExists, errorAlert;
+          errorAlert = $(form).find('.error-alert');
+          checkIfMsgExists = errorAlert.children().length;
+          if (checkIfMsgExists === 0) {
+            errorAlert.append(divAlert);
+            $(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
+          }
+        },
+        totaluploadprogress: function(totalUploadProgress) {
+          uploadProgress.text(Math.round(totalUploadProgress) + "%");
+        },
+        sending: function() {
+          form_dropzone.find(".div-dropzone-spinner").css({
+            "opacity": 0.7,
+            "display": "inherit"
+          });
+        },
+        queuecomplete: function() {
+          uploadProgress.text("");
+          $(".dz-preview").remove();
+          $(".markdown-area").trigger("input");
+          $(".div-dropzone-spinner").css({
+            "opacity": 0,
+            "display": "none"
+          });
+        }
+      });
+      child = $(dropzone[0]).children("textarea");
+      handlePaste = function(event) {
+        var filename, image, pasteEvent, text;
+        pasteEvent = event.originalEvent;
+        if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
+          image = isImage(pasteEvent);
+          if (image) {
+            event.preventDefault();
+            filename = getFilename(pasteEvent) || "image.png";
+            text = "{{" + filename + "}}";
+            pasteText(text);
+            return uploadFile(image.getAsFile(), filename);
+          }
+        }
+      };
+      isImage = function(data) {
+        var i, item;
+        i = 0;
+        while (i < data.clipboardData.items.length) {
+          item = data.clipboardData.items[i];
+          if (item.type.indexOf("image") !== -1) {
+            return item;
+          }
+          i++;
+        }
+        return false;
+      };
+      pasteText = function(text) {
+        var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
+        caretStart = $(child)[0].selectionStart;
+        caretEnd = $(child)[0].selectionEnd;
+        textEnd = $(child).val().length;
+        beforeSelection = $(child).val().substring(0, caretStart);
+        afterSelection = $(child).val().substring(caretEnd, textEnd);
+        $(child).val(beforeSelection + text + afterSelection);
+        child.get(0).setSelectionRange(caretStart + text.length, caretEnd + text.length);
+        return form_textarea.trigger("input");
+      };
+      getFilename = function(e) {
+        var value;
+        if (window.clipboardData && window.clipboardData.getData) {
+          value = window.clipboardData.getData("Text");
+        } else if (e.clipboardData && e.clipboardData.getData) {
+          value = e.clipboardData.getData("text/plain");
+        }
+        value = value.split("\r");
+        return value.first();
+      };
+      uploadFile = function(item, filename) {
+        var formData;
+        formData = new FormData();
+        formData.append("file", item, filename);
+        return $.ajax({
+          url: project_uploads_path,
+          type: "POST",
+          data: formData,
+          dataType: "json",
+          processData: false,
+          contentType: false,
+          headers: {
+            "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+          },
+          beforeSend: function() {
+            showSpinner();
+            return closeAlertMessage();
+          },
+          success: function(e, textStatus, response) {
+            return insertToTextArea(filename, response.responseJSON.link.markdown);
+          },
+          error: function(response) {
+            return showError(response.responseJSON.message);
+          },
+          complete: function() {
+            return closeSpinner();
+          }
+        });
+      };
+      insertToTextArea = function(filename, url) {
+        return $(child).val(function(index, val) {
+          return val.replace("{{" + filename + "}}", url + "\n");
+        });
+      };
+      appendToTextArea = function(url) {
+        return $(child).val(function(index, val) {
+          return val + url + "\n";
+        });
+      };
+      showSpinner = function(e) {
+        return form.find(".div-dropzone-spinner").css({
+          "opacity": 0.7,
+          "display": "inherit"
+        });
+      };
+      closeSpinner = function() {
+        return form.find(".div-dropzone-spinner").css({
+          "opacity": 0,
+          "display": "none"
+        });
+      };
+      showError = function(message) {
+        var checkIfMsgExists, errorAlert;
+        errorAlert = $(form).find('.error-alert');
+        checkIfMsgExists = errorAlert.children().length;
+        if (checkIfMsgExists === 0) {
+          errorAlert.append(divAlert);
+          return $(".div-dropzone-alert").append(btnAlert + message);
+        }
+      };
+      closeAlertMessage = function() {
+        return form.find(".div-dropzone-alert").alert("close");
+      };
+      form.find(".markdown-selector").click(function(e) {
+        e.preventDefault();
+        $(this).closest('.gfm-form').find('.div-dropzone').click();
+      });
+    }
+
+    return DropzoneInput;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
deleted file mode 100644
index 665246e2a7db41cdeff8f3ff54937f392b1ad3c0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ /dev/null
@@ -1,201 +0,0 @@
-#= require markdown_preview
-
-class @DropzoneInput
-  constructor: (form) ->
-    Dropzone.autoDiscover = false
-    alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
-    alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
-    divHover = "<div class=\"div-dropzone-hover\"></div>"
-    divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
-    divAlert = "<div class=\"" + alertClass + "\"></div>"
-    iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>"
-    iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
-    uploadProgress = $("<div class=\"div-dropzone-progress\"></div>")
-    btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
-    project_uploads_path = window.project_uploads_path or null
-    max_file_size = gon.max_file_size or 10
-
-    form_textarea = $(form).find(".js-gfm-input")
-    form_textarea.wrap "<div class=\"div-dropzone\"></div>"
-    form_textarea.on 'paste', (event) =>
-      handlePaste(event)
-
-    $mdArea = $(form_textarea).closest('.md-area')
-
-    $(form).setupMarkdownPreview()
-
-    form_dropzone = $(form).find('.div-dropzone')
-    form_dropzone.parent().addClass "div-dropzone-wrapper"
-    form_dropzone.append divHover
-    form_dropzone.find(".div-dropzone-hover").append iconPaperclip
-    form_dropzone.append divSpinner
-    form_dropzone.find(".div-dropzone-spinner").append iconSpinner
-    form_dropzone.find(".div-dropzone-spinner").append uploadProgress
-    form_dropzone.find(".div-dropzone-spinner").css
-      "opacity": 0
-      "display": "none"
-
-    dropzone = form_dropzone.dropzone(
-      url: project_uploads_path
-      dictDefaultMessage: ""
-      clickable: true
-      paramName: "file"
-      maxFilesize: max_file_size
-      uploadMultiple: false
-      headers:
-        "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
-      previewContainer: false
-
-      processing: ->
-        $(".div-dropzone-alert").alert "close"
-
-      dragover: ->
-        $mdArea.addClass 'is-dropzone-hover'
-        form.find(".div-dropzone-hover").css "opacity", 0.7
-        return
-
-      dragleave: ->
-        $mdArea.removeClass 'is-dropzone-hover'
-        form.find(".div-dropzone-hover").css "opacity", 0
-        return
-
-      drop: ->
-        $mdArea.removeClass 'is-dropzone-hover'
-        form.find(".div-dropzone-hover").css "opacity", 0
-        form_textarea.focus()
-        return
-
-      success: (header, response) ->
-        pasteText response.link.markdown
-        return
-
-      error: (temp) ->
-        errorAlert = $(form).find('.error-alert')
-        checkIfMsgExists = errorAlert.children().length
-        if checkIfMsgExists is 0
-          errorAlert.append divAlert
-          $(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed."
-        return
-
-      totaluploadprogress: (totalUploadProgress) ->
-        uploadProgress.text Math.round(totalUploadProgress) + "%"
-        return
-
-      sending: ->
-        form_dropzone.find(".div-dropzone-spinner").css
-          "opacity": 0.7
-          "display": "inherit"
-        return
-
-      queuecomplete: ->
-        uploadProgress.text ""
-        $(".dz-preview").remove()
-        $(".markdown-area").trigger "input"
-        $(".div-dropzone-spinner").css
-          "opacity": 0
-          "display": "none"
-        return
-    )
-
-    child = $(dropzone[0]).children("textarea")
-
-    handlePaste = (event) ->
-      pasteEvent = event.originalEvent
-      if pasteEvent.clipboardData and pasteEvent.clipboardData.items
-        image = isImage(pasteEvent)
-        if image
-          event.preventDefault()
-
-          filename = getFilename(pasteEvent) or "image.png"
-          text = "{{" + filename + "}}"
-          pasteText(text)
-          uploadFile image.getAsFile(), filename
-
-    isImage = (data) ->
-      i = 0
-      while i < data.clipboardData.items.length
-        item = data.clipboardData.items[i]
-        if item.type.indexOf("image") isnt -1
-          return item
-        i++
-      return false
-
-    pasteText = (text) ->
-      caretStart = $(child)[0].selectionStart
-      caretEnd = $(child)[0].selectionEnd
-      textEnd = $(child).val().length
-
-      beforeSelection = $(child).val().substring 0, caretStart
-      afterSelection = $(child).val().substring caretEnd, textEnd
-      $(child).val beforeSelection + text + afterSelection
-      child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
-      form_textarea.trigger "input"
-
-    getFilename = (e) ->
-      if window.clipboardData and window.clipboardData.getData
-        value = window.clipboardData.getData("Text")
-      else if e.clipboardData and e.clipboardData.getData
-        value = e.clipboardData.getData("text/plain")
-
-      value = value.split("\r")
-      value.first()
-
-    uploadFile = (item, filename) ->
-      formData = new FormData()
-      formData.append "file", item, filename
-      $.ajax
-        url: project_uploads_path
-        type: "POST"
-        data: formData
-        dataType: "json"
-        processData: false
-        contentType: false
-        headers:
-          "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
-
-        beforeSend: ->
-          showSpinner()
-          closeAlertMessage()
-
-        success: (e, textStatus, response) ->
-          insertToTextArea(filename, response.responseJSON.link.markdown)
-
-        error: (response) ->
-          showError(response.responseJSON.message)
-
-        complete: ->
-          closeSpinner()
-
-    insertToTextArea = (filename, url) ->
-      $(child).val (index, val) ->
-        val.replace("{{" + filename + "}}", url + "\n")
-
-    appendToTextArea = (url) ->
-      $(child).val (index, val) ->
-        val + url + "\n"
-
-    showSpinner = (e) ->
-      form.find(".div-dropzone-spinner").css
-        "opacity": 0.7
-        "display": "inherit"
-
-    closeSpinner = ->
-      form.find(".div-dropzone-spinner").css
-        "opacity": 0
-        "display": "none"
-
-    showError = (message) ->
-      errorAlert = $(form).find('.error-alert')
-      checkIfMsgExists = errorAlert.children().length
-      if checkIfMsgExists is 0
-        errorAlert.append divAlert
-        $(".div-dropzone-alert").append btnAlert + message
-
-    closeAlertMessage = ->
-      form.find(".div-dropzone-alert").alert "close"
-
-    form.find(".markdown-selector").click (e) ->
-      e.preventDefault()
-      $(@).closest('.gfm-form').find('.div-dropzone').click()
-      return
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a725a41fd123f9d4084136f621dde967c8d4b4a
--- /dev/null
+++ b/app/assets/javascripts/due_date_select.js
@@ -0,0 +1,104 @@
+(function() {
+  this.DueDateSelect = (function() {
+    function DueDateSelect() {
+      var $datePicker, $dueDate, $loading;
+      $datePicker = $('.datepicker');
+      if ($datePicker.length) {
+        $dueDate = $('#milestone_due_date');
+        $datePicker.datepicker({
+          dateFormat: 'yy-mm-dd',
+          onSelect: function(dateText, inst) {
+            return $dueDate.val(dateText);
+          }
+        }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()));
+      }
+      $('.js-clear-due-date').on('click', function(e) {
+        e.preventDefault();
+        return $.datepicker._clearDate($datePicker);
+      });
+      $loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
+      $('.js-due-date-select').each(function(i, dropdown) {
+        var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
+        $dropdown = $(dropdown);
+        $dropdownParent = $dropdown.closest('.dropdown');
+        $datePicker = $dropdownParent.find('.js-due-date-calendar');
+        $block = $dropdown.closest('.block');
+        $selectbox = $dropdown.closest('.selectbox');
+        $value = $block.find('.value');
+        $valueContent = $block.find('.value-content');
+        $sidebarValue = $('.js-due-date-sidebar-value', $block);
+        fieldName = $dropdown.data('field-name');
+        abilityName = $dropdown.data('ability-name');
+        issueUpdateURL = $dropdown.data('issue-update');
+        $dropdown.glDropdown({
+          hidden: function() {
+            $selectbox.hide();
+            return $value.css('display', '');
+          }
+        });
+        addDueDate = function(isDropdown) {
+          var data, date, mediumDate, value;
+          value = $("input[name='" + fieldName + "']").val();
+          if (value !== '') {
+            date = new Date(value.replace(new RegExp('-', 'g'), ','));
+            mediumDate = $.datepicker.formatDate('M d, yy', date);
+          } else {
+            mediumDate = 'No due date';
+          }
+          data = {};
+          data[abilityName] = {};
+          data[abilityName].due_date = value;
+          return $.ajax({
+            type: 'PUT',
+            url: issueUpdateURL,
+            data: data,
+            dataType: 'json',
+            beforeSend: function() {
+              var cssClass;
+              $loading.fadeIn();
+              if (isDropdown) {
+                $dropdown.trigger('loading.gl.dropdown');
+                $selectbox.hide();
+              }
+              $value.css('display', '');
+              cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value';
+              $valueContent.html("<span class='" + cssClass + "'>" + mediumDate + "</span>");
+              $sidebarValue.html(mediumDate);
+              if (value !== '') {
+                return $('.js-remove-due-date-holder').removeClass('hidden');
+              } else {
+                return $('.js-remove-due-date-holder').addClass('hidden');
+              }
+            }
+          }).done(function(data) {
+            if (isDropdown) {
+              $dropdown.trigger('loaded.gl.dropdown');
+              $dropdown.dropdown('toggle');
+            }
+            return $loading.fadeOut();
+          });
+        };
+        $block.on('click', '.js-remove-due-date', function(e) {
+          e.preventDefault();
+          $("input[name='" + fieldName + "']").val('');
+          return addDueDate(false);
+        });
+        return $datePicker.datepicker({
+          dateFormat: 'yy-mm-dd',
+          defaultDate: $("input[name='" + fieldName + "']").val(),
+          altField: "input[name='" + fieldName + "']",
+          onSelect: function() {
+            return addDueDate(true);
+          }
+        });
+      });
+      $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) {
+        return e.stopImmediatePropagation();
+      });
+    }
+
+    return DueDateSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee
deleted file mode 100644
index d65c018dad5f28537a540059ac75afeada86aa17..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/due_date_select.js.coffee
+++ /dev/null
@@ -1,99 +0,0 @@
-class @DueDateSelect
-  constructor: ->
-    # Milestone edit/new form
-    $datePicker = $('.datepicker')
-
-    if $datePicker.length
-      $dueDate = $('#milestone_due_date')
-      $datePicker.datepicker
-        dateFormat: 'yy-mm-dd'
-        onSelect: (dateText, inst) ->
-          $dueDate.val(dateText)
-      .datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()))
-
-    $('.js-clear-due-date').on 'click', (e) ->
-      e.preventDefault()
-      $.datepicker._clearDate($datePicker)
-
-    # Issuable sidebar
-    $loading = $('.js-issuable-update .due_date')
-      .find('.block-loading')
-      .hide()
-
-    $('.js-due-date-select').each (i, dropdown) ->
-      $dropdown = $(dropdown)
-      $dropdownParent = $dropdown.closest('.dropdown')
-      $datePicker = $dropdownParent.find('.js-due-date-calendar')
-      $block = $dropdown.closest('.block')
-      $selectbox = $dropdown.closest('.selectbox')
-      $value = $block.find('.value')
-      $valueContent = $block.find('.value-content')
-      $sidebarValue = $('.js-due-date-sidebar-value', $block)
-
-      fieldName = $dropdown.data('field-name')
-      abilityName = $dropdown.data('ability-name')
-      issueUpdateURL = $dropdown.data('issue-update')
-
-      $dropdown.glDropdown(
-        hidden: ->
-          $selectbox.hide()
-          $value.css('display', '')
-      )
-
-      addDueDate = (isDropdown) ->
-        # Create the post date
-        value = $("input[name='#{fieldName}']").val()
-
-        if value isnt ''
-          date = new Date value.replace(new RegExp('-', 'g'), ',')
-          mediumDate = $.datepicker.formatDate 'M d, yy', date
-        else
-          mediumDate = 'No due date'
-
-        data = {}
-        data[abilityName] = {}
-        data[abilityName].due_date = value
-
-        $.ajax(
-          type: 'PUT'
-          url: issueUpdateURL
-          data: data
-          dataType: 'json'
-          beforeSend: ->
-            $loading.fadeIn()
-            if isDropdown
-              $dropdown.trigger('loading.gl.dropdown')
-              $selectbox.hide()
-            $value.css('display', '')
-
-            cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value'
-            $valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>")
-            $sidebarValue.html(mediumDate)
-
-            if value isnt ''
-              $('.js-remove-due-date-holder').removeClass 'hidden'
-            else
-              $('.js-remove-due-date-holder').addClass 'hidden'
-        ).done (data) ->
-          if isDropdown
-            $dropdown.trigger('loaded.gl.dropdown')
-            $dropdown.dropdown('toggle')
-          $loading.fadeOut()
-
-      $block.on 'click', '.js-remove-due-date', (e) ->
-        e.preventDefault()
-        $("input[name='#{fieldName}']").val ''
-        addDueDate(false)
-
-      $datePicker.datepicker(
-        dateFormat: 'yy-mm-dd',
-        defaultDate: $("input[name='#{fieldName}']").val()
-        altField: "input[name='#{fieldName}']"
-        onSelect: ->
-          addDueDate(true)
-      )
-
-    $(document)
-      .off 'click', '.ui-datepicker-header a'
-      .on 'click', '.ui-datepicker-header a', (e) ->
-        e.stopImmediatePropagation()
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
new file mode 100644
index 0000000000000000000000000000000000000000..ae3dde63da382bfb6cb24695daeb34579f1c5c2f
--- /dev/null
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -0,0 +1,14 @@
+(function() {
+  $.fn.extend({
+    disable: function() {
+      return $(this).attr('disabled', 'disabled').addClass('disabled');
+    }
+  });
+
+  $.fn.extend({
+    enable: function() {
+      return $(this).removeAttr('disabled').removeClass('disabled');
+    }
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
deleted file mode 100644
index 0a9db8eb5ef583328cf56fc0b8fedd6233fffe83..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/extensions/jquery.js.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-# Disable an element and add the 'disabled' Bootstrap class
-$.fn.extend disable: ->
-  $(@)
-    .attr('disabled', 'disabled')
-    .addClass('disabled')
-
-# Enable an element and remove the 'disabled' Bootstrap class
-$.fn.extend enable: ->
-  $(@)
-    .removeAttr('disabled')
-    .removeClass('disabled')
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
new file mode 100644
index 0000000000000000000000000000000000000000..09b5eb398d4431ca2890a6e8f21b0a30c2dee997
--- /dev/null
+++ b/app/assets/javascripts/files_comment_button.js
@@ -0,0 +1,141 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.FilesCommentButton = (function() {
+    var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
+
+    COMMENT_BUTTON_CLASS = '.add-diff-note';
+
+    COMMENT_BUTTON_TEMPLATE = _.template('<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
+
+    LINE_HOLDER_CLASS = '.line_holder';
+
+    LINE_NUMBER_CLASS = 'diff-line-num';
+
+    LINE_CONTENT_CLASS = 'line_content';
+
+    UNFOLDABLE_LINE_CLASS = 'js-unfold';
+
+    EMPTY_CELL_CLASS = 'empty-cell';
+
+    OLD_LINE_CLASS = 'old_line';
+
+    LINE_COLUMN_CLASSES = "." + LINE_NUMBER_CLASS + ", .line_content";
+
+    TEXT_FILE_SELECTOR = '.text-file';
+
+    DEBOUNCE_TIMEOUT_DURATION = 100;
+
+    function FilesCommentButton(filesContainerElement) {
+      var debounce;
+      this.filesContainerElement = filesContainerElement;
+      this.destroy = bind(this.destroy, this);
+      this.render = bind(this.render, this);
+      this.VIEW_TYPE = $('input#view[type=hidden]').val();
+      debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION);
+      $(document).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy);
+    }
+
+    FilesCommentButton.prototype.render = function(e) {
+      var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
+      $currentTarget = $(e.currentTarget);
+      buttonParentElement = this.getButtonParent($currentTarget);
+      if (!this.shouldRender(e, buttonParentElement)) {
+        return;
+      }
+      textFileElement = this.getTextFileElement($currentTarget);
+      lineContentElement = this.getLineContent($currentTarget);
+      buttonParentElement.append(this.buildButton({
+        noteableType: textFileElement.attr('data-noteable-type'),
+        noteableID: textFileElement.attr('data-noteable-id'),
+        commitID: textFileElement.attr('data-commit-id'),
+        noteType: lineContentElement.attr('data-note-type'),
+        position: lineContentElement.attr('data-position'),
+        lineType: lineContentElement.attr('data-line-type'),
+        discussionID: lineContentElement.attr('data-discussion-id'),
+        lineCode: lineContentElement.attr('data-line-code')
+      }));
+    };
+
+    FilesCommentButton.prototype.destroy = function(e) {
+      if (this.isMovingToSameType(e)) {
+        return;
+      }
+      $(COMMENT_BUTTON_CLASS, this.getButtonParent($(e.currentTarget))).remove();
+    };
+
+    FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
+      var initializedButtonTemplate;
+      initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE({
+        COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr(1)
+      });
+      return $(initializedButtonTemplate).attr({
+        'data-noteable-type': buttonAttributes.noteableType,
+        'data-noteable-id': buttonAttributes.noteableID,
+        'data-commit-id': buttonAttributes.commitID,
+        'data-note-type': buttonAttributes.noteType,
+        'data-line-code': buttonAttributes.lineCode,
+        'data-position': buttonAttributes.position,
+        'data-discussion-id': buttonAttributes.discussionID,
+        'data-line-type': buttonAttributes.lineType
+      });
+    };
+
+    FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
+      return $(hoveredElement.closest(TEXT_FILE_SELECTOR));
+    };
+
+    FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
+      if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
+        return hoveredElement;
+      }
+      if (this.VIEW_TYPE === 'inline') {
+        return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
+      } else {
+        return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
+      }
+    };
+
+    FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
+      if (this.VIEW_TYPE === 'inline') {
+        if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
+          return hoveredElement;
+        }
+        return hoveredElement.parent().find("." + OLD_LINE_CLASS);
+      } else {
+        if (hoveredElement.hasClass(LINE_NUMBER_CLASS)) {
+          return hoveredElement;
+        }
+        return $(hoveredElement).prev("." + LINE_NUMBER_CLASS);
+      }
+    };
+
+    FilesCommentButton.prototype.isMovingToSameType = function(e) {
+      var newButtonParent;
+      newButtonParent = this.getButtonParent($(e.toElement));
+      if (!newButtonParent) {
+        return false;
+      }
+      return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
+    };
+
+    FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) {
+      return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
+    };
+
+    return FilesCommentButton;
+
+  })();
+
+  $.fn.filesCommentButton = function() {
+    if (!(this && (this.parent().data('can-create-note') != null))) {
+      return;
+    }
+    return this.each(function() {
+      if (!$.data(this, 'filesCommentButton')) {
+        return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
+      }
+    });
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee
deleted file mode 100644
index db0bf7082a99441716248d10704a446c828306da..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/files_comment_button.js.coffee
+++ /dev/null
@@ -1,97 +0,0 @@
-class @FilesCommentButton
-  COMMENT_BUTTON_CLASS = '.add-diff-note'
-  COMMENT_BUTTON_TEMPLATE = _.template '<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'
-  LINE_HOLDER_CLASS = '.line_holder'
-  LINE_NUMBER_CLASS = 'diff-line-num'
-  LINE_CONTENT_CLASS = 'line_content'
-  UNFOLDABLE_LINE_CLASS = 'js-unfold'
-  EMPTY_CELL_CLASS = 'empty-cell'
-  OLD_LINE_CLASS = 'old_line'
-  NEW_CLASS = 'new'
-  LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content"
-  TEXT_FILE_SELECTOR = '.text-file'
-  DEBOUNCE_TIMEOUT_DURATION = 100
-
-  constructor: (@filesContainerElement) ->
-    @VIEW_TYPE = $('input#view[type=hidden]').val()
-
-    debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION
-
-    $(document)
-      .on 'mouseover', LINE_COLUMN_CLASSES, debounce
-      .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy
-
-  render: (e) =>
-    $currentTarget = $(e.currentTarget)
-    buttonParentElement = @getButtonParent $currentTarget
-    return unless @shouldRender e, buttonParentElement
-
-    textFileElement = @getTextFileElement $currentTarget
-    lineContentElement = @getLineContent $currentTarget
-
-    buttonParentElement.append @buildButton
-      noteableType: textFileElement.attr 'data-noteable-type'
-      noteableID: textFileElement.attr 'data-noteable-id'
-      commitID: textFileElement.attr 'data-commit-id'
-      noteType: lineContentElement.attr 'data-note-type'
-      position: lineContentElement.attr 'data-position'
-      lineType: lineContentElement.attr 'data-line-type'
-      discussionID: lineContentElement.attr 'data-discussion-id'
-      lineCode: lineContentElement.attr 'data-line-code'
-    return
-
-  destroy: (e) =>
-    return if @isMovingToSameType e
-    $(COMMENT_BUTTON_CLASS, @getButtonParent $(e.currentTarget)).remove()
-    return
-
-  buildButton: (buttonAttributes) ->
-    initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE
-      COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr 1
-    $(initializedButtonTemplate).attr
-      'data-noteable-type': buttonAttributes.noteableType
-      'data-noteable-id': buttonAttributes.noteableID
-      'data-commit-id': buttonAttributes.commitID
-      'data-note-type': buttonAttributes.noteType
-      'data-line-code': buttonAttributes.lineCode
-      'data-position': buttonAttributes.position
-      'data-discussion-id': buttonAttributes.discussionID
-      'data-line-type': buttonAttributes.lineType
-
-  getTextFileElement: (hoveredElement) ->
-    $(hoveredElement.closest TEXT_FILE_SELECTOR)
-
-  getLineContent: (hoveredElement) ->
-    return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS
-
-    $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
-
-  getButtonParent: (hoveredElement) ->
-    if @VIEW_TYPE is 'inline'
-      return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS
-
-      $(".#{OLD_LINE_CLASS}", hoveredElement.parent())
-    else
-      return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS
-
-      $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
-
-  diffTypeClass: (hoveredElement) ->
-    if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old'
-
-  isMovingToSameType: (e) ->
-    newButtonParent = @getButtonParent $(e.toElement)
-    return false unless newButtonParent
-    newButtonParent.is @getButtonParent $(e.currentTarget)
-
-  shouldRender: (e, buttonParentElement) ->
-    (not buttonParentElement.hasClass(EMPTY_CELL_CLASS) and \
-    not buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) and \
-    $(COMMENT_BUTTON_CLASS, buttonParentElement).length is 0)
-
-$.fn.filesCommentButton = ->
-  return unless this and @parent().data('can-create-note')?
-
-  @each ->
-    unless $.data this, 'filesCommentButton'
-      $.data this, 'filesCommentButton', new FilesCommentButton $(this)
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8a02d6fa158e3de137f596ea8860f1e946ca3ac
--- /dev/null
+++ b/app/assets/javascripts/flash.js
@@ -0,0 +1,43 @@
+(function() {
+  this.Flash = (function() {
+    var hideFlash;
+
+    hideFlash = function() {
+      return $(this).fadeOut();
+    };
+
+    function Flash(message, type, parent) {
+      var flash, textDiv;
+      if (type == null) {
+        type = 'alert';
+      }
+      if (parent == null) {
+        parent = null;
+      }
+      if (parent) {
+        this.flashContainer = parent.find('.flash-container');
+      } else {
+        this.flashContainer = $('.flash-container-page');
+      }
+      this.flashContainer.html('');
+      flash = $('<div/>', {
+        "class": "flash-" + type
+      });
+      flash.on('click', hideFlash);
+      textDiv = $('<div/>', {
+        "class": 'flash-text',
+        text: message
+      });
+      textDiv.appendTo(flash);
+      if (this.flashContainer.parent().hasClass('content-wrapper')) {
+        textDiv.addClass('container-fluid container-limited');
+      }
+      flash.appendTo(this.flashContainer);
+      this.flashContainer.show();
+    }
+
+    return Flash;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
deleted file mode 100644
index 5a493041538c82750b9981388bc5c42bdc1fe19b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/flash.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-class @Flash
-  hideFlash = -> $(@).fadeOut()
-
-  constructor: (message, type = 'alert', parent = null)->
-    if parent
-      @flashContainer = parent.find('.flash-container')
-    else
-      @flashContainer = $('.flash-container-page')
-
-    @flashContainer.html('')
-
-    flash = $('<div/>',
-      class: "flash-#{type}"
-    )
-    flash.on 'click', hideFlash
-
-    textDiv = $('<div/>',
-      class: 'flash-text',
-      text: message
-    )
-    textDiv.appendTo(flash)
-
-    if @flashContainer.parent().hasClass('content-wrapper')
-      textDiv.addClass('container-fluid container-limited')
-
-    flash.appendTo(@flashContainer)
-    @flashContainer.show()
-
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e5b15f4b77ea4c512c52ff4ee816c3fac861c60
--- /dev/null
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -0,0 +1,272 @@
+(function() {
+  if (window.GitLab == null) {
+    window.GitLab = {};
+  }
+
+  GitLab.GfmAutoComplete = {
+    dataLoading: false,
+    dataLoaded: false,
+    cachedData: {},
+    dataSource: '',
+    Emoji: {
+      template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
+    },
+    Members: {
+      template: '<li>${username} <small>${title}</small></li>'
+    },
+    Labels: {
+      template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
+    },
+    Issues: {
+      template: '<li><small>${id}</small> ${title}</li>'
+    },
+    Milestones: {
+      template: '<li>${title}</li>'
+    },
+    Loading: {
+      template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
+    },
+    DefaultOptions: {
+      sorter: function(query, items, searchKey) {
+        if ((items[0].name != null) && items[0].name === 'loading') {
+          return items;
+        }
+        return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey);
+      },
+      filter: function(query, data, searchKey) {
+        if (data[0] === 'loading') {
+          return data;
+        }
+        return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
+      },
+      beforeInsert: function(value) {
+        if (!GitLab.GfmAutoComplete.dataLoaded) {
+          return this.at;
+        } else {
+          return value;
+        }
+      }
+    },
+    setup: function(input) {
+      this.input = input || $('.js-gfm-input');
+      this.destroyAtWho();
+      this.setupAtWho();
+      if (this.dataSource) {
+        if (!this.dataLoading && !this.cachedData) {
+          this.dataLoading = true;
+          setTimeout((function(_this) {
+            return function() {
+              var fetch;
+              fetch = _this.fetchData(_this.dataSource);
+              return fetch.done(function(data) {
+                _this.dataLoading = false;
+                return _this.loadData(data);
+              });
+            };
+          })(this), 1000);
+        }
+        if (this.cachedData != null) {
+          return this.loadData(this.cachedData);
+        }
+      }
+    },
+    setupAtWho: function() {
+      this.input.atwho({
+        at: ':',
+        displayTpl: (function(_this) {
+          return function(value) {
+            if (value.path != null) {
+              return _this.Emoji.template;
+            } else {
+              return _this.Loading.template;
+            }
+          };
+        })(this),
+        insertTpl: ':${name}:',
+        data: ['loading'],
+        callbacks: {
+          sorter: this.DefaultOptions.sorter,
+          filter: this.DefaultOptions.filter,
+          beforeInsert: this.DefaultOptions.beforeInsert
+        }
+      });
+      this.input.atwho({
+        at: '@',
+        displayTpl: (function(_this) {
+          return function(value) {
+            if (value.username != null) {
+              return _this.Members.template;
+            } else {
+              return _this.Loading.template;
+            }
+          };
+        })(this),
+        insertTpl: '${atwho-at}${username}',
+        searchKey: 'search',
+        data: ['loading'],
+        callbacks: {
+          sorter: this.DefaultOptions.sorter,
+          filter: this.DefaultOptions.filter,
+          beforeInsert: this.DefaultOptions.beforeInsert,
+          beforeSave: function(members) {
+            return $.map(members, function(m) {
+              var title;
+              if (m.username == null) {
+                return m;
+              }
+              title = m.name;
+              if (m.count) {
+                title += " (" + m.count + ")";
+              }
+              return {
+                username: m.username,
+                title: sanitize(title),
+                search: sanitize(m.username + " " + m.name)
+              };
+            });
+          }
+        }
+      });
+      this.input.atwho({
+        at: '#',
+        alias: 'issues',
+        searchKey: 'search',
+        displayTpl: (function(_this) {
+          return function(value) {
+            if (value.title != null) {
+              return _this.Issues.template;
+            } else {
+              return _this.Loading.template;
+            }
+          };
+        })(this),
+        data: ['loading'],
+        insertTpl: '${atwho-at}${id}',
+        callbacks: {
+          sorter: this.DefaultOptions.sorter,
+          filter: this.DefaultOptions.filter,
+          beforeInsert: this.DefaultOptions.beforeInsert,
+          beforeSave: function(issues) {
+            return $.map(issues, function(i) {
+              if (i.title == null) {
+                return i;
+              }
+              return {
+                id: i.iid,
+                title: sanitize(i.title),
+                search: i.iid + " " + i.title
+              };
+            });
+          }
+        }
+      });
+      this.input.atwho({
+        at: '%',
+        alias: 'milestones',
+        searchKey: 'search',
+        displayTpl: (function(_this) {
+          return function(value) {
+            if (value.title != null) {
+              return _this.Milestones.template;
+            } else {
+              return _this.Loading.template;
+            }
+          };
+        })(this),
+        insertTpl: '${atwho-at}"${title}"',
+        data: ['loading'],
+        callbacks: {
+          beforeSave: function(milestones) {
+            return $.map(milestones, function(m) {
+              if (m.title == null) {
+                return m;
+              }
+              return {
+                id: m.iid,
+                title: sanitize(m.title),
+                search: "" + m.title
+              };
+            });
+          }
+        }
+      });
+      this.input.atwho({
+        at: '!',
+        alias: 'mergerequests',
+        searchKey: 'search',
+        displayTpl: (function(_this) {
+          return function(value) {
+            if (value.title != null) {
+              return _this.Issues.template;
+            } else {
+              return _this.Loading.template;
+            }
+          };
+        })(this),
+        data: ['loading'],
+        insertTpl: '${atwho-at}${id}',
+        callbacks: {
+          sorter: this.DefaultOptions.sorter,
+          filter: this.DefaultOptions.filter,
+          beforeInsert: this.DefaultOptions.beforeInsert,
+          beforeSave: function(merges) {
+            return $.map(merges, function(m) {
+              if (m.title == null) {
+                return m;
+              }
+              return {
+                id: m.iid,
+                title: sanitize(m.title),
+                search: m.iid + " " + m.title
+              };
+            });
+          }
+        }
+      });
+      return this.input.atwho({
+        at: '~',
+        alias: 'labels',
+        searchKey: 'search',
+        displayTpl: this.Labels.template,
+        insertTpl: '${atwho-at}${title}',
+        callbacks: {
+          beforeSave: function(merges) {
+            var sanitizeLabelTitle;
+            sanitizeLabelTitle = function(title) {
+              if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) {
+                return "\"" + (sanitize(title)) + "\"";
+              } else {
+                return sanitize(title);
+              }
+            };
+            return $.map(merges, function(m) {
+              return {
+                title: sanitizeLabelTitle(m.title),
+                color: m.color,
+                search: "" + m.title
+              };
+            });
+          }
+        }
+      });
+    },
+    destroyAtWho: function() {
+      return this.input.atwho('destroy');
+    },
+    fetchData: function(dataSource) {
+      return $.getJSON(dataSource);
+    },
+    loadData: function(data) {
+      this.cachedData = data;
+      this.dataLoaded = true;
+      this.input.atwho('load', '@', data.members);
+      this.input.atwho('load', 'issues', data.issues);
+      this.input.atwho('load', 'milestones', data.milestones);
+      this.input.atwho('load', 'mergerequests', data.mergerequests);
+      this.input.atwho('load', ':', data.emojis);
+      this.input.atwho('load', '~', data.labels);
+      return $(':focus').trigger('keyup');
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
deleted file mode 100644
index 4a851d9c9fbd626f6f36b997742aafc75fb23e23..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ /dev/null
@@ -1,228 +0,0 @@
-# Creates the variables for setting up GFM auto-completion
-
-window.GitLab ?= {}
-GitLab.GfmAutoComplete =
-  dataLoading: false
-  dataLoaded: false
-  cachedData: {}
-  dataSource: ''
-
-  # Emoji
-  Emoji:
-    template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
-
-  # Team Members
-  Members:
-    template: '<li>${username} <small>${title}</small></li>'
-
-  Labels:
-    template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
-
-  # Issues and MergeRequests
-  Issues:
-    template: '<li><small>${id}</small> ${title}</li>'
-
-  # Milestones
-  Milestones:
-    template: '<li>${title}</li>'
-
-  Loading:
-    template: '<li><i class="fa fa-refresh fa-spin"></i> Loading...</li>'
-
-  DefaultOptions:
-    sorter: (query, items, searchKey) ->
-      return items if items[0].name? and items[0].name is 'loading'
-
-      $.fn.atwho.default.callbacks.sorter(query, items, searchKey)
-    filter: (query, data, searchKey) ->
-      return data if data[0] is 'loading'
-
-      $.fn.atwho.default.callbacks.filter(query, data, searchKey)
-    beforeInsert: (value) ->
-      if not GitLab.GfmAutoComplete.dataLoaded
-        @at
-      else
-        value
-
-  # Add GFM auto-completion to all input fields, that accept GFM input.
-  setup: (wrap) ->
-    @input = $('.js-gfm-input')
-
-    # destroy previous instances
-    @destroyAtWho()
-
-    # set up instances
-    @setupAtWho()
-
-    if @dataSource
-      if not @dataLoading and not @cachedData
-        @dataLoading = true
-
-        # We should wait until initializations are done
-        # and only trigger the last .setup since
-        # The previous .dataSource belongs to the previous issuable
-        # and the last one will have the **proper** .dataSource property
-        # TODO: Make this a singleton and turn off events when moving to another page
-        setTimeout( =>
-          fetch = @fetchData(@dataSource)
-          fetch.done (data) =>
-            @dataLoading = false
-            @loadData(data)
-        , 1000)
-
-      if @cachedData?
-        @loadData(@cachedData)
-
-  setupAtWho: ->
-    # Emoji
-    @input.atwho
-      at: ':'
-      displayTpl: (value) =>
-        if value.path?
-          @Emoji.template
-        else
-          @Loading.template
-      insertTpl: ':${name}:'
-      data: ['loading']
-      callbacks:
-        sorter: @DefaultOptions.sorter
-        filter: @DefaultOptions.filter
-        beforeInsert: @DefaultOptions.beforeInsert
-
-    # Team Members
-    @input.atwho
-      at: '@'
-      displayTpl: (value) =>
-        if value.username?
-          @Members.template
-        else
-          @Loading.template
-      insertTpl: '${atwho-at}${username}'
-      searchKey: 'search'
-      data: ['loading']
-      callbacks:
-        sorter: @DefaultOptions.sorter
-        filter: @DefaultOptions.filter
-        beforeInsert: @DefaultOptions.beforeInsert
-        beforeSave: (members) ->
-          $.map members, (m) ->
-            return m if not m.username?
-
-            title = m.name
-            title += " (#{m.count})" if m.count
-
-            username: m.username
-            title:    sanitize(title)
-            search:   sanitize("#{m.username} #{m.name}")
-
-    @input.atwho
-      at: '#'
-      alias: 'issues'
-      searchKey: 'search'
-      displayTpl:  (value) =>
-        if value.title?
-          @Issues.template
-        else
-          @Loading.template
-      data: ['loading']
-      insertTpl: '${atwho-at}${id}'
-      callbacks:
-        sorter: @DefaultOptions.sorter
-        filter: @DefaultOptions.filter
-        beforeInsert: @DefaultOptions.beforeInsert
-        beforeSave: (issues) ->
-          $.map issues, (i) ->
-            return i if not i.title?
-
-            id:     i.iid
-            title:  sanitize(i.title)
-            search: "#{i.iid} #{i.title}"
-
-    @input.atwho
-      at: '%'
-      alias: 'milestones'
-      searchKey: 'search'
-      displayTpl:  (value) =>
-        if value.title?
-          @Milestones.template
-        else
-          @Loading.template
-      insertTpl: '${atwho-at}"${title}"'
-      data: ['loading']
-      callbacks:
-        beforeSave: (milestones) ->
-          $.map milestones, (m) ->
-            return m if not m.title?
-
-            id:     m.iid
-            title:  sanitize(m.title)
-            search: "#{m.title}"
-
-    @input.atwho
-      at: '!'
-      alias: 'mergerequests'
-      searchKey: 'search'
-      displayTpl:  (value) =>
-        if value.title?
-          @Issues.template
-        else
-          @Loading.template
-      data: ['loading']
-      insertTpl: '${atwho-at}${id}'
-      callbacks:
-        sorter: @DefaultOptions.sorter
-        filter: @DefaultOptions.filter
-        beforeInsert: @DefaultOptions.beforeInsert
-        beforeSave: (merges) ->
-          $.map merges, (m) ->
-            return m if not m.title?
-
-            id:     m.iid
-            title:  sanitize(m.title)
-            search: "#{m.iid} #{m.title}"
-
-    @input.atwho
-      at: '~'
-      alias: 'labels'
-      searchKey: 'search'
-      displayTpl: @Labels.template
-      insertTpl: '${atwho-at}${title}'
-      callbacks:
-        beforeSave: (merges) ->
-          sanitizeLabelTitle = (title)->
-            if /[\w\?&]+\s+[\w\?&]+/g.test(title)
-              "\"#{sanitize(title)}\""
-            else
-              sanitize(title)
-
-          $.map merges, (m) ->
-            title: sanitizeLabelTitle(m.title)
-            color: m.color
-            search: "#{m.title}"
-
-  destroyAtWho: ->
-    @input.atwho('destroy')
-
-  fetchData: (dataSource) ->
-    $.getJSON(dataSource)
-
-  loadData: (data) ->
-    @cachedData = data
-    @dataLoaded = true
-
-    # load members
-    @input.atwho 'load', '@', data.members
-    # load issues
-    @input.atwho 'load', 'issues', data.issues
-    # load milestones
-    @input.atwho 'load', 'milestones', data.milestones
-    # load merge requests
-    @input.atwho 'load', 'mergerequests', data.mergerequests
-    # load emojis
-    @input.atwho 'load', ':', data.emojis
-    # load labels
-    @input.atwho 'load', '~', data.labels
-
-    # This trigger at.js again
-    # otherwise we would be stuck with loading until the user types
-    $(':focus').trigger('keyup')
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
new file mode 100644
index 0000000000000000000000000000000000000000..a9a7c802f87db0b21a9c2b0b748f66ed3bb0e9fb
--- /dev/null
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -0,0 +1,712 @@
+(function() {
+  var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
+    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+  GitLabDropdownFilter = (function() {
+    var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
+
+    BLUR_KEYCODES = [27, 40];
+
+    ARROW_KEY_CODES = [38, 40];
+
+    HAS_VALUE_CLASS = "has-value";
+
+    function GitLabDropdownFilter(input, options) {
+      var $clearButton, $inputContainer, ref, timeout;
+      this.input = input;
+      this.options = options;
+      this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
+      $inputContainer = this.input.parent();
+      $clearButton = $inputContainer.find('.js-dropdown-input-clear');
+      this.indeterminateIds = [];
+      $clearButton.on('click', (function(_this) {
+        return function(e) {
+          e.preventDefault();
+          e.stopPropagation();
+          return _this.input.val('').trigger('keyup').focus();
+        };
+      })(this));
+      timeout = "";
+      this.input
+        .on('keydown', function (e) {
+          var keyCode = e.which;
+
+          if (keyCode === 13) {
+            e.preventDefault()
+          }
+        })
+        .on('keyup', function(e) {
+          var keyCode;
+          keyCode = e.which;
+          if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
+            return;
+          }
+          if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
+            $inputContainer.addClass(HAS_VALUE_CLASS);
+          } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
+            $inputContainer.removeClass(HAS_VALUE_CLASS);
+          }
+          if (keyCode === 13) {
+            return false;
+          }
+          if (this.options.remote) {
+            clearTimeout(timeout);
+            return timeout = setTimeout(function() {
+              var blur_field;
+              blur_field = this.shouldBlur(keyCode);
+              if (blur_field && this.filterInputBlur) {
+                this.input.blur();
+              }
+              return this.options.query(this.input.val(), function(data) {
+                return this.options.callback(data);
+              });
+            }, 250);
+          } else {
+            return this.filter(this.input.val());
+          }
+        }.bind(this));
+    }
+
+    GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
+      return BLUR_KEYCODES.indexOf(keyCode) >= 0;
+    };
+
+    GitLabDropdownFilter.prototype.filter = function(search_text) {
+      var data, elements, group, key, results, tmp;
+      if (this.options.onFilter) {
+        this.options.onFilter(search_text);
+      }
+      data = this.options.data();
+      if ((data != null) && !this.options.filterByText) {
+        results = data;
+        if (search_text !== '') {
+          if (_.isArray(data)) {
+            results = fuzzaldrinPlus.filter(data, search_text, {
+              key: this.options.keys
+            });
+          } else {
+            if (gl.utils.isObject(data)) {
+              results = {};
+              for (key in data) {
+                group = data[key];
+                tmp = fuzzaldrinPlus.filter(group, search_text, {
+                  key: this.options.keys
+                });
+                if (tmp.length) {
+                  results[key] = tmp.map(function(item) {
+                    return item;
+                  });
+                }
+              }
+            }
+          }
+        }
+        return this.options.callback(results);
+      } else {
+        elements = this.options.elements();
+        if (search_text) {
+          return elements.each(function() {
+            var $el, matches;
+            $el = $(this);
+            matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
+            if (!$el.is('.dropdown-header')) {
+              if (matches.length) {
+                return $el.show();
+              } else {
+                return $el.hide();
+              }
+            }
+          });
+        } else {
+          return elements.show();
+        }
+      }
+    };
+
+    return GitLabDropdownFilter;
+
+  })();
+
+  GitLabDropdownRemote = (function() {
+    function GitLabDropdownRemote(dataEndpoint, options) {
+      this.dataEndpoint = dataEndpoint;
+      this.options = options;
+    }
+
+    GitLabDropdownRemote.prototype.execute = function() {
+      if (typeof this.dataEndpoint === "string") {
+        return this.fetchData();
+      } else if (typeof this.dataEndpoint === "function") {
+        if (this.options.beforeSend) {
+          this.options.beforeSend();
+        }
+        return this.dataEndpoint("", (function(_this) {
+          return function(data) {
+            if (_this.options.success) {
+              _this.options.success(data);
+            }
+            if (_this.options.beforeSend) {
+              return _this.options.beforeSend();
+            }
+          };
+        })(this));
+      }
+    };
+
+    GitLabDropdownRemote.prototype.fetchData = function() {
+      return $.ajax({
+        url: this.dataEndpoint,
+        dataType: this.options.dataType,
+        beforeSend: (function(_this) {
+          return function() {
+            if (_this.options.beforeSend) {
+              return _this.options.beforeSend();
+            }
+          };
+        })(this),
+        success: (function(_this) {
+          return function(data) {
+            if (_this.options.success) {
+              return _this.options.success(data);
+            }
+          };
+        })(this)
+      });
+    };
+
+    return GitLabDropdownRemote;
+
+  })();
+
+  GitLabDropdown = (function() {
+    var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, currentIndex;
+
+    LOADING_CLASS = "is-loading";
+
+    PAGE_TWO_CLASS = "is-page-two";
+
+    ACTIVE_CLASS = "is-active";
+
+    INDETERMINATE_CLASS = "is-indeterminate";
+
+    currentIndex = -1;
+
+    FILTER_INPUT = '.dropdown-input .dropdown-input-field';
+
+    function GitLabDropdown(el1, options) {
+      var ref, ref1, ref2, ref3, searchFields, selector, self;
+      this.el = el1;
+      this.options = options;
+      this.updateLabel = bind(this.updateLabel, this);
+      this.hidden = bind(this.hidden, this);
+      this.opened = bind(this.opened, this);
+      this.shouldPropagate = bind(this.shouldPropagate, this);
+      self = this;
+      selector = $(this.el).data("target");
+      this.dropdown = selector != null ? $(selector) : $(this.el).parent();
+      ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
+      self = this;
+      if (_.isString(this.filterInput)) {
+        this.filterInput = this.getElement(this.filterInput);
+      }
+      searchFields = this.options.search ? this.options.search.fields : [];
+      if (this.options.data) {
+        if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
+          this.fullData = this.options.data;
+          this.parseData(this.options.data);
+        } else {
+          this.remote = new GitLabDropdownRemote(this.options.data, {
+            dataType: this.options.dataType,
+            beforeSend: this.toggleLoading.bind(this),
+            success: (function(_this) {
+              return function(data) {
+                _this.fullData = data;
+                _this.parseData(_this.fullData);
+                if (_this.options.filterable && _this.filter && _this.filter.input) {
+                  return _this.filter.input.trigger('keyup');
+                }
+              };
+            })(this)
+          });
+        }
+      }
+      if (this.options.filterable) {
+        this.filter = new GitLabDropdownFilter(this.filterInput, {
+          filterInputBlur: this.filterInputBlur,
+          filterByText: this.options.filterByText,
+          onFilter: this.options.onFilter,
+          remote: this.options.filterRemote,
+          query: this.options.data,
+          keys: searchFields,
+          elements: (function(_this) {
+            return function() {
+              selector = '.dropdown-content li:not(.divider)';
+              if (_this.dropdown.find('.dropdown-toggle-page').length) {
+                selector = ".dropdown-page-one " + selector;
+              }
+              return $(selector);
+            };
+          })(this),
+          data: (function(_this) {
+            return function() {
+              return _this.fullData;
+            };
+          })(this),
+          callback: (function(_this) {
+            return function(data) {
+              _this.parseData(data);
+              if (_this.filterInput.val() !== '') {
+                selector = '.dropdown-content li:not(.divider):visible';
+                if (_this.dropdown.find('.dropdown-toggle-page').length) {
+                  selector = ".dropdown-page-one " + selector;
+                }
+                $(selector, _this.dropdown).first().find('a').addClass('is-focused');
+                return currentIndex = 0;
+              }
+            };
+          })(this)
+        });
+      }
+      this.dropdown.on("shown.bs.dropdown", this.opened);
+      this.dropdown.on("hidden.bs.dropdown", this.hidden);
+      $(this.el).on("update.label", this.updateLabel);
+      this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
+      this.dropdown.on('keyup', (function(_this) {
+        return function(e) {
+          if (e.which === 27) {
+            return $('.dropdown-menu-close', _this.dropdown).trigger('click');
+          }
+        };
+      })(this));
+      this.dropdown.on('blur', 'a', (function(_this) {
+        return function(e) {
+          var $dropdownMenu, $relatedTarget;
+          if (e.relatedTarget != null) {
+            $relatedTarget = $(e.relatedTarget);
+            $dropdownMenu = $relatedTarget.closest('.dropdown-menu');
+            if ($dropdownMenu.length === 0) {
+              return _this.dropdown.removeClass('open');
+            }
+          }
+        };
+      })(this));
+      if (this.dropdown.find(".dropdown-toggle-page").length) {
+        this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) {
+          return function(e) {
+            e.preventDefault();
+            e.stopPropagation();
+            return _this.togglePage();
+          };
+        })(this));
+      }
+      if (this.options.selectable) {
+        selector = ".dropdown-content a";
+        if (this.dropdown.find(".dropdown-toggle-page").length) {
+          selector = ".dropdown-page-one .dropdown-content a";
+        }
+        this.dropdown.on("click", selector, function(e) {
+          var $el, selected;
+          $el = $(this);
+          selected = self.rowClicked($el);
+          if (self.options.clicked) {
+            self.options.clicked(selected, $el, e);
+          }
+          return $el.trigger('blur');
+        });
+      }
+    }
+
+    GitLabDropdown.prototype.getElement = function(selector) {
+      return this.dropdown.find(selector);
+    };
+
+    GitLabDropdown.prototype.toggleLoading = function() {
+      return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
+    };
+
+    GitLabDropdown.prototype.togglePage = function() {
+      var menu;
+      menu = $('.dropdown-menu', this.dropdown);
+      if (menu.hasClass(PAGE_TWO_CLASS)) {
+        if (this.remote) {
+          this.remote.execute();
+        }
+      }
+      menu.toggleClass(PAGE_TWO_CLASS);
+      return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
+    };
+
+    GitLabDropdown.prototype.parseData = function(data) {
+      var full_html, groupData, html, name;
+      this.renderedData = data;
+      if (this.options.filterable && data.length === 0) {
+        html = [this.noResults()];
+      } else {
+        if (gl.utils.isObject(data)) {
+          html = [];
+          for (name in data) {
+            groupData = data[name];
+            html.push(this.renderItem({
+              header: name
+            }, name));
+            this.renderData(groupData, name).map(function(item) {
+              return html.push(item);
+            });
+          }
+        } else {
+          html = this.renderData(data);
+        }
+      }
+      full_html = this.renderMenu(html);
+      return this.appendMenu(full_html);
+    };
+
+    GitLabDropdown.prototype.renderData = function(data, group) {
+      if (group == null) {
+        group = false;
+      }
+      return data.map((function(_this) {
+        return function(obj, index) {
+          return _this.renderItem(obj, group, index);
+        };
+      })(this));
+    };
+
+    GitLabDropdown.prototype.shouldPropagate = function(e) {
+      var $target;
+      if (this.options.multiSelect) {
+        $target = $(e.target);
+        if (!$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
+          e.stopPropagation();
+          return false;
+        } else {
+          return true;
+        }
+      }
+    };
+
+    GitLabDropdown.prototype.opened = function() {
+      var contentHtml;
+      currentIndex = -1;
+      this.addArrowKeyEvent();
+      if (this.options.setIndeterminateIds) {
+        this.options.setIndeterminateIds.call(this);
+      }
+      if (this.options.setActiveIds) {
+        this.options.setActiveIds.call(this);
+      }
+      if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+        this.parseData(this.fullData);
+      }
+      contentHtml = $('.dropdown-content', this.dropdown).html();
+      if (this.remote && contentHtml === "") {
+        this.remote.execute();
+      }
+      if (this.options.filterable) {
+        this.filterInput.focus();
+      }
+      return this.dropdown.trigger('shown.gl.dropdown');
+    };
+
+    GitLabDropdown.prototype.hidden = function(e) {
+      var $input;
+      this.removeArrayKeyEvent();
+      $input = this.dropdown.find(".dropdown-input-field");
+      if (this.options.filterable) {
+        $input.blur().val("");
+      }
+      if (!this.options.persistWhenHide) {
+        $input.trigger("keyup");
+      }
+      if (this.dropdown.find(".dropdown-toggle-page").length) {
+        $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
+      }
+      if (this.options.hidden) {
+        this.options.hidden.call(this, e);
+      }
+      return this.dropdown.trigger('hidden.gl.dropdown');
+    };
+
+    GitLabDropdown.prototype.renderMenu = function(html) {
+      var menu_html;
+      menu_html = "";
+      if (this.options.renderMenu) {
+        menu_html = this.options.renderMenu(html);
+      } else {
+        menu_html = $('<ul />').append(html);
+      }
+      return menu_html;
+    };
+
+    GitLabDropdown.prototype.appendMenu = function(html) {
+      var selector;
+      selector = '.dropdown-content';
+      if (this.dropdown.find(".dropdown-toggle-page").length) {
+        selector = ".dropdown-page-one .dropdown-content";
+      }
+      return $(selector, this.dropdown).empty().append(html);
+    };
+
+    GitLabDropdown.prototype.renderItem = function(data, group, index) {
+      var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value;
+      if (group == null) {
+        group = false;
+      }
+      if (index == null) {
+        index = false;
+      }
+      html = "";
+      if (data === "divider") {
+        return "<li class='divider'></li>";
+      }
+      if (data === "separator") {
+        return "<li class='separator'></li>";
+      }
+      if (data.header != null) {
+        return "<li class='dropdown-header'>" + data.header + "</li>";
+      }
+      if (this.options.renderRow) {
+        html = this.options.renderRow.call(this.options, data, this);
+      } else {
+        if (!selected) {
+          value = this.options.id ? this.options.id(data) : data.id;
+          fieldName = this.options.fieldName;
+          field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+          if (field.length) {
+            selected = true;
+          }
+        }
+        if (this.options.url != null) {
+          url = this.options.url(data);
+        } else {
+          url = data.url != null ? data.url : '#';
+        }
+        if (this.options.text != null) {
+          text = this.options.text(data);
+        } else {
+          text = data.text != null ? data.text : '';
+        }
+        cssClass = "";
+        if (selected) {
+          cssClass = "is-active";
+        }
+        if (this.highlight) {
+          text = this.highlightTextMatches(text, this.filterInput.val());
+        }
+        if (group) {
+          groupAttrs = "data-group='" + group + "' data-index='" + index + "'";
+        } else {
+          groupAttrs = '';
+        }
+        html = "<li> <a href='" + url + "' " + groupAttrs + " class='" + cssClass + "'> " + text + " </a> </li>";
+      }
+      return html;
+    };
+
+    GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
+      var occurrences;
+      occurrences = fuzzaldrinPlus.match(text, term);
+      return text.split('').map(function(character, i) {
+        if (indexOf.call(occurrences, i) >= 0) {
+          return "<b>" + character + "</b>";
+        } else {
+          return character;
+        }
+      }).join('');
+    };
+
+    GitLabDropdown.prototype.noResults = function() {
+      var html;
+      return html = "<li class='dropdown-menu-empty-link'> <a href='#' class='is-focused'> No matching results. </a> </li>";
+    };
+
+    GitLabDropdown.prototype.highlightRow = function(index) {
+      var selector;
+      if (this.filterInput.val() !== "") {
+        selector = '.dropdown-content li:first-child a';
+        if (this.dropdown.find(".dropdown-toggle-page").length) {
+          selector = ".dropdown-page-one .dropdown-content li:first-child a";
+        }
+        return this.getElement(selector).addClass('is-focused');
+      }
+    };
+
+    GitLabDropdown.prototype.rowClicked = function(el) {
+      var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
+      fieldName = this.options.fieldName;
+      isInput = $(this.el).is('input');
+      if (this.renderedData) {
+        groupName = el.data('group');
+        if (groupName) {
+          selectedIndex = el.data('index');
+          selectedObject = this.renderedData[groupName][selectedIndex];
+        } else {
+          selectedIndex = el.closest('li').index();
+          selectedObject = this.renderedData[selectedIndex];
+        }
+      }
+      value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
+      if (isInput) {
+        field = $(this.el);
+      } else {
+        field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
+      }
+      if (el.hasClass(ACTIVE_CLASS)) {
+        el.removeClass(ACTIVE_CLASS);
+        if (isInput) {
+          field.val('');
+        } else {
+          field.remove();
+        }
+        if (this.options.toggleLabel) {
+          return this.updateLabel(selectedObject, el, this);
+        } else {
+          return selectedObject;
+        }
+      } else if (el.hasClass(INDETERMINATE_CLASS)) {
+        el.addClass(ACTIVE_CLASS);
+        el.removeClass(INDETERMINATE_CLASS);
+        if (value == null) {
+          field.remove();
+        }
+        if (!field.length && fieldName) {
+          this.addInput(fieldName, value);
+        }
+        return selectedObject;
+      } else {
+        if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
+          this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
+          if (!isInput) {
+            this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
+          }
+        }
+        if (value == null) {
+          field.remove();
+        }
+        el.addClass(ACTIVE_CLASS);
+        if (this.options.toggleLabel) {
+          this.updateLabel(selectedObject, el, this);
+        }
+        if (value != null) {
+          if (!field.length && fieldName) {
+            this.addInput(fieldName, value);
+          } else {
+            field.val(value).trigger('change');
+          }
+        }
+        return selectedObject;
+      }
+    };
+
+    GitLabDropdown.prototype.addInput = function(fieldName, value) {
+      var $input;
+      $input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
+      if (this.options.inputId != null) {
+        $input.attr('id', this.options.inputId);
+      }
+      return this.dropdown.before($input);
+    };
+
+    GitLabDropdown.prototype.selectRowAtIndex = function(e, index) {
+      var $el, selector;
+      selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
+      if (this.dropdown.find(".dropdown-toggle-page").length) {
+        selector = ".dropdown-page-one " + selector;
+      }
+      $el = $(selector, this.dropdown);
+      if ($el.length) {
+        e.preventDefault();
+        e.stopImmediatePropagation();
+        return $el.first().trigger('click');
+      }
+    };
+
+    GitLabDropdown.prototype.addArrowKeyEvent = function() {
+      var $input, ARROW_KEY_CODES, selector;
+      ARROW_KEY_CODES = [38, 40];
+      $input = this.dropdown.find(".dropdown-input-field");
+      selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible';
+      if (this.dropdown.find(".dropdown-toggle-page").length) {
+        selector = ".dropdown-page-one " + selector;
+      }
+      return $('body').on('keydown', (function(_this) {
+        return function(e) {
+          var $listItems, PREV_INDEX, currentKeyCode;
+          currentKeyCode = e.which;
+          if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+            PREV_INDEX = currentIndex;
+            $listItems = $(selector, _this.dropdown);
+            if (currentKeyCode === 40) {
+              if (currentIndex < ($listItems.length - 1)) {
+                currentIndex += 1;
+              }
+            } else if (currentKeyCode === 38) {
+              if (currentIndex > 0) {
+                currentIndex -= 1;
+              }
+            }
+            if (currentIndex !== PREV_INDEX) {
+              _this.highlightRowAtIndex($listItems, currentIndex);
+            }
+            return false;
+          }
+          if (currentKeyCode === 13 && currentIndex !== -1) {
+            return _this.selectRowAtIndex(e, $('.is-focused', _this.dropdown).closest('li').index() - 1);
+          }
+        };
+      })(this));
+    };
+
+    GitLabDropdown.prototype.removeArrayKeyEvent = function() {
+      return $('body').off('keydown');
+    };
+
+    GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
+      var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
+      $('.is-focused', this.dropdown).removeClass('is-focused');
+      $listItem = $listItems.eq(index);
+      $listItem.find('a:first-child').addClass("is-focused");
+      $dropdownContent = $listItem.closest('.dropdown-content');
+      dropdownScrollTop = $dropdownContent.scrollTop();
+      dropdownContentHeight = $dropdownContent.outerHeight();
+      dropdownContentTop = $dropdownContent.prop('offsetTop');
+      dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
+      listItemHeight = $listItem.outerHeight();
+      listItemTop = $listItem.prop('offsetTop');
+      listItemBottom = listItemTop + listItemHeight;
+      if (listItemBottom > dropdownContentBottom + dropdownScrollTop) {
+        return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom);
+      } else if (listItemTop < dropdownContentTop + dropdownScrollTop) {
+        return $dropdownContent.scrollTop(listItemTop - dropdownContentTop);
+      }
+    };
+
+    GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
+      if (selected == null) {
+        selected = null;
+      }
+      if (el == null) {
+        el = null;
+      }
+      if (instance == null) {
+        instance = null;
+      }
+      return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
+    };
+
+    return GitLabDropdown;
+
+  })();
+
+  $.fn.glDropdown = function(opts) {
+    return this.each(function() {
+      if (!$.data(this, 'glDropdown')) {
+        return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
+      }
+    });
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
deleted file mode 100644
index ab41caa28f857c8f80dfa79a5d85fddcc1870f10..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ /dev/null
@@ -1,622 +0,0 @@
-class GitLabDropdownFilter
-  BLUR_KEYCODES = [27, 40]
-  ARROW_KEY_CODES = [38, 40]
-  HAS_VALUE_CLASS = "has-value"
-
-  constructor: (@input, @options) ->
-    {
-      @filterInputBlur = true
-    } = @options
-
-    $inputContainer = @input.parent()
-    $clearButton = $inputContainer.find('.js-dropdown-input-clear')
-
-    @indeterminateIds = []
-
-    # Clear click
-    $clearButton.on 'click', (e) =>
-      e.preventDefault()
-      e.stopPropagation()
-      @input
-        .val('')
-        .trigger('keyup')
-        .focus()
-
-    # Key events
-    timeout = ""
-    @input
-      .on 'keydown', (e) ->
-        keyCode = e.which
-        e.preventDefault() if keyCode is 13
-      .on 'keyup', (e) =>
-        keyCode = e.which
-
-        return unless ARROW_KEY_CODES.indexOf(keyCode) is -1
-
-        if @input.val() isnt '' and !$inputContainer.hasClass HAS_VALUE_CLASS
-          $inputContainer.addClass HAS_VALUE_CLASS
-        else if @input.val() is '' and $inputContainer.hasClass HAS_VALUE_CLASS
-          $inputContainer.removeClass HAS_VALUE_CLASS
-
-        # Only filter asynchronously only if option remote is set
-        if @options.remote
-          clearTimeout timeout
-          timeout = setTimeout =>
-            blur_field = @shouldBlur keyCode
-
-            if blur_field and @filterInputBlur
-              @input.blur()
-
-            @options.query @input.val(), (data) =>
-              @options.callback(data)
-          , 250
-        else
-          @filter @input.val()
-
-  shouldBlur: (keyCode) ->
-    return BLUR_KEYCODES.indexOf(keyCode) >= 0
-
-  filter: (search_text) ->
-    @options.onFilter(search_text) if @options.onFilter
-    data = @options.data()
-
-    if data? and not @options.filterByText
-      results = data
-
-      if search_text isnt ''
-        # When data is an array of objects therefore [object Array] e.g.
-        # [
-        #   { prop: 'foo' },
-        #   { prop: 'baz' }
-        # ]
-        if _.isArray(data)
-          results = fuzzaldrinPlus.filter(data, search_text,
-            key: @options.keys
-          )
-        else
-          # If data is grouped therefore an [object Object]. e.g.
-          # {
-          #   groupName1: [
-          #     { prop: 'foo' },
-          #     { prop: 'baz' }
-          #   ],
-          #   groupName2: [
-          #     { prop: 'abc' },
-          #     { prop: 'def' }
-          #   ]
-          # }
-          if gl.utils.isObject data
-            results = {}
-            for key, group of data
-              tmp = fuzzaldrinPlus.filter(group, search_text,
-                key: @options.keys
-              )
-
-              if tmp.length
-                results[key] = tmp.map (item) -> item
-
-      @options.callback results
-    else
-      elements = @options.elements()
-
-      if search_text
-        elements.each ->
-          $el = $(@)
-          matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
-
-          unless $el.is('.dropdown-header')
-            if matches.length
-              $el.show()
-            else
-              $el.hide()
-      else
-        elements.show()
-
-class GitLabDropdownRemote
-  constructor: (@dataEndpoint, @options) ->
-
-  execute: ->
-    if typeof @dataEndpoint is "string"
-      @fetchData()
-    else if typeof @dataEndpoint is "function"
-      if @options.beforeSend
-        @options.beforeSend()
-
-      # Fetch the data by calling the data funcfion
-      @dataEndpoint "", (data) =>
-        if @options.success
-          @options.success(data)
-
-        if @options.beforeSend
-          @options.beforeSend()
-
-  # Fetch the data through ajax if the data is a string
-  fetchData: ->
-    $.ajax(
-      url: @dataEndpoint,
-      dataType: @options.dataType,
-      beforeSend: =>
-        if @options.beforeSend
-          @options.beforeSend()
-      success: (data) =>
-        if @options.success
-          @options.success(data)
-    )
-
-class GitLabDropdown
-  LOADING_CLASS = "is-loading"
-  PAGE_TWO_CLASS = "is-page-two"
-  ACTIVE_CLASS = "is-active"
-  INDETERMINATE_CLASS = "is-indeterminate"
-  currentIndex = -1
-
-  FILTER_INPUT = '.dropdown-input .dropdown-input-field'
-
-  constructor: (@el, @options) ->
-    self = @
-    selector = $(@el).data "target"
-    @dropdown = if selector? then $(selector) else $(@el).parent()
-
-    # Set Defaults
-    {
-      # If no input is passed create a default one
-      @filterInput = @getElement(FILTER_INPUT)
-      @highlight = false
-      @filterInputBlur = true
-    } = @options
-
-    self = @
-
-    # If selector was passed
-    if _.isString(@filterInput)
-      @filterInput = @getElement(@filterInput)
-
-    searchFields = if @options.search then @options.search.fields else [];
-
-    if @options.data
-      # If we provided data
-      # data could be an array of objects or a group of arrays
-      if _.isObject(@options.data) and not _.isFunction(@options.data)
-        @fullData = @options.data
-        @parseData @options.data
-      else
-        # Remote data
-        @remote = new GitLabDropdownRemote @options.data, {
-          dataType: @options.dataType,
-          beforeSend: @toggleLoading.bind(@)
-          success: (data) =>
-            @fullData = data
-
-            @parseData @fullData
-
-            @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
-        }
-
-    # Init filterable
-    if @options.filterable
-      @filter = new GitLabDropdownFilter @filterInput,
-        filterInputBlur: @filterInputBlur
-        filterByText: @options.filterByText
-        onFilter: @options.onFilter
-        remote: @options.filterRemote
-        query: @options.data
-        keys: searchFields
-        elements: =>
-          selector = '.dropdown-content li:not(.divider)'
-
-          if @dropdown.find('.dropdown-toggle-page').length
-            selector = ".dropdown-page-one #{selector}"
-
-          return $(selector)
-        data: =>
-          return @fullData
-        callback: (data) =>
-          currentIndex = -1
-          @parseData data
-
-    # Event listeners
-
-    @dropdown.on "shown.bs.dropdown", @opened
-    @dropdown.on "hidden.bs.dropdown", @hidden
-    $(@el).on "update.label", @updateLabel
-    @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
-    @dropdown.on 'keyup', (e) =>
-      if e.which is 27 # Escape key
-        $('.dropdown-menu-close', @dropdown).trigger 'click'
-    @dropdown.on 'blur', 'a', (e) =>
-      if e.relatedTarget?
-        $relatedTarget = $(e.relatedTarget)
-        $dropdownMenu = $relatedTarget.closest('.dropdown-menu')
-
-        if $dropdownMenu.length is 0
-          @dropdown.removeClass('open')
-
-    if @dropdown.find(".dropdown-toggle-page").length
-      @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
-        e.preventDefault()
-        e.stopPropagation()
-
-        @togglePage()
-
-    if @options.selectable
-      selector = ".dropdown-content a"
-
-      if @dropdown.find(".dropdown-toggle-page").length
-        selector = ".dropdown-page-one .dropdown-content a"
-
-      @dropdown.on "click", selector, (e) ->
-        $el = $(@)
-        selected = self.rowClicked $el
-
-        if self.options.clicked
-          self.options.clicked(selected, $el, e)
-
-  # Finds an element inside wrapper element
-  getElement: (selector) ->
-    @dropdown.find selector
-
-  toggleLoading: ->
-    $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
-
-  togglePage: ->
-    menu = $('.dropdown-menu', @dropdown)
-
-    if menu.hasClass(PAGE_TWO_CLASS)
-      if @remote
-        @remote.execute()
-
-    menu.toggleClass PAGE_TWO_CLASS
-
-    # Focus first visible input on active page
-    @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
-
-  parseData: (data) ->
-    @renderedData = data
-
-    if @options.filterable and data.length is 0
-      # render no matching results
-      html = [@noResults()]
-    else
-      # Handle array groups
-      if gl.utils.isObject data
-        html = []
-        for name, groupData of data
-          # Add header for each group
-          html.push(@renderItem(header: name, name))
-
-          @renderData(groupData, name)
-            .map (item) ->
-              html.push item
-      else
-        # Render each row
-        html = @renderData(data)
-
-    # Render the full menu
-    full_html = @renderMenu(html)
-
-    @appendMenu(full_html)
-
-  renderData: (data, group = false) ->
-    data.map (obj, index) =>
-      return @renderItem(obj, group, index)
-
-  shouldPropagate: (e) =>
-    if @options.multiSelect
-      $target = $(e.target)
-
-      if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
-        e.stopPropagation()
-        return false
-      else
-        return true
-
-  opened: =>
-    currentIndex = -1
-    @addArrowKeyEvent()
-
-    if @options.setIndeterminateIds
-      @options.setIndeterminateIds.call(@)
-
-    if @options.setActiveIds
-      @options.setActiveIds.call(@)
-
-    # Makes indeterminate items effective
-    if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
-      @parseData @fullData
-
-    contentHtml = $('.dropdown-content', @dropdown).html()
-    if @remote && contentHtml is ""
-      @remote.execute()
-
-    if @options.filterable
-      @filterInput.focus()
-
-    @dropdown.trigger('shown.gl.dropdown')
-
-  hidden: (e) =>
-    @removeArrayKeyEvent()
-
-    $input = @dropdown.find(".dropdown-input-field")
-
-    if @options.filterable
-      $input
-        .blur()
-        .val("")
-
-    # Triggering 'keyup' will re-render the dropdown which is not always required
-    # specially if we want to keep the state of the dropdown needed for bulk-assignment
-    if not @options.persistWhenHide
-      $input.trigger("keyup")
-
-    if @dropdown.find(".dropdown-toggle-page").length
-      $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
-
-    if @options.hidden
-      @options.hidden.call(@,e)
-
-    @dropdown.trigger('hidden.gl.dropdown')
-
-
-  # Render the full menu
-  renderMenu: (html) ->
-    menu_html = ""
-
-    if @options.renderMenu
-      menu_html = @options.renderMenu(html)
-    else
-      menu_html = $('<ul />')
-        .append(html)
-
-    return menu_html
-
-  # Append the menu into the dropdown
-  appendMenu: (html) ->
-    selector = '.dropdown-content'
-    if @dropdown.find(".dropdown-toggle-page").length
-      selector = ".dropdown-page-one .dropdown-content"
-    $(selector, @dropdown)
-      .empty()
-      .append(html)
-
-  # Render the row
-  renderItem: (data, group = false, index = false) ->
-    html = ""
-
-    # Divider
-    return "<li class='divider'></li>" if data is "divider"
-
-    # Separator is a full-width divider
-    return "<li class='separator'></li>" if data is "separator"
-
-    # Header
-    return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
-
-    if @options.renderRow
-      # Call the render function
-      html = @options.renderRow.call(@options, data, @)
-    else
-      if not selected
-        value = if @options.id then @options.id(data) else data.id
-        fieldName = @options.fieldName
-        field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-        if field.length
-          selected = true
-
-      # Set URL
-      if @options.url?
-        url = @options.url(data)
-      else
-        url = if data.url? then data.url else '#'
-
-      # Set Text
-      if @options.text?
-        text = @options.text(data)
-      else
-        text = if data.text? then data.text else ''
-
-      cssClass = "";
-
-      if selected
-        cssClass = "is-active"
-
-      if @highlight
-        text = @highlightTextMatches(text, @filterInput.val())
-
-      if group
-        groupAttrs = "data-group='#{group}' data-index='#{index}'"
-      else
-        groupAttrs = ''
-
-      html = "<li>
-        <a href='#{url}' #{groupAttrs} class='#{cssClass}'>
-          #{text}
-        </a>
-      </li>"
-
-    return html
-
-  highlightTextMatches: (text, term) ->
-    occurrences = fuzzaldrinPlus.match(text, term)
-    text.split('').map((character, i) ->
-      if i in occurrences then "<b>#{character}</b>" else character
-    ).join('')
-
-  noResults: ->
-    html = "<li class='dropdown-menu-empty-link'>
-      <a href='#' class='is-focused'>
-        No matching results.
-      </a>
-    </li>"
-
-  highlightRow: (index) ->
-    if @filterInput.val() isnt ""
-      selector = '.dropdown-content li:first-child a'
-      if @dropdown.find(".dropdown-toggle-page").length
-        selector = ".dropdown-page-one .dropdown-content li:first-child a"
-
-      @getElement(selector).addClass 'is-focused'
-
-  rowClicked: (el) ->
-    fieldName = @options.fieldName
-    isInput = $(@el).is('input')
-
-    if @renderedData
-      groupName = el.data('group')
-      if groupName
-        selectedIndex = el.data('index')
-        selectedObject = @renderedData[groupName][selectedIndex]
-      else
-        selectedIndex = el.closest('li').index()
-        selectedObject = @renderedData[selectedIndex]
-
-    value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
-
-    if isInput
-      field = $(@el)
-    else
-      field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
-
-    if el.hasClass(ACTIVE_CLASS)
-      el.removeClass(ACTIVE_CLASS)
-
-      if isInput
-        field.val('')
-      else
-        field.remove()
-
-      # Toggle the dropdown label
-      if @options.toggleLabel
-        @updateLabel(selectedObject, el, @)
-      else
-        selectedObject
-    else if el.hasClass(INDETERMINATE_CLASS)
-      el.addClass ACTIVE_CLASS
-      el.removeClass INDETERMINATE_CLASS
-
-      if not value?
-        field.remove()
-
-      if not field.length and fieldName
-        @addInput(fieldName, value)
-
-      return selectedObject
-    else
-      if not @options.multiSelect or el.hasClass('dropdown-clear-active')
-        @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
-
-        unless isInput
-          @dropdown.parent().find("input[name='#{fieldName}']").remove()
-
-      if !value?
-        field.remove()
-
-      # Toggle active class for the tick mark
-      el.addClass ACTIVE_CLASS
-
-      # Toggle the dropdown label
-      if @options.toggleLabel
-        @updateLabel(selectedObject, el, @)
-      if value?
-        if !field.length and fieldName
-          @addInput(fieldName, value)
-        else
-          field
-            .val value
-            .trigger 'change'
-
-      return selectedObject
-
-  addInput: (fieldName, value)->
-    # Create hidden input for form
-    $input = $('<input>').attr('type', 'hidden')
-                         .attr('name', fieldName)
-                        .val(value)
-
-    if @options.inputId?
-      $input.attr('id', @options.inputId)
-
-    @dropdown.before $input
-
-  selectRowAtIndex: (e, index) ->
-    selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(#{index}) a"
-
-    if @dropdown.find(".dropdown-toggle-page").length
-      selector = ".dropdown-page-one #{selector}"
-
-    # simulate a click on the first link
-    $el = $(selector, @dropdown)
-
-    if $el.length
-      e.preventDefault()
-      e.stopImmediatePropagation()
-      $el.first().trigger('click')
-
-  addArrowKeyEvent: ->
-    ARROW_KEY_CODES = [38, 40]
-    $input = @dropdown.find(".dropdown-input-field")
-
-    selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible'
-    if @dropdown.find(".dropdown-toggle-page").length
-      selector = ".dropdown-page-one #{selector}"
-
-    $('body').on 'keydown', (e) =>
-      currentKeyCode = e.which
-
-      if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
-        e.preventDefault()
-        e.stopImmediatePropagation()
-
-        PREV_INDEX = currentIndex
-        $listItems = $(selector, @dropdown)
-
-        if currentKeyCode is 40
-          # Move down
-          currentIndex += 1 if currentIndex < ($listItems.length - 1)
-        else if currentKeyCode is 38
-          # Move up
-          currentIndex -= 1 if currentIndex > 0
-
-        @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
-
-        return false
-
-      if currentKeyCode is 13 and currentIndex isnt -1
-        @selectRowAtIndex e, $('.is-focused', @dropdown).closest('li').index() - 1
-
-  removeArrayKeyEvent: ->
-    $('body').off 'keydown'
-
-  highlightRowAtIndex: ($listItems, index) ->
-    # Remove the class for the previously focused row
-    $('.is-focused', @dropdown).removeClass 'is-focused'
-
-    # Update the class for the row at the specific index
-    $listItem = $listItems.eq(index)
-    $listItem.find('a:first-child').addClass "is-focused"
-
-    # Dropdown content scroll area
-    $dropdownContent = $listItem.closest('.dropdown-content')
-    dropdownScrollTop = $dropdownContent.scrollTop()
-    dropdownContentHeight = $dropdownContent.outerHeight()
-    dropdownContentTop = $dropdownContent.prop('offsetTop')
-    dropdownContentBottom = dropdownContentTop + dropdownContentHeight
-
-    # Get the offset bottom of the list item
-    listItemHeight = $listItem.outerHeight()
-    listItemTop = $listItem.prop('offsetTop')
-    listItemBottom = listItemTop + listItemHeight
-
-    if listItemBottom > dropdownContentBottom + dropdownScrollTop
-      # Scroll the dropdown content down
-      $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
-    else if listItemTop < dropdownContentTop + dropdownScrollTop
-      # Scroll the dropdown content up
-      $dropdownContent.scrollTop(listItemTop - dropdownContentTop)
-
-  updateLabel: (selected = null, el = null, instance = null) =>
-    $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
-
-$.fn.glDropdown = (opts) ->
-  return @.each ->
-    if (!$.data @, 'glDropdown')
-      $.data(@, 'glDropdown', new GitLabDropdown @, opts)
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..528a673eb15c07a341806fd35d1ca9452a953b52
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js
@@ -0,0 +1,53 @@
+(function() {
+  this.GLForm = (function() {
+    function GLForm(form) {
+      this.form = form;
+      this.textarea = this.form.find('textarea.js-gfm-input');
+      this.destroy();
+      this.setupForm();
+      this.form.data('gl-form', this);
+    }
+
+    GLForm.prototype.destroy = function() {
+      this.clearEventListeners();
+      return this.form.data('gl-form', null);
+    };
+
+    GLForm.prototype.setupForm = function() {
+      var isNewForm;
+      isNewForm = this.form.is(':not(.gfm-form)');
+      this.form.removeClass('js-new-note-form');
+      if (isNewForm) {
+        this.form.find('.div-dropzone').remove();
+        this.form.addClass('gfm-form');
+        disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+        GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
+        new DropzoneInput(this.form);
+        autosize(this.textarea);
+        this.addEventListeners();
+        gl.text.init(this.form);
+      }
+      this.form.find('.js-note-discard').hide();
+      return this.form.show();
+    };
+
+    GLForm.prototype.clearEventListeners = function() {
+      this.textarea.off('focus');
+      this.textarea.off('blur');
+      return gl.text.removeListeners(this.form);
+    };
+
+    GLForm.prototype.addEventListeners = function() {
+      this.textarea.on('focus', function() {
+        return $(this).closest('.md-area').addClass('is-focused');
+      });
+      return this.textarea.on('blur', function() {
+        return $(this).closest('.md-area').removeClass('is-focused');
+      });
+    };
+
+    return GLForm;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
deleted file mode 100644
index 77512d187c9537ec6a767703c02079589df3bd3e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/gl_form.js.coffee
+++ /dev/null
@@ -1,54 +0,0 @@
-class @GLForm
-  constructor: (@form) ->
-    @textarea = @form.find('textarea.js-gfm-input')
-
-    # Before we start, we should clean up any previous data for this form
-    @destroy()
-
-    # Setup the form
-    @setupForm()
-
-    @form.data 'gl-form', @
-
-  destroy: ->
-    # Clean form listeners
-    @clearEventListeners()
-    @form.data 'gl-form', null
-
-  setupForm: ->
-    isNewForm = @form.is(':not(.gfm-form)')
-
-    @form.removeClass 'js-new-note-form'
-
-    if isNewForm
-      @form.find('.div-dropzone').remove()
-      @form.addClass('gfm-form')
-      disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
-
-      # remove notify commit author checkbox for non-commit notes
-      GitLab.GfmAutoComplete.setup()
-      new DropzoneInput(@form)
-
-      autosize(@textarea)
-
-      # form and textarea event listeners
-      @addEventListeners()
-
-      gl.text.init(@form)
-
-    # hide discard button
-    @form.find('.js-note-discard').hide()
-
-    @form.show()
-
-  clearEventListeners: ->
-    @textarea.off 'focus'
-    @textarea.off 'blur'
-    gl.text.removeListeners(@form)
-
-  addEventListeners: ->
-    @textarea.on 'focus', ->
-      $(@).closest('.md-area').addClass 'is-focused'
-
-    @textarea.on 'blur', ->
-      $(@).closest('.md-area').removeClass 'is-focused'
diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/application.js.coffee
deleted file mode 100644
index e0f681acf0b53315089c45a62a5103e2d969a191..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/graphs/application.js.coffee
+++ /dev/null
@@ -1,7 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require_tree .
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..b95faadc8e72f17e7cdb90eab9203622916bee02
--- /dev/null
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -0,0 +1,7 @@
+
+/*= require_tree . */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
new file mode 100644
index 0000000000000000000000000000000000000000..f041980bc199189fb36599a9f0a366e86b06d898
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph.js
@@ -0,0 +1,19 @@
+(function() {
+  this.StatGraph = (function() {
+    function StatGraph() {}
+
+    StatGraph.log = {};
+
+    StatGraph.get_log = function() {
+      return this.log;
+    };
+
+    StatGraph.set_log = function(data) {
+      return this.log = data;
+    };
+
+    return StatGraph;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph.js.coffee
deleted file mode 100644
index f36c71fd25e51f113c721019a9cc3badfdfcdfc2..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/graphs/stat_graph.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-class @StatGraph  
-  @log: {}
-  @get_log: ->
-    @log
-  @set_log: (data) ->
-    @log = data
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
new file mode 100644
index 0000000000000000000000000000000000000000..927d241b35745287305c3aab7988e180894b43e4
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -0,0 +1,112 @@
+
+/*= require d3 */
+
+(function() {
+  this.ContributorsStatGraph = (function() {
+    function ContributorsStatGraph() {}
+
+    ContributorsStatGraph.prototype.init = function(log) {
+      var author_commits, total_commits;
+      this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
+      this.set_current_field("commits");
+      total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+      author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
+      this.add_master_graph(total_commits);
+      this.add_authors_graph(author_commits);
+      return this.change_date_header();
+    };
+
+    ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
+      this.master_graph = new ContributorsMasterGraph(total_data);
+      return this.master_graph.draw();
+    };
+
+    ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
+      var limited_author_data;
+      this.authors = [];
+      limited_author_data = author_data.slice(0, 100);
+      return _.each(limited_author_data, (function(_this) {
+        return function(d) {
+          var author_graph, author_header;
+          author_header = _this.create_author_header(d);
+          $(".contributors-list").append(author_header);
+          _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
+          return author_graph.draw();
+        };
+      })(this));
+    };
+
+    ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
+      var commits;
+      commits = $('<span/>', {
+        "class": 'graph-author-commits-count'
+      });
+      commits.text(author.commits + " commits");
+      return $('<span/>').append(commits);
+    };
+
+    ContributorsStatGraph.prototype.create_author_header = function(author) {
+      var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
+      list_item = $('<li/>', {
+        "class": 'person',
+        style: 'display: block;'
+      });
+      author_name = $('<h4>' + author.author_name + '</h4>');
+      author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
+      author_commit_info_span = $('<span/>', {
+        "class": 'commits'
+      });
+      author_commit_info = this.format_author_commit_info(author);
+      author_commit_info_span.html(author_commit_info);
+      list_item.append(author_name);
+      list_item.append(author_email);
+      list_item.append(author_commit_info_span);
+      return list_item;
+    };
+
+    ContributorsStatGraph.prototype.redraw_master = function() {
+      var total_data;
+      total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+      this.master_graph.set_data(total_data);
+      return this.master_graph.redraw();
+    };
+
+    ContributorsStatGraph.prototype.redraw_authors = function() {
+      var author_commits, x_domain;
+      $("ol").html("");
+      x_domain = ContributorsGraph.prototype.x_domain;
+      author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
+      return _.each(author_commits, (function(_this) {
+        return function(d) {
+          _this.redraw_author_commit_info(d);
+          $(_this.authors[d.author_name].list_item).appendTo("ol");
+          _this.authors[d.author_name].set_data(d.dates);
+          return _this.authors[d.author_name].redraw();
+        };
+      })(this));
+    };
+
+    ContributorsStatGraph.prototype.set_current_field = function(field) {
+      return this.field = field;
+    };
+
+    ContributorsStatGraph.prototype.change_date_header = function() {
+      var print, print_date_format, x_domain;
+      x_domain = ContributorsGraph.prototype.x_domain;
+      print_date_format = d3.time.format("%B %e %Y");
+      print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
+      return $("#date_header").text(print);
+    };
+
+    ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
+      var author_commit_info, author_list_item;
+      author_list_item = $(this.authors[author.author_name].list_item);
+      author_commit_info = this.format_author_commit_info(author);
+      return author_list_item.find("span").html(author_commit_info);
+    };
+
+    return ContributorsStatGraph;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
deleted file mode 100644
index 1d9fae7cf79d81bbe66d4e6df1a56eb74b7d8e1f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js.coffee
+++ /dev/null
@@ -1,71 +0,0 @@
-#= require d3
-
-class @ContributorsStatGraph
-  init: (log) ->
-    @parsed_log = ContributorsStatGraphUtil.parse_log(log)
-    @set_current_field("commits")
-    total_commits = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
-    author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field)
-    @add_master_graph(total_commits)
-    @add_authors_graph(author_commits)
-    @change_date_header()
-  add_master_graph: (total_data) ->
-    @master_graph = new ContributorsMasterGraph(total_data)
-    @master_graph.draw()
-  add_authors_graph: (author_data) ->
-    @authors = []
-    limited_author_data = author_data.slice(0, 100)
-    _.each(limited_author_data, (d) =>
-      author_header = @create_author_header(d)
-      $(".contributors-list").append(author_header)
-      @authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates)
-      author_graph.draw()
-    )
-  format_author_commit_info: (author) ->
-    commits = $('<span/>', {
-      class: 'graph-author-commits-count'
-    })
-    commits.text(author.commits + " commits")
-    $('<span/>').append(commits)
-
-  create_author_header: (author) ->
-    list_item = $('<li/>', {
-      class: 'person'
-      style: 'display: block;'
-    })
-    author_name = $('<h4>' + author.author_name + '</h4>')
-    author_email = $('<p class="graph-author-email">' + author.author_email + '</p>')
-    author_commit_info_span = $('<span/>', {
-      class: 'commits'
-    })
-    author_commit_info = @format_author_commit_info(author)
-    author_commit_info_span.html(author_commit_info)
-    list_item.append(author_name)
-    list_item.append(author_email)
-    list_item.append(author_commit_info_span)
-    list_item
-  redraw_master: ->
-    total_data = ContributorsStatGraphUtil.get_total_data(@parsed_log, @field)
-    @master_graph.set_data(total_data)
-    @master_graph.redraw()
-  redraw_authors: ->
-    $("ol").html("")
-    x_domain = ContributorsGraph.prototype.x_domain
-    author_commits = ContributorsStatGraphUtil.get_author_data(@parsed_log, @field, x_domain)
-    _.each(author_commits, (d) =>
-      @redraw_author_commit_info(d)
-      $(@authors[d.author_name].list_item).appendTo("ol")
-      @authors[d.author_name].set_data(d.dates)
-      @authors[d.author_name].redraw()
-    )
-  set_current_field: (field) ->
-    @field = field
-  change_date_header: ->
-    x_domain = ContributorsGraph.prototype.x_domain
-    print_date_format = d3.time.format("%B %e %Y")
-    print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1])
-    $("#date_header").text(print)
-  redraw_author_commit_info: (author) ->
-    author_list_item = $(@authors[author.author_name].list_item)
-    author_commit_info = @format_author_commit_info(author)
-    author_list_item.find("span").html(author_commit_info)
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
new file mode 100644
index 0000000000000000000000000000000000000000..a646ca1d84f00fb45ee004bd060adc960c9c7be8
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -0,0 +1,279 @@
+
+/*= require d3 */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ContributorsGraph = (function() {
+    function ContributorsGraph() {}
+
+    ContributorsGraph.prototype.MARGIN = {
+      top: 20,
+      right: 20,
+      bottom: 30,
+      left: 50
+    };
+
+    ContributorsGraph.prototype.x_domain = null;
+
+    ContributorsGraph.prototype.y_domain = null;
+
+    ContributorsGraph.prototype.dates = [];
+
+    ContributorsGraph.set_x_domain = function(data) {
+      return ContributorsGraph.prototype.x_domain = data;
+    };
+
+    ContributorsGraph.set_y_domain = function(data) {
+      return ContributorsGraph.prototype.y_domain = [
+        0, d3.max(data, function(d) {
+          var ref, ref1;
+          return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+        })
+      ];
+    };
+
+    ContributorsGraph.init_x_domain = function(data) {
+      return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
+        return d.date;
+      });
+    };
+
+    ContributorsGraph.init_y_domain = function(data) {
+      return ContributorsGraph.prototype.y_domain = [
+        0, d3.max(data, function(d) {
+          var ref, ref1;
+          return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+        })
+      ];
+    };
+
+    ContributorsGraph.init_domain = function(data) {
+      ContributorsGraph.init_x_domain(data);
+      return ContributorsGraph.init_y_domain(data);
+    };
+
+    ContributorsGraph.set_dates = function(data) {
+      return ContributorsGraph.prototype.dates = data;
+    };
+
+    ContributorsGraph.prototype.set_x_domain = function() {
+      return this.x.domain(this.x_domain);
+    };
+
+    ContributorsGraph.prototype.set_y_domain = function() {
+      return this.y.domain(this.y_domain);
+    };
+
+    ContributorsGraph.prototype.set_domain = function() {
+      this.set_x_domain();
+      return this.set_y_domain();
+    };
+
+    ContributorsGraph.prototype.create_scale = function(width, height) {
+      this.x = d3.time.scale().range([0, width]).clamp(true);
+      return this.y = d3.scale.linear().range([height, 0]).nice();
+    };
+
+    ContributorsGraph.prototype.draw_x_axis = function() {
+      return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
+    };
+
+    ContributorsGraph.prototype.draw_y_axis = function() {
+      return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
+    };
+
+    ContributorsGraph.prototype.set_data = function(data) {
+      return this.data = data;
+    };
+
+    return ContributorsGraph;
+
+  })();
+
+  this.ContributorsMasterGraph = (function(superClass) {
+    extend(ContributorsMasterGraph, superClass);
+
+    function ContributorsMasterGraph(data1) {
+      this.data = data1;
+      this.update_content = bind(this.update_content, this);
+      this.width = $('.content').width() - 70;
+      this.height = 200;
+      this.x = null;
+      this.y = null;
+      this.x_axis = null;
+      this.y_axis = null;
+      this.area = null;
+      this.svg = null;
+      this.brush = null;
+      this.x_max_domain = null;
+    }
+
+    ContributorsMasterGraph.prototype.process_dates = function(data) {
+      var dates;
+      dates = this.get_dates(data);
+      this.parse_dates(data);
+      return ContributorsGraph.set_dates(dates);
+    };
+
+    ContributorsMasterGraph.prototype.get_dates = function(data) {
+      return _.pluck(data, 'date');
+    };
+
+    ContributorsMasterGraph.prototype.parse_dates = function(data) {
+      var parseDate;
+      parseDate = d3.time.format("%Y-%m-%d").parse;
+      return data.forEach(function(d) {
+        return d.date = parseDate(d.date);
+      });
+    };
+
+    ContributorsMasterGraph.prototype.create_scale = function() {
+      return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
+    };
+
+    ContributorsMasterGraph.prototype.create_axes = function() {
+      this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
+      return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+    };
+
+    ContributorsMasterGraph.prototype.create_svg = function() {
+      return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+    };
+
+    ContributorsMasterGraph.prototype.create_area = function(x, y) {
+      return this.area = d3.svg.area().x(function(d) {
+        return x(d.date);
+      }).y0(this.height).y1(function(d) {
+        var ref, ref1, xa;
+        xa = d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
+        return y(xa);
+      }).interpolate("basis");
+    };
+
+    ContributorsMasterGraph.prototype.create_brush = function() {
+      return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
+    };
+
+    ContributorsMasterGraph.prototype.draw_path = function(data) {
+      return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
+    };
+
+    ContributorsMasterGraph.prototype.add_brush = function() {
+      return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
+    };
+
+    ContributorsMasterGraph.prototype.update_content = function() {
+      ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
+      return $("#brush_change").trigger('change');
+    };
+
+    ContributorsMasterGraph.prototype.draw = function() {
+      this.process_dates(this.data);
+      this.create_scale();
+      this.create_axes();
+      ContributorsGraph.init_domain(this.data);
+      this.x_max_domain = this.x_domain;
+      this.set_domain();
+      this.create_area(this.x, this.y);
+      this.create_svg();
+      this.create_brush();
+      this.draw_path(this.data);
+      this.draw_x_axis();
+      this.draw_y_axis();
+      return this.add_brush();
+    };
+
+    ContributorsMasterGraph.prototype.redraw = function() {
+      this.process_dates(this.data);
+      ContributorsGraph.set_y_domain(this.data);
+      this.set_y_domain();
+      this.svg.select("path").datum(this.data);
+      this.svg.select("path").attr("d", this.area);
+      return this.svg.select(".y.axis").call(this.y_axis);
+    };
+
+    return ContributorsMasterGraph;
+
+  })(ContributorsGraph);
+
+  this.ContributorsAuthorGraph = (function(superClass) {
+    extend(ContributorsAuthorGraph, superClass);
+
+    function ContributorsAuthorGraph(data1) {
+      this.data = data1;
+      if ($(window).width() < 768) {
+        this.width = $('.content').width() - 80;
+      } else {
+        this.width = ($('.content').width() / 2) - 100;
+      }
+      this.height = 200;
+      this.x = null;
+      this.y = null;
+      this.x_axis = null;
+      this.y_axis = null;
+      this.area = null;
+      this.svg = null;
+      this.list_item = null;
+    }
+
+    ContributorsAuthorGraph.prototype.create_scale = function() {
+      return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
+    };
+
+    ContributorsAuthorGraph.prototype.create_axes = function() {
+      this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
+      return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+    };
+
+    ContributorsAuthorGraph.prototype.create_area = function(x, y) {
+      return this.area = d3.svg.area().x(function(d) {
+        var parseDate;
+        parseDate = d3.time.format("%Y-%m-%d").parse;
+        return x(parseDate(d));
+      }).y0(this.height).y1((function(_this) {
+        return function(d) {
+          if (_this.data[d] != null) {
+            return y(_this.data[d]);
+          } else {
+            return y(0);
+          }
+        };
+      })(this)).interpolate("basis");
+    };
+
+    ContributorsAuthorGraph.prototype.create_svg = function() {
+      this.list_item = d3.selectAll(".person")[0].pop();
+      return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+    };
+
+    ContributorsAuthorGraph.prototype.draw_path = function(data) {
+      return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
+    };
+
+    ContributorsAuthorGraph.prototype.draw = function() {
+      this.create_scale();
+      this.create_axes();
+      this.set_domain();
+      this.create_area(this.x, this.y);
+      this.create_svg();
+      this.draw_path(this.dates);
+      this.draw_x_axis();
+      return this.draw_y_axis();
+    };
+
+    ContributorsAuthorGraph.prototype.redraw = function() {
+      this.set_domain();
+      this.svg.select("path").datum(this.dates);
+      this.svg.select("path").attr("d", this.area);
+      this.svg.select(".x.axis").call(this.x_axis);
+      return this.svg.select(".y.axis").call(this.y_axis);
+    };
+
+    return ContributorsAuthorGraph;
+
+  })(ContributorsGraph);
+
+}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
deleted file mode 100644
index 834a81af459c4c3ea5587489a6c0db989c8d201e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
+++ /dev/null
@@ -1,173 +0,0 @@
-#= require d3
-
-class @ContributorsGraph
-  MARGIN:
-    top: 20
-    right: 20
-    bottom: 30
-    left: 50
-  x_domain: null
-  y_domain: null
-  dates: []
-  @set_x_domain: (data) =>
-    @prototype.x_domain = data
-  @set_y_domain: (data) =>
-    @prototype.y_domain = [0, d3.max(data, (d) ->
-      d.commits = d.commits ? d.additions ? d.deletions
-    )]
-  @init_x_domain: (data) =>
-    @prototype.x_domain = d3.extent(data, (d) ->
-     d.date
-    )
-  @init_y_domain: (data) =>
-    @prototype.y_domain = [0, d3.max(data, (d) ->
-      d.commits = d.commits ? d.additions ? d.deletions
-    )]
-  @init_domain: (data) =>
-    @init_x_domain(data)
-    @init_y_domain(data)
-  @set_dates: (data) =>
-    @prototype.dates = data
-  set_x_domain: ->
-    @x.domain(@x_domain)
-  set_y_domain: ->
-    @y.domain(@y_domain)
-  set_domain: ->
-    @set_x_domain()
-    @set_y_domain()
-  create_scale: (width, height) ->
-    @x = d3.time.scale().range([0, width]).clamp(true)
-    @y = d3.scale.linear().range([height, 0]).nice()
-  draw_x_axis: ->
-    @svg.append("g").attr("class", "x axis").attr("transform", "translate(0, #{@height})")
-    .call(@x_axis)
-  draw_y_axis: ->
-    @svg.append("g").attr("class", "y axis").call(@y_axis)
-  set_data: (data) ->
-    @data = data
-
-class @ContributorsMasterGraph extends ContributorsGraph
-  constructor: (@data) ->
-    @width = $('.content').width() - 70
-    @height = 200
-    @x = null
-    @y = null
-    @x_axis = null
-    @y_axis = null
-    @area = null
-    @svg = null
-    @brush = null
-    @x_max_domain = null
-  process_dates: (data) ->
-    dates = @get_dates(data)
-    @parse_dates(data)
-    ContributorsGraph.set_dates(dates)
-  get_dates: (data) ->
-    _.pluck(data, 'date')
-  parse_dates: (data) ->
-    parseDate = d3.time.format("%Y-%m-%d").parse
-    data.forEach((d) ->
-      d.date = parseDate(d.date)
-    )
-  create_scale: ->
-    super @width, @height
-  create_axes: ->
-    @x_axis = d3.svg.axis().scale(@x).orient("bottom")
-    @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
-  create_svg: ->
-    @svg = d3.select("#contributors-master").append("svg")
-    .attr("width", @width + @MARGIN.left + @MARGIN.right)
-    .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
-    .attr("class", "tint-box")
-    .append("g")
-    .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
-  create_area: (x, y) ->
-    @area = d3.svg.area().x((d) ->
-      x(d.date)
-    ).y0(@height).y1((d) ->
-      xa = d.commits = d.commits ? d.additions ? d.deletions
-      y(xa)
-    ).interpolate("basis")
-  create_brush: ->
-    @brush = d3.svg.brush().x(@x).on("brushend", @update_content)
-  draw_path: (data) ->
-    @svg.append("path").datum(data).attr("class", "area").attr("d", @area)
-  add_brush: ->
-    @svg.append("g").attr("class", "selection").call(@brush).selectAll("rect").attr("height", @height)
-  update_content: =>
-    ContributorsGraph.set_x_domain(if @brush.empty() then @x_max_domain else @brush.extent())
-    $("#brush_change").trigger('change')
-  draw: ->
-    @process_dates(@data)
-    @create_scale()
-    @create_axes()
-    ContributorsGraph.init_domain(@data)
-    @x_max_domain = @x_domain
-    @set_domain()
-    @create_area(@x, @y)
-    @create_svg()
-    @create_brush()
-    @draw_path(@data)
-    @draw_x_axis()
-    @draw_y_axis()
-    @add_brush()
-  redraw: ->
-    @process_dates(@data)
-    ContributorsGraph.set_y_domain(@data)
-    @set_y_domain()
-    @svg.select("path").datum(@data)
-    @svg.select("path").attr("d", @area)
-    @svg.select(".y.axis").call(@y_axis)
-
-class @ContributorsAuthorGraph extends ContributorsGraph
-  constructor: (@data) ->
-    # Don't split graph size in half for mobile devices.
-    if $(window).width() < 768
-      @width = $('.content').width() - 80
-    else
-      @width = ($('.content').width() / 2) - 100
-    @height = 200
-    @x = null
-    @y = null
-    @x_axis = null
-    @y_axis = null
-    @area = null
-    @svg = null
-    @list_item = null
-  create_scale: ->
-    super @width, @height
-  create_axes: ->
-    @x_axis = d3.svg.axis().scale(@x).orient("bottom").ticks(8)
-    @y_axis = d3.svg.axis().scale(@y).orient("left").ticks(5)
-  create_area: (x, y) ->
-    @area = d3.svg.area().x((d) ->
-      parseDate = d3.time.format("%Y-%m-%d").parse
-      x(parseDate(d))
-    ).y0(@height).y1((d) =>
-      if @data[d]? then y(@data[d]) else y(0)
-    ).interpolate("basis")
-  create_svg: ->
-    @list_item = d3.selectAll(".person")[0].pop()
-    @svg = d3.select(@list_item).append("svg")
-    .attr("width", @width + @MARGIN.left + @MARGIN.right)
-    .attr("height", @height + @MARGIN.top + @MARGIN.bottom)
-    .attr("class", "spark")
-    .append("g")
-    .attr("transform", "translate(" + @MARGIN.left + "," + @MARGIN.top + ")")
-  draw_path: (data) ->
-    @svg.append("path").datum(data).attr("class", "area-contributor").attr("d", @area)
-  draw: ->
-    @create_scale()
-    @create_axes()
-    @set_domain()
-    @create_area(@x, @y)
-    @create_svg()
-    @draw_path(@dates)
-    @draw_x_axis()
-    @draw_y_axis()
-  redraw: ->
-    @set_domain()
-    @svg.select("path").datum(@dates)
-    @svg.select("path").attr("d", @area)
-    @svg.select(".x.axis").call(@x_axis)
-    @svg.select(".y.axis").call(@y_axis)
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d240bed8b61ea2c43d689b3d557ec65d64b7d2a
--- /dev/null
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -0,0 +1,135 @@
+(function() {
+  window.ContributorsStatGraphUtil = {
+    parse_log: function(log) {
+      var by_author, by_email, data, entry, i, len, total;
+      total = {};
+      by_author = {};
+      by_email = {};
+      for (i = 0, len = log.length; i < len; i++) {
+        entry = log[i];
+        if (total[entry.date] == null) {
+          this.add_date(entry.date, total);
+        }
+        data = by_author[entry.author_name] || by_email[entry.author_email];
+        if (data == null) {
+          data = this.add_author(entry, by_author, by_email);
+        }
+        if (!data[entry.date]) {
+          this.add_date(entry.date, data);
+        }
+        this.store_data(entry, total[entry.date], data[entry.date]);
+      }
+      total = _.toArray(total);
+      by_author = _.toArray(by_author);
+      return {
+        total: total,
+        by_author: by_author
+      };
+    },
+    add_date: function(date, collection) {
+      collection[date] = {};
+      return collection[date].date = date;
+    },
+    add_author: function(author, by_author, by_email) {
+      var data;
+      data = {};
+      data.author_name = author.author_name;
+      data.author_email = author.author_email;
+      by_author[author.author_name] = data;
+      return by_email[author.author_email] = data;
+    },
+    store_data: function(entry, total, by_author) {
+      this.store_commits(total, by_author);
+      this.store_additions(entry, total, by_author);
+      return this.store_deletions(entry, total, by_author);
+    },
+    store_commits: function(total, by_author) {
+      this.add(total, "commits", 1);
+      return this.add(by_author, "commits", 1);
+    },
+    add: function(collection, field, value) {
+      if (collection[field] == null) {
+        collection[field] = 0;
+      }
+      return collection[field] += value;
+    },
+    store_additions: function(entry, total, by_author) {
+      if (entry.additions == null) {
+        entry.additions = 0;
+      }
+      this.add(total, "additions", entry.additions);
+      return this.add(by_author, "additions", entry.additions);
+    },
+    store_deletions: function(entry, total, by_author) {
+      if (entry.deletions == null) {
+        entry.deletions = 0;
+      }
+      this.add(total, "deletions", entry.deletions);
+      return this.add(by_author, "deletions", entry.deletions);
+    },
+    get_total_data: function(parsed_log, field) {
+      var log, total_data;
+      log = parsed_log.total;
+      total_data = this.pick_field(log, field);
+      return _.sortBy(total_data, function(d) {
+        return d.date;
+      });
+    },
+    pick_field: function(log, field) {
+      var total_data;
+      total_data = [];
+      _.each(log, function(d) {
+        return total_data.push(_.pick(d, [field, 'date']));
+      });
+      return total_data;
+    },
+    get_author_data: function(parsed_log, field, date_range) {
+      var author_data, log;
+      if (date_range == null) {
+        date_range = null;
+      }
+      log = parsed_log.by_author;
+      author_data = [];
+      _.each(log, (function(_this) {
+        return function(log_entry) {
+          var parsed_log_entry;
+          parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
+          if (!_.isEmpty(parsed_log_entry.dates)) {
+            return author_data.push(parsed_log_entry);
+          }
+        };
+      })(this));
+      return _.sortBy(author_data, function(d) {
+        return d[field];
+      }).reverse();
+    },
+    parse_log_entry: function(log_entry, field, date_range) {
+      var parsed_entry;
+      parsed_entry = {};
+      parsed_entry.author_name = log_entry.author_name;
+      parsed_entry.author_email = log_entry.author_email;
+      parsed_entry.dates = {};
+      parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
+      _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
+        return function(value, key) {
+          if (_this.in_range(value.date, date_range)) {
+            parsed_entry.dates[value.date] = value[field];
+            parsed_entry.commits += value.commits;
+            parsed_entry.additions += value.additions;
+            return parsed_entry.deletions += value.deletions;
+          }
+        };
+      })(this));
+      return parsed_entry;
+    },
+    in_range: function(date, date_range) {
+      var ref;
+      if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
deleted file mode 100644
index 31617c88b4a63bbb969553c68541704963e61000..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js.coffee
+++ /dev/null
@@ -1,98 +0,0 @@
-window.ContributorsStatGraphUtil =
-  parse_log: (log) ->
-    total = {}
-    by_author = {}
-    by_email = {}
-    for entry in log
-      @add_date(entry.date, total) unless total[entry.date]?
-
-      data = by_author[entry.author_name] || by_email[entry.author_email]      
-      data ?= @add_author(entry, by_author, by_email)
-
-      @add_date(entry.date, data) unless data[entry.date]
-      @store_data(entry, total[entry.date], data[entry.date])
-    total = _.toArray(total)
-    by_author = _.toArray(by_author)
-    total: total, by_author: by_author
-
-  add_date: (date, collection) ->
-    collection[date] = {}
-    collection[date].date = date
-
-  add_author: (author, by_author, by_email) ->
-    data = {}
-    data.author_name = author.author_name
-    data.author_email = author.author_email
-    by_author[author.author_name] = data
-    by_email[author.author_email] = data
-
-  store_data: (entry, total, by_author) ->
-    @store_commits(total, by_author)
-    @store_additions(entry, total, by_author)
-    @store_deletions(entry, total, by_author)
-
-  store_commits: (total, by_author) ->
-    @add(total, "commits", 1)
-    @add(by_author, "commits", 1)
-
-  add: (collection, field, value) ->
-    collection[field] ?= 0
-    collection[field] += value
-
-  store_additions: (entry, total, by_author) ->
-    entry.additions ?= 0
-    @add(total, "additions", entry.additions)
-    @add(by_author, "additions", entry.additions)
-
-  store_deletions: (entry, total, by_author) ->
-    entry.deletions ?= 0
-    @add(total, "deletions", entry.deletions)
-    @add(by_author, "deletions", entry.deletions)
-
-  get_total_data: (parsed_log, field) ->
-    log = parsed_log.total
-    total_data = @pick_field(log, field)
-    _.sortBy(total_data, (d) ->
-      d.date
-    )
-  pick_field: (log, field) ->
-    total_data = []
-    _.each(log, (d) ->
-      total_data.push(_.pick(d, [field, 'date']))
-    )
-    total_data
-
-  get_author_data: (parsed_log, field, date_range = null) ->
-    log = parsed_log.by_author
-    author_data = []
-
-    _.each(log, (log_entry) =>
-      parsed_log_entry = @parse_log_entry(log_entry, field, date_range)
-      if not _.isEmpty(parsed_log_entry.dates)
-        author_data.push(parsed_log_entry)
-    )
-
-    _.sortBy(author_data, (d) ->
-      d[field]
-    ).reverse()
-
-  parse_log_entry: (log_entry, field, date_range) ->
-    parsed_entry = {}
-    parsed_entry.author_name = log_entry.author_name
-    parsed_entry.author_email = log_entry.author_email
-    parsed_entry.dates = {}
-    parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0
-    _.each(_.omit(log_entry, 'author_name', 'author_email'), (value, key) =>
-      if @in_range(value.date, date_range)
-        parsed_entry.dates[value.date] = value[field]
-        parsed_entry.commits += value.commits
-        parsed_entry.additions += value.additions
-        parsed_entry.deletions += value.deletions
-    )
-    return parsed_entry
-
-  in_range: (date, date_range) ->
-    if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
-      true
-    else
-      false
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
new file mode 100644
index 0000000000000000000000000000000000000000..c28ce86d7afc8cc8878b67d5c2265057b3f3421c
--- /dev/null
+++ b/app/assets/javascripts/group_avatar.js
@@ -0,0 +1,21 @@
+(function() {
+  this.GroupAvatar = (function() {
+    function GroupAvatar() {
+      $('.js-choose-group-avatar-button').bind("click", function() {
+        var form;
+        form = $(this).closest("form");
+        return form.find(".js-group-avatar-input").click();
+      });
+      $('.js-group-avatar-input').bind("change", function() {
+        var filename, form;
+        form = $(this).closest("form");
+        filename = $(this).val().replace(/^.*[\\\/]/, '');
+        return form.find(".js-avatar-filename").text(filename);
+      });
+    }
+
+    return GroupAvatar;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee
deleted file mode 100644
index 0825fd3ce52531a6873bf299e32428a16280e43a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/group_avatar.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @GroupAvatar
-  constructor: ->
-    $('.js-choose-group-avatar-button').bind "click", ->
-      form = $(this).closest("form")
-      form.find(".js-group-avatar-input").click()
-    $('.js-group-avatar-input').bind "change", ->
-      form = $(this).closest("form")
-      filename = $(this).val().replace(/^.*[\\\/]/, '')
-      form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js
new file mode 100644
index 0000000000000000000000000000000000000000..4382dd6860f542d75fdb69d94adca8e82397ed04
--- /dev/null
+++ b/app/assets/javascripts/groups.js
@@ -0,0 +1,13 @@
+(function() {
+  this.GroupMembers = (function() {
+    function GroupMembers() {
+      $('li.group_member').bind('ajax:success', function() {
+        return $(this).fadeOut();
+      });
+    }
+
+    return GroupMembers;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
deleted file mode 100644
index cc905e91ea2570de625c61fd243177bbc7d3b1b5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/groups.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @GroupMembers
-  constructor: ->
-    $('li.group_member').bind 'ajax:success', ->
-      $(this).fadeOut()
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd5b6dc0ddd405ff5ee3a9076ec9cb5b95de803f
--- /dev/null
+++ b/app/assets/javascripts/groups_select.js
@@ -0,0 +1,67 @@
+(function() {
+  var slice = [].slice;
+
+  this.GroupsSelect = (function() {
+    function GroupsSelect() {
+      $('.ajax-groups-select').each((function(_this) {
+        return function(i, select) {
+          var skip_ldap;
+          skip_ldap = $(select).hasClass('skip_ldap');
+          return $(select).select2({
+            placeholder: "Search for a group",
+            multiple: $(select).hasClass('multiselect'),
+            minimumInputLength: 0,
+            query: function(query) {
+              return Api.groups(query.term, skip_ldap, function(groups) {
+                var data;
+                data = {
+                  results: groups
+                };
+                return query.callback(data);
+              });
+            },
+            initSelection: function(element, callback) {
+              var id;
+              id = $(element).val();
+              if (id !== "") {
+                return Api.group(id, callback);
+              }
+            },
+            formatResult: function() {
+              var args;
+              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+              return _this.formatResult.apply(_this, args);
+            },
+            formatSelection: function() {
+              var args;
+              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+              return _this.formatSelection.apply(_this, args);
+            },
+            dropdownCssClass: "ajax-groups-dropdown",
+            escapeMarkup: function(m) {
+              return m;
+            }
+          });
+        };
+      })(this));
+    }
+
+    GroupsSelect.prototype.formatResult = function(group) {
+      var avatar;
+      if (group.avatar_url) {
+        avatar = group.avatar_url;
+      } else {
+        avatar = gon.default_avatar_url;
+      }
+      return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
+    };
+
+    GroupsSelect.prototype.formatSelection = function(group) {
+      return group.name;
+    };
+
+    return GroupsSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/groups_select.js.coffee b/app/assets/javascripts/groups_select.js.coffee
deleted file mode 100644
index 1084e2a17d191db263d3a039f5746b85dfaf909e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/groups_select.js.coffee
+++ /dev/null
@@ -1,41 +0,0 @@
-class @GroupsSelect
-  constructor: ->
-    $('.ajax-groups-select').each (i, select) =>
-      skip_ldap = $(select).hasClass('skip_ldap')
-
-      $(select).select2
-        placeholder: "Search for a group"
-        multiple: $(select).hasClass('multiselect')
-        minimumInputLength: 0
-        query: (query) ->
-          Api.groups query.term, skip_ldap, (groups) ->
-            data = { results: groups }
-            query.callback(data)
-
-        initSelection: (element, callback) ->
-          id = $(element).val()
-          if id isnt ""
-            Api.group(id, callback)
-
-
-        formatResult: (args...) =>
-          @formatResult(args...)
-        formatSelection: (args...) =>
-          @formatSelection(args...)
-        dropdownCssClass: "ajax-groups-dropdown"
-        escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
-          m
-
-  formatResult: (group) ->
-    if group.avatar_url
-      avatar = group.avatar_url
-    else
-      avatar = gon.default_avatar_url
-
-    "<div class='group-result'>
-       <div class='group-name'>#{group.name}</div>
-       <div class='group-path'>#{group.path}</div>
-     </div>"
-
-  formatSelection: (group) ->
-    group.name
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f840821f5394149151f69019c6675f50b9c94ed
--- /dev/null
+++ b/app/assets/javascripts/importer_status.js
@@ -0,0 +1,77 @@
+(function() {
+  this.ImporterStatus = (function() {
+    function ImporterStatus(jobs_url, import_url) {
+      this.jobs_url = jobs_url;
+      this.import_url = import_url;
+      this.initStatusPage();
+      this.setAutoUpdate();
+    }
+
+    ImporterStatus.prototype.initStatusPage = function() {
+      $('.js-add-to-import').off('click').on('click', (function(_this) {
+        return function(e) {
+          var $btn, $namespace_input, $target_field, $tr, id, new_namespace;
+          $btn = $(e.currentTarget);
+          $tr = $btn.closest('tr');
+          $target_field = $tr.find('.import-target');
+          $namespace_input = $target_field.find('input');
+          id = $tr.attr('id').replace('repo_', '');
+          new_namespace = null;
+          if ($namespace_input.length > 0) {
+            new_namespace = $namespace_input.prop('value');
+            $target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name')));
+          }
+          $btn.disable().addClass('is-loading');
+          return $.post(_this.import_url, {
+            repo_id: id,
+            new_namespace: new_namespace
+          }, {
+            dataType: 'script'
+          });
+        };
+      })(this));
+      return $('.js-import-all').off('click').on('click', function(e) {
+        var $btn;
+        $btn = $(this);
+        $btn.disable().addClass('is-loading');
+        return $('.js-add-to-import').each(function() {
+          return $(this).trigger('click');
+        });
+      });
+    };
+
+    ImporterStatus.prototype.setAutoUpdate = function() {
+      return setInterval(((function(_this) {
+        return function() {
+          return $.get(_this.jobs_url, function(data) {
+            return $.each(data, function(i, job) {
+              var job_item, status_field;
+              job_item = $("#project_" + job.id);
+              status_field = job_item.find(".job-status");
+              if (job.import_status === 'finished') {
+                job_item.removeClass("active").addClass("success");
+                return status_field.html('<span><i class="fa fa-check"></i> done</span>');
+              } else if (job.import_status === 'started') {
+                return status_field.html("<i class='fa fa-spinner fa-spin'></i> started");
+              } else {
+                return status_field.html(job.import_status);
+              }
+            });
+          });
+        };
+      })(this)), 4000);
+    };
+
+    return ImporterStatus;
+
+  })();
+
+  $(function() {
+    if ($('.js-importer-status').length) {
+      var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
+      var importPath = $('.js-importer-status').data('import-path');
+      
+      new ImporterStatus(jobsImportPath, importPath);
+    }
+  });
+}).call(this);
diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee
deleted file mode 100644
index eb046eb2eff90b60ab308459cb2548260c7d0987..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/importer_status.js.coffee
+++ /dev/null
@@ -1,53 +0,0 @@
-class @ImporterStatus
-  constructor: (@jobs_url, @import_url) ->
-    this.initStatusPage()
-    this.setAutoUpdate()
-
-  initStatusPage: ->
-    $('.js-add-to-import')
-      .off 'click'
-      .on 'click', (e) =>
-        $btn = $(e.currentTarget)
-        $tr = $btn.closest('tr')
-        $target_field = $tr.find('.import-target')
-        $namespace_input = $target_field.find('input')
-        id = $tr.attr('id').replace('repo_', '')
-        new_namespace = null
-
-        if $namespace_input.length > 0
-          new_namespace = $namespace_input.prop('value')
-          $target_field.empty().append("#{new_namespace}/#{$target_field.data('project_name')}")
-
-        $btn
-          .disable()
-          .addClass 'is-loading'
-
-        $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
-
-    $('.js-import-all')
-      .off 'click'
-      .on 'click', (e) ->
-        $btn = $(@)
-        $btn
-          .disable()
-          .addClass 'is-loading'
-
-        $('.js-add-to-import').each ->
-          $(this).trigger('click')
-
-  setAutoUpdate: ->
-    setInterval (=>
-      $.get @jobs_url, (data) =>
-        $.each data, (i, job) =>
-          job_item = $("#project_" + job.id)
-          status_field = job_item.find(".job-status")
-
-          if job.import_status == 'finished'
-            job_item.removeClass("active").addClass("success")
-            status_field.html('<span><i class="fa fa-check"></i> done</span>')
-          else if job.import_status == 'started'
-            status_field.html("<i class='fa fa-spinner fa-spin'></i> started")
-          else
-            status_field.html(job.import_status)
-
-    ), 4000
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
new file mode 100644
index 0000000000000000000000000000000000000000..f27f1bad1f7c2a35ed7c7b79261b024c9349af2d
--- /dev/null
+++ b/app/assets/javascripts/issuable.js
@@ -0,0 +1,89 @@
+(function() {
+  var issuable_created;
+
+  issuable_created = false;
+
+  this.Issuable = {
+    init: function() {
+      if (!issuable_created) {
+        issuable_created = true;
+        Issuable.initTemplates();
+        Issuable.initSearch();
+        Issuable.initChecks();
+        return Issuable.initLabelFilterRemove();
+      }
+    },
+    initTemplates: function() {
+      return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
+    },
+    initSearch: function() {
+      this.timer = null;
+      return $('#issue_search').off('keyup').on('keyup', function() {
+        clearTimeout(this.timer);
+        return this.timer = setTimeout(function() {
+          var $form, $input, $search;
+          $search = $('#issue_search');
+          $form = $('.js-filter-form');
+          $input = $("input[name='" + ($search.attr('name')) + "']", $form);
+          if ($input.length === 0) {
+            $form.append("<input type='hidden' name='" + ($search.attr('name')) + "' value='" + (_.escape($search.val())) + "'/>");
+          } else {
+            $input.val($search.val());
+          }
+          if ($search.val() !== '') {
+            return Issuable.filterResults($form);
+          }
+        }, 500);
+      });
+    },
+    initLabelFilterRemove: function() {
+      return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
+        var $button;
+        $button = $(this);
+        $('input[name="label_name[]"]').filter(function() {
+          return this.value === $button.data('label');
+        }).remove();
+        Issuable.filterResults($('.filter-form'));
+        return $('.js-label-select').trigger('update.label');
+      });
+    },
+    filterResults: (function(_this) {
+      return function(form) {
+        var formAction, formData, issuesUrl;
+        formData = form.serialize();
+        formAction = form.attr('action');
+        issuesUrl = formAction;
+        issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
+        issuesUrl += formData;
+        return Turbolinks.visit(issuesUrl);
+      };
+    })(this),
+    initChecks: function() {
+      this.issuableBulkActions = $('.bulk-update').data('bulkActions');
+      $('.check_all_issues').off('click').on('click', function() {
+        $('.selected_issue').prop('checked', this.checked);
+        return Issuable.checkChanged();
+      });
+      return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
+    },
+    checkChanged: function() {
+      var checked_issues, ids;
+      checked_issues = $('.selected_issue:checked');
+      if (checked_issues.length > 0) {
+        ids = $.map(checked_issues, function(value) {
+          return $(value).data('id');
+        });
+        $('#update_issues_ids').val(ids);
+        $('.issues-other-filters').hide();
+        $('.issues_bulk_update').show();
+      } else {
+        $('#update_issues_ids').val([]);
+        $('.issues_bulk_update').hide();
+        $('.issues-other-filters').show();
+        this.issuableBulkActions.willUpdateLabels = false;
+      }
+      return true;
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee
deleted file mode 100644
index 7f795f8096b7080759f880123581e0189a609244..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issuable.js.coffee
+++ /dev/null
@@ -1,93 +0,0 @@
-issuable_created = false
-@Issuable =
-  init: ->
-    unless issuable_created
-      issuable_created = true
-      Issuable.initTemplates()
-      Issuable.initSearch()
-      Issuable.initChecks()
-      Issuable.initLabelFilterRemove()
-
-  initTemplates: ->
-    Issuable.labelRow = _.template(
-      '<% _.each(labels, function(label){ %>
-        <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;">
-          <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body">
-            <%- label.title %>
-          </a>
-          <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>">
-            <i class="fa fa-times"></i>
-          </button>
-        </span>
-      <% }); %>'
-    )
-
-  initSearch: ->
-    @timer = null
-    $('#issue_search')
-      .off 'keyup'
-      .on 'keyup', ->
-        clearTimeout(@timer)
-        @timer = setTimeout( ->
-          $search = $('#issue_search')
-          $form = $('.js-filter-form')
-          $input = $("input[name='#{$search.attr('name')}']", $form)
-          if $input.length is 0
-            $form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
-          else
-            $input.val $search.val()
-          Issuable.filterResults $form if $search.val() isnt ''
-        , 500)
-
-  initLabelFilterRemove: ->
-    $(document)
-      .off 'click', '.js-label-filter-remove'
-      .on 'click', '.js-label-filter-remove', (e) ->
-        $button = $(@)
-
-        # Remove the label input box
-        $('input[name="label_name[]"]')
-          .filter -> @value is $button.data('label')
-          .remove()
-
-        # Submit the form to get new data
-        Issuable.filterResults $('.filter-form')
-        $('.js-label-select').trigger('update.label')
-
-  filterResults: (form) =>
-    formData = form.serialize()
-
-    formAction = form.attr('action')
-    issuesUrl = formAction
-    issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
-    issuesUrl += formData
-
-    Turbolinks.visit(issuesUrl)
-
-  initChecks: ->
-    @issuableBulkActions = $('.bulk-update').data('bulkActions')
-
-    $('.check_all_issues').off('click').on('click', ->
-      $('.selected_issue').prop('checked', @checked)
-      Issuable.checkChanged()
-    )
-
-    $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
-
-
-  checkChanged: ->
-    checked_issues = $('.selected_issue:checked')
-    if checked_issues.length > 0
-      ids = $.map checked_issues, (value) ->
-        $(value).data('id')
-
-      $('#update_issues_ids').val ids
-      $('.issues-other-filters').hide()
-      $('.issues_bulk_update').show()
-    else
-      $('#update_issues_ids').val []
-      $('.issues_bulk_update').hide()
-      $('.issues-other-filters').show()
-      @issuableBulkActions.willUpdateLabels = false
-
-    return true
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
new file mode 100644
index 0000000000000000000000000000000000000000..8147e83ffe8f0f343e455e0395409e50ac67b2d1
--- /dev/null
+++ b/app/assets/javascripts/issuable_context.js
@@ -0,0 +1,69 @@
+(function() {
+  this.IssuableContext = (function() {
+    function IssuableContext(currentUser) {
+      this.initParticipants();
+      new UsersSelect(currentUser);
+      $('select.select2').select2({
+        width: 'resolve',
+        dropdownAutoWidth: true
+      });
+      $(".issuable-sidebar .inline-update").on("change", "select", function() {
+        return $(this).submit();
+      });
+      $(".issuable-sidebar .inline-update").on("change", ".js-assignee", function() {
+        return $(this).submit();
+      });
+      $(document).off('click', '.issuable-sidebar .dropdown-content a').on('click', '.issuable-sidebar .dropdown-content a', function(e) {
+        return e.preventDefault();
+      });
+      $(document).off('click', '.edit-link').on('click', '.edit-link', function(e) {
+        var $block, $selectbox;
+        e.preventDefault();
+        $block = $(this).parents('.block');
+        $selectbox = $block.find('.selectbox');
+        if ($selectbox.is(':visible')) {
+          $selectbox.hide();
+          $block.find('.value').show();
+        } else {
+          $selectbox.show();
+          $block.find('.value').hide();
+        }
+        if ($selectbox.is(':visible')) {
+          return setTimeout(function() {
+            return $block.find('.dropdown-menu-toggle').trigger('click');
+          }, 0);
+        }
+      });
+      $(".right-sidebar").niceScroll();
+    }
+
+    IssuableContext.prototype.initParticipants = function() {
+      var _this;
+      _this = this;
+      $(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
+      return $(".js-participants-author").each(function(i) {
+        if (i >= _this.PARTICIPANTS_ROW_COUNT) {
+          return $(this).addClass("js-participants-hidden").hide();
+        }
+      });
+    };
+
+    IssuableContext.prototype.toggleHiddenParticipants = function(e) {
+      var currentText, lessText, originalText;
+      e.preventDefault();
+      currentText = $(this).text().trim();
+      lessText = $(this).data("less-text");
+      originalText = $(this).data("original-text");
+      if (currentText === originalText) {
+        $(this).text(lessText);
+      } else {
+        $(this).text(originalText);
+      }
+      return $(".js-participants-hidden").toggle();
+    };
+
+    return IssuableContext;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
deleted file mode 100644
index 3c491ebfc4cd18fad7148db2e39f0790c7cb9a8b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @IssuableContext
-  constructor: (currentUser) ->
-    @initParticipants()
-    new UsersSelect(currentUser)
-    $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
-
-    $(".issuable-sidebar .inline-update").on "change", "select", ->
-      $(this).submit()
-    $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
-      $(this).submit()
-
-    $(document)
-      .off 'click', '.issuable-sidebar .dropdown-content a'
-      .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
-        e.preventDefault()
-
-    $(document)
-      .off 'click', '.edit-link'
-      .on 'click', '.edit-link', (e) ->
-        e.preventDefault()
-
-        $block = $(@).parents('.block')
-        $selectbox = $block.find('.selectbox')
-        if $selectbox.is(':visible')
-          $selectbox.hide()
-          $block.find('.value').show()
-        else
-          $selectbox.show()
-          $block.find('.value').hide()
-
-        if $selectbox.is(':visible')
-          setTimeout ->
-            $block.find('.dropdown-menu-toggle').trigger 'click'
-          , 0
-
-    $(".right-sidebar").niceScroll()
-
-  initParticipants: ->
-    _this = @
-    $(document).on "click", ".js-participants-more", @toggleHiddenParticipants
-
-    $(".js-participants-author").each (i) ->
-      if i >= _this.PARTICIPANTS_ROW_COUNT
-        $(@)
-          .addClass "js-participants-hidden"
-          .hide()
-
-  toggleHiddenParticipants: (e) ->
-    e.preventDefault()
-
-    currentText = $(this).text().trim()
-    lessText = $(this).data("less-text")
-    originalText = $(this).data("original-text")
-
-    if currentText is originalText
-      $(this).text(lessText)
-    else
-      $(this).text(originalText)
-
-    $(".js-participants-hidden").toggle()
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..297d4f029f0e50c541b8474f850c74f94ef271fb
--- /dev/null
+++ b/app/assets/javascripts/issuable_form.js
@@ -0,0 +1,136 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.IssuableForm = (function() {
+    IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?';
+
+    IssuableForm.prototype.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
+
+    function IssuableForm(form) {
+      var $issuableDueDate;
+      this.form = form;
+      this.toggleWip = bind(this.toggleWip, this);
+      this.renderWipExplanation = bind(this.renderWipExplanation, this);
+      this.resetAutosave = bind(this.resetAutosave, this);
+      this.handleSubmit = bind(this.handleSubmit, this);
+      GitLab.GfmAutoComplete.setup();
+      new UsersSelect();
+      new ZenMode();
+      this.titleField = this.form.find("input[name*='[title]']");
+      this.descriptionField = this.form.find("textarea[name*='[description]']");
+      this.issueMoveField = this.form.find("#move_to_project_id");
+      if (!(this.titleField.length && this.descriptionField.length)) {
+        return;
+      }
+      this.initAutosave();
+      this.form.on("submit", this.handleSubmit);
+      this.form.on("click", ".btn-cancel", this.resetAutosave);
+      this.initWip();
+      this.initMoveDropdown();
+      $issuableDueDate = $('#issuable-due-date');
+      if ($issuableDueDate.length) {
+        $('.datepicker').datepicker({
+          dateFormat: 'yy-mm-dd',
+          onSelect: function(dateText, inst) {
+            return $issuableDueDate.val(dateText);
+          }
+        }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val()));
+      }
+    }
+
+    IssuableForm.prototype.initAutosave = function() {
+      new Autosave(this.titleField, [document.location.pathname, document.location.search, "title"]);
+      return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, "description"]);
+    };
+
+    IssuableForm.prototype.handleSubmit = function() {
+      var ref, ref1;
+      if (((ref = parseInt((ref1 = this.issueMoveField) != null ? ref1.val() : void 0)) != null ? ref : 0) > 0) {
+        if (!confirm(this.issueMoveConfirmMsg)) {
+          return false;
+        }
+      }
+      return this.resetAutosave();
+    };
+
+    IssuableForm.prototype.resetAutosave = function() {
+      this.titleField.data("autosave").reset();
+      return this.descriptionField.data("autosave").reset();
+    };
+
+    IssuableForm.prototype.initWip = function() {
+      this.$wipExplanation = this.form.find(".js-wip-explanation");
+      this.$noWipExplanation = this.form.find(".js-no-wip-explanation");
+      if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) {
+        return;
+      }
+      this.form.on("click", ".js-toggle-wip", this.toggleWip);
+      this.titleField.on("keyup blur", this.renderWipExplanation);
+      return this.renderWipExplanation();
+    };
+
+    IssuableForm.prototype.workInProgress = function() {
+      return this.wipRegex.test(this.titleField.val());
+    };
+
+    IssuableForm.prototype.renderWipExplanation = function() {
+      if (this.workInProgress()) {
+        this.$wipExplanation.show();
+        return this.$noWipExplanation.hide();
+      } else {
+        this.$wipExplanation.hide();
+        return this.$noWipExplanation.show();
+      }
+    };
+
+    IssuableForm.prototype.toggleWip = function(event) {
+      event.preventDefault();
+      if (this.workInProgress()) {
+        this.removeWip();
+      } else {
+        this.addWip();
+      }
+      return this.renderWipExplanation();
+    };
+
+    IssuableForm.prototype.removeWip = function() {
+      return this.titleField.val(this.titleField.val().replace(this.wipRegex, ""));
+    };
+
+    IssuableForm.prototype.addWip = function() {
+      return this.titleField.val("WIP: " + (this.titleField.val()));
+    };
+
+    IssuableForm.prototype.initMoveDropdown = function() {
+      var $moveDropdown;
+      $moveDropdown = $('.js-move-dropdown');
+      if ($moveDropdown.length) {
+        return $('.js-move-dropdown').select2({
+          ajax: {
+            url: $moveDropdown.data('projects-url'),
+            results: function(data) {
+              return {
+                results: data
+              };
+            },
+            data: function(query) {
+              return {
+                search: query
+              };
+            }
+          },
+          formatResult: function(project) {
+            return project.name_with_namespace;
+          },
+          formatSelection: function(project) {
+            return project.name_with_namespace;
+          }
+        });
+      }
+    };
+
+    return IssuableForm;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
deleted file mode 100644
index 5b7a4831dfc8fe1449b6649f9b8aedd6ea928dee..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ /dev/null
@@ -1,112 +0,0 @@
-class @IssuableForm
-  issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
-  wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
-
-  constructor: (@form) ->
-    GitLab.GfmAutoComplete.setup()
-    new UsersSelect()
-    new ZenMode()
-
-    @titleField       = @form.find("input[name*='[title]']")
-    @descriptionField = @form.find("textarea[name*='[description]']")
-    @issueMoveField   = @form.find("#move_to_project_id")
-
-    return unless @titleField.length && @descriptionField.length
-
-    @initAutosave()
-
-    @form.on "submit", @handleSubmit
-    @form.on "click", ".btn-cancel", @resetAutosave
-
-    @initWip()
-    @initMoveDropdown()
-
-    $issuableDueDate = $('#issuable-due-date')
-
-    if $issuableDueDate.length
-      $('.datepicker').datepicker(
-        dateFormat: 'yy-mm-dd',
-        onSelect: (dateText, inst) ->
-          $issuableDueDate.val dateText
-      ).datepicker 'setDate', $.datepicker.parseDate('yy-mm-dd', $issuableDueDate.val())
-
-  initAutosave: ->
-    new Autosave @titleField, [
-      document.location.pathname,
-      document.location.search,
-      "title"
-    ]
-
-    new Autosave @descriptionField, [
-      document.location.pathname,
-      document.location.search,
-      "description"
-    ]
-
-  handleSubmit: =>
-    if (parseInt(@issueMoveField?.val()) ? 0) > 0
-      return false unless confirm(@issueMoveConfirmMsg)
-
-    @resetAutosave()
-
-  resetAutosave: =>
-    @titleField.data("autosave").reset()
-    @descriptionField.data("autosave").reset()
-
-  initWip: ->
-    @$wipExplanation = @form.find(".js-wip-explanation")
-    @$noWipExplanation = @form.find(".js-no-wip-explanation")
-    return unless @$wipExplanation.length and @$noWipExplanation.length
-
-    @form.on "click", ".js-toggle-wip", @toggleWip
-
-    @titleField.on "keyup blur", @renderWipExplanation
-
-    @renderWipExplanation()
-
-  workInProgress: ->
-    @wipRegex.test @titleField.val()
-
-  renderWipExplanation: =>
-    if @workInProgress()
-      @$wipExplanation.show()
-      @$noWipExplanation.hide()
-    else
-      @$wipExplanation.hide()
-      @$noWipExplanation.show()
-
-  toggleWip: (event) =>
-    event.preventDefault()
-
-    if @workInProgress()
-      @removeWip()
-    else
-      @addWip()
-
-    @renderWipExplanation()
-
-  removeWip: ->
-    @titleField.val @titleField.val().replace(@wipRegex, "")
-
-  addWip: ->
-    @titleField.val "WIP: #{@titleField.val()}"
-
-  initMoveDropdown: ->
-    $moveDropdown = $('.js-move-dropdown')
-
-    if $moveDropdown.length
-      $('.js-move-dropdown').select2
-        ajax:
-          url: $moveDropdown.data('projects-url')
-          results: (data) ->
-            return {
-              results: data
-            }
-          data: (query) ->
-            {
-              search: query
-            }
-        formatResult: (project) ->
-          project.name_with_namespace
-        formatSelection: (project) ->
-          project.name_with_namespace
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
new file mode 100644
index 0000000000000000000000000000000000000000..6838d9d8da15953f74ab2c2df0bf878c1fa1cebf
--- /dev/null
+++ b/app/assets/javascripts/issue.js
@@ -0,0 +1,154 @@
+
+/*= require flash */
+
+
+/*= require jquery.waitforimages */
+
+
+/*= require task_list */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Issue = (function() {
+    function Issue() {
+      this.submitNoteForm = bind(this.submitNoteForm, this);
+      this.disableTaskList();
+      if ($('a.btn-close').length) {
+        this.initTaskList();
+        this.initIssueBtnEventListeners();
+      }
+      this.initMergeRequests();
+      this.initRelatedBranches();
+      this.initCanCreateBranch();
+    }
+
+    Issue.prototype.initTaskList = function() {
+      $('.detail-page-description .js-task-list-container').taskList('enable');
+      return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
+    };
+
+    Issue.prototype.initIssueBtnEventListeners = function() {
+      var _this, issueFailMessage;
+      _this = this;
+      issueFailMessage = 'Unable to update this issue at this time.';
+      return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+        var $this, isClose, shouldSubmit, url;
+        e.preventDefault();
+        e.stopImmediatePropagation();
+        $this = $(this);
+        isClose = $this.hasClass('btn-close');
+        shouldSubmit = $this.hasClass('btn-comment');
+        if (shouldSubmit) {
+          _this.submitNoteForm($this.closest('form'));
+        }
+        $this.prop('disabled', true);
+        url = $this.attr('href');
+        return $.ajax({
+          type: 'PUT',
+          url: url,
+          error: function(jqXHR, textStatus, errorThrown) {
+            var issueStatus;
+            issueStatus = isClose ? 'close' : 'open';
+            return new Flash(issueFailMessage, 'alert');
+          },
+          success: function(data, textStatus, jqXHR) {
+            if ('id' in data) {
+              $(document).trigger('issuable:change');
+              if (isClose) {
+                $('a.btn-close').addClass('hidden');
+                $('a.btn-reopen').removeClass('hidden');
+                $('div.status-box-closed').removeClass('hidden');
+                $('div.status-box-open').addClass('hidden');
+              } else {
+                $('a.btn-reopen').addClass('hidden');
+                $('a.btn-close').removeClass('hidden');
+                $('div.status-box-closed').addClass('hidden');
+                $('div.status-box-open').removeClass('hidden');
+              }
+            } else {
+              new Flash(issueFailMessage, 'alert');
+            }
+            return $this.prop('disabled', false);
+          }
+        });
+      });
+    };
+
+    Issue.prototype.submitNoteForm = function(form) {
+      var noteText;
+      noteText = form.find("textarea.js-note-text").val();
+      if (noteText.trim().length > 0) {
+        return form.submit();
+      }
+    };
+
+    Issue.prototype.disableTaskList = function() {
+      $('.detail-page-description .js-task-list-container').taskList('disable');
+      return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
+    };
+
+    Issue.prototype.updateTaskList = function() {
+      var patchData;
+      patchData = {};
+      patchData['issue'] = {
+        'description': $('.js-task-list-field', this).val()
+      };
+      return $.ajax({
+        type: 'PATCH',
+        url: $('form.js-issuable-update').attr('action'),
+        data: patchData
+      });
+    };
+
+    Issue.prototype.initMergeRequests = function() {
+      var $container;
+      $container = $('#merge-requests');
+      return $.getJSON($container.data('url')).error(function() {
+        return new Flash('Failed to load referenced merge requests', 'alert');
+      }).success(function(data) {
+        if ('html' in data) {
+          return $container.html(data.html);
+        }
+      });
+    };
+
+    Issue.prototype.initRelatedBranches = function() {
+      var $container;
+      $container = $('#related-branches');
+      return $.getJSON($container.data('url')).error(function() {
+        return new Flash('Failed to load related branches', 'alert');
+      }).success(function(data) {
+        if ('html' in data) {
+          return $container.html(data.html);
+        }
+      });
+    };
+
+    Issue.prototype.initCanCreateBranch = function() {
+      var $container;
+      $container = $('div#new-branch');
+      if ($container.length === 0) {
+        return;
+      }
+      return $.getJSON($container.data('path')).error(function() {
+        $container.find('.checking').hide();
+        $container.find('.unavailable').show();
+        return new Flash('Failed to check if a new branch can be created.', 'alert');
+      }).success(function(data) {
+        if (data.can_create_branch) {
+          $container.find('.checking').hide();
+          $container.find('.available').show();
+          return $container.find('a').attr('disabled', false);
+        } else {
+          $container.find('.checking').hide();
+          return $container.find('.unavailable').show();
+        }
+      });
+    };
+
+    return Issue;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
deleted file mode 100644
index f446aa49cde388c6242a791b4ff37d0d9fa5a240..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issue.js.coffee
+++ /dev/null
@@ -1,117 +0,0 @@
-#= require flash
-#= require jquery.waitforimages
-#= require task_list
-
-class @Issue
-  constructor: ->
-    # Prevent duplicate event bindings
-    @disableTaskList()
-    if $('a.btn-close').length
-      @initTaskList()
-      @initIssueBtnEventListeners()
-
-    @initMergeRequests()
-    @initRelatedBranches()
-    @initCanCreateBranch()
-
-  initTaskList: ->
-    $('.detail-page-description .js-task-list-container').taskList('enable')
-    $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
-
-  initIssueBtnEventListeners: ->
-    _this = @
-    issueFailMessage = 'Unable to update this issue at this time.'
-    $('a.btn-close, a.btn-reopen').on 'click', (e) ->
-      e.preventDefault()
-      e.stopImmediatePropagation()
-      $this = $(this)
-      isClose = $this.hasClass('btn-close')
-      shouldSubmit = $this.hasClass('btn-comment')
-      if shouldSubmit
-        _this.submitNoteForm($this.closest('form'))
-      $this.prop('disabled', true)
-      url = $this.attr('href')
-      $.ajax
-        type: 'PUT'
-        url: url,
-        error: (jqXHR, textStatus, errorThrown) ->
-          issueStatus = if isClose then 'close' else 'open'
-          new Flash(issueFailMessage, 'alert')
-        success: (data, textStatus, jqXHR) ->
-          if 'id' of data
-            $(document).trigger('issuable:change');
-            if isClose
-              $('a.btn-close').addClass('hidden')
-              $('a.btn-reopen').removeClass('hidden')
-              $('div.status-box-closed').removeClass('hidden')
-              $('div.status-box-open').addClass('hidden')
-            else
-              $('a.btn-reopen').addClass('hidden')
-              $('a.btn-close').removeClass('hidden')
-              $('div.status-box-closed').addClass('hidden')
-              $('div.status-box-open').removeClass('hidden')
-          else
-            new Flash(issueFailMessage, 'alert')
-          $this.prop('disabled', false)
-
-  submitNoteForm: (form) =>
-    noteText = form.find("textarea.js-note-text").val()
-    if noteText.trim().length > 0
-      form.submit()
-
-  disableTaskList: ->
-    $('.detail-page-description .js-task-list-container').taskList('disable')
-    $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
-
-  # TODO (rspeicher): Make the issue description inline-editable like a note so
-  # that we can re-use its form here
-  updateTaskList: ->
-    patchData = {}
-    patchData['issue'] = {'description': $('.js-task-list-field', this).val()}
-
-    $.ajax
-      type: 'PATCH'
-      url: $('form.js-issuable-update').attr('action')
-      data: patchData
-
-  initMergeRequests: ->
-    $container = $('#merge-requests')
-
-    $.getJSON($container.data('url'))
-      .error ->
-        new Flash('Failed to load referenced merge requests', 'alert')
-      .success (data) ->
-        if 'html' of data
-          $container.html(data.html)
-
-  initRelatedBranches: ->
-    $container = $('#related-branches')
-
-    $.getJSON($container.data('url'))
-      .error ->
-        new Flash('Failed to load related branches', 'alert')
-      .success (data) ->
-        if 'html' of data
-          $container.html(data.html)
-
-  initCanCreateBranch: ->
-    $container = $('div#new-branch')
-
-    # If the user doesn't have the required permissions the container isn't
-    # rendered at all.
-    return if $container.length is 0
-
-    $.getJSON($container.data('path'))
-      .error ->
-        $container.find('.checking').hide()
-        $container.find('.unavailable').show()
-
-        new Flash('Failed to check if a new branch can be created.', 'alert')
-      .success (data) ->
-        if data.can_create_branch
-          $container.find('.checking').hide()
-          $container.find('.available').show()
-          $container.find('a').attr('disabled', false)
-        else
-          $container.find('.checking').hide()
-          $container.find('.unavailable').show()
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..076e39729444f1deb81f96cb47d881a46c6832f6
--- /dev/null
+++ b/app/assets/javascripts/issue_status_select.js
@@ -0,0 +1,35 @@
+(function() {
+  this.IssueStatusSelect = (function() {
+    function IssueStatusSelect() {
+      $('.js-issue-status').each(function(i, el) {
+        var fieldName;
+        fieldName = $(el).data("field-name");
+        return $(el).glDropdown({
+          selectable: true,
+          fieldName: fieldName,
+          toggleLabel: (function(_this) {
+            return function(selected, el, instance) {
+              var $item, label;
+              label = 'Author';
+              $item = instance.dropdown.find('.is-active');
+              if ($item.length) {
+                label = $item.text();
+              }
+              return label;
+            };
+          })(this),
+          clicked: function(item, $el, e) {
+            return e.preventDefault();
+          },
+          id: function(obj, el) {
+            return $(el).data("id");
+          }
+        });
+      });
+    }
+
+    return IssueStatusSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee
deleted file mode 100644
index ed50e2e698ff8cd4842c9bb2505a73b5770b5366..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issue_status_select.js.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-class @IssueStatusSelect
-  constructor: ->
-    $('.js-issue-status').each (i, el) ->
-      fieldName = $(el).data("field-name")
-
-      $(el).glDropdown(
-        selectable: true
-        fieldName: fieldName
-        toggleLabel: (selected, el, instance) =>
-          label = 'Author'
-          $item = instance.dropdown.find('.is-active')
-          label = $item.text() if $item.length
-          label
-        clicked: (item, $el, e)->
-          e.preventDefault()
-        id: (obj, el) ->
-          $(el).data("id")
-      )
diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js
new file mode 100644
index 0000000000000000000000000000000000000000..98d3358ba921da1fd57dae38f22a1cddd6e65143
--- /dev/null
+++ b/app/assets/javascripts/issues-bulk-assignment.js
@@ -0,0 +1,161 @@
+(function() {
+  this.IssuableBulkActions = (function() {
+    function IssuableBulkActions(opts) {
+      var ref, ref1, ref2;
+      if (opts == null) {
+        opts = {};
+      }
+      this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
+      this.form.data('bulkActions', this);
+      this.willUpdateLabels = false;
+      this.bindEvents();
+      Issuable.initChecks();
+    }
+
+    IssuableBulkActions.prototype.getElement = function(selector) {
+      return this.container.find(selector);
+    };
+
+    IssuableBulkActions.prototype.bindEvents = function() {
+      return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
+    };
+
+    IssuableBulkActions.prototype.onFormSubmit = function(e) {
+      e.preventDefault();
+      return this.submit();
+    };
+
+    IssuableBulkActions.prototype.submit = function() {
+      var _this, xhr;
+      _this = this;
+      xhr = $.ajax({
+        url: this.form.attr('action'),
+        method: this.form.attr('method'),
+        dataType: 'JSON',
+        data: this.getFormDataAsObject()
+      });
+      xhr.done(function(response, status, xhr) {
+        return location.reload();
+      });
+      xhr.fail(function() {
+        return new Flash("Issue update failed");
+      });
+      return xhr.always(this.onFormSubmitAlways.bind(this));
+    };
+
+    IssuableBulkActions.prototype.onFormSubmitAlways = function() {
+      return this.form.find('[type="submit"]').enable();
+    };
+
+    IssuableBulkActions.prototype.getSelectedIssues = function() {
+      return this.issues.has('.selected_issue:checked');
+    };
+
+    IssuableBulkActions.prototype.getLabelsFromSelection = function() {
+      var labels;
+      labels = [];
+      this.getSelectedIssues().map(function() {
+        var _labels;
+        _labels = $(this).data('labels');
+        if (_labels) {
+          return _labels.map(function(labelId) {
+            if (labels.indexOf(labelId) === -1) {
+              return labels.push(labelId);
+            }
+          });
+        }
+      });
+      return labels;
+    };
+
+
+    /**
+     * Will return only labels that were marked previously and the user has unmarked
+     * @return {Array} Label IDs
+     */
+
+    IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() {
+      var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result;
+      result = [];
+      labelsToKeep = [];
+      ref = this.getElement('.labels-filter .is-indeterminate');
+      for (i = 0, len = ref.length; i < len; i++) {
+        el = ref[i];
+        labelsToKeep.push($(el).data('labelId'));
+      }
+      ref1 = this.getLabelsFromSelection();
+      for (j = 0, len1 = ref1.length; j < len1; j++) {
+        id = ref1[j];
+        if (labelsToKeep.indexOf(id) === -1) {
+          result.push(id);
+        }
+      }
+      return result;
+    };
+
+
+    /**
+     * Simple form serialization, it will return just what we need
+     * Returns key/value pairs from form data
+     */
+
+    IssuableBulkActions.prototype.getFormDataAsObject = function() {
+      var formData;
+      formData = {
+        update: {
+          state_event: this.form.find('input[name="update[state_event]"]').val(),
+          assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
+          milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
+          issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
+          subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
+          add_label_ids: [],
+          remove_label_ids: []
+        }
+      };
+      if (this.willUpdateLabels) {
+        this.getLabelsToApply().map(function(id) {
+          return formData.update.add_label_ids.push(id);
+        });
+        this.getLabelsToRemove().map(function(id) {
+          return formData.update.remove_label_ids.push(id);
+        });
+      }
+      return formData;
+    };
+
+    IssuableBulkActions.prototype.getLabelsToApply = function() {
+      var $labels, labelIds;
+      labelIds = [];
+      $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
+      $labels.each(function(k, label) {
+        if (label) {
+          return labelIds.push(parseInt($(label).val()));
+        }
+      });
+      return labelIds;
+    };
+
+
+    /**
+     * Returns Label IDs that will be removed from issue selection
+     * @return {Array} Array of labels IDs
+     */
+
+    IssuableBulkActions.prototype.getLabelsToRemove = function() {
+      var indeterminatedLabels, labelsToApply, result;
+      result = [];
+      indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
+      labelsToApply = this.getLabelsToApply();
+      indeterminatedLabels.map(function(id) {
+        if (labelsToApply.indexOf(id) === -1) {
+          return result.push(id);
+        }
+      });
+      return result;
+    };
+
+    return IssuableBulkActions;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee
deleted file mode 100644
index 3d09ea08e3b1833d8813fd12c18811db7636286d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/issues-bulk-assignment.js.coffee
+++ /dev/null
@@ -1,128 +0,0 @@
-class @IssuableBulkActions
-  constructor: (opts = {}) ->
-    # Set defaults
-    {
-      @container = $('.content')
-      @form = @getElement('.bulk-update')
-      @issues = @getElement('.issues-list .issue')
-    } = opts
-
-    # Save instance
-    @form.data 'bulkActions', @
-
-    @willUpdateLabels = false
-
-    @bindEvents()
-
-    # Fixes bulk-assign not working when navigating through pages
-    Issuable.initChecks();
-
-  getElement: (selector) ->
-    @container.find selector
-
-  bindEvents: ->
-    @form.off('submit').on('submit', @onFormSubmit.bind(@))
-
-  onFormSubmit: (e) ->
-    e.preventDefault()
-    @submit()
-
-  submit: ->
-    _this = @
-
-    xhr = $.ajax
-            url: @form.attr 'action'
-            method: @form.attr 'method'
-            dataType: 'JSON',
-            data: @getFormDataAsObject()
-
-    xhr.done (response, status, xhr) ->
-      location.reload()
-
-    xhr.fail ->
-      new Flash("Issue update failed")
-
-    xhr.always @onFormSubmitAlways.bind(@)
-
-  onFormSubmitAlways: ->
-    @form.find('[type="submit"]').enable()
-
-  getSelectedIssues: ->
-    @issues.has('.selected_issue:checked')
-
-  getLabelsFromSelection: ->
-    labels = []
-
-    @getSelectedIssues().map ->
-      _labels = $(@).data('labels')
-      if _labels
-        _labels.map (labelId) ->
-          labels.push(labelId) if labels.indexOf(labelId) is -1
-
-    labels
-
-  ###*
-   * Will return only labels that were marked previously and the user has unmarked
-   * @return {Array} Label IDs
-  ###
-  getUnmarkedIndeterminedLabels: ->
-    result = []
-    labelsToKeep = []
-
-    for el in @getElement('.labels-filter .is-indeterminate')
-      labelsToKeep.push $(el).data('labelId')
-
-    for id in @getLabelsFromSelection()
-      # Only the ones that we are not going to keep
-      result.push(id) if labelsToKeep.indexOf(id) is -1
-
-    result
-
-  ###*
-   * Simple form serialization, it will return just what we need
-   * Returns key/value pairs from form data
-  ###
-  getFormDataAsObject: ->
-    formData =
-      update:
-        state_event        : @form.find('input[name="update[state_event]"]').val()
-        assignee_id        : @form.find('input[name="update[assignee_id]"]').val()
-        milestone_id       : @form.find('input[name="update[milestone_id]"]').val()
-        issues_ids         : @form.find('input[name="update[issues_ids]"]').val()
-        subscription_event : @form.find('input[name="update[subscription_event]"]').val()
-        add_label_ids      : []
-        remove_label_ids   : []
-
-    if @willUpdateLabels
-      @getLabelsToApply().map (id) ->
-        formData.update.add_label_ids.push id
-
-      @getLabelsToRemove().map (id) ->
-        formData.update.remove_label_ids.push id
-
-    formData
-
-  getLabelsToApply: ->
-    labelIds = []
-    $labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
-
-    $labels.each (k, label) ->
-      labelIds.push parseInt($(label).val()) if label
-
-    labelIds
-
-  ###*
-   * Returns Label IDs that will be removed from issue selection
-   * @return {Array} Array of labels IDs
-  ###
-  getLabelsToRemove: ->
-    result = []
-    indeterminatedLabels = @getUnmarkedIndeterminedLabels()
-    labelsToApply = @getLabelsToApply()
-
-    indeterminatedLabels.map (id) ->
-      # We need to exclude label IDs that will be applied
-      # By not doing this will cause issues from selection to not add labels at all
-      result.push(id) if labelsToApply.indexOf(id) is -1
-
-    result
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe071fca67ca4396c312976be004e4e87cd875a5
--- /dev/null
+++ b/app/assets/javascripts/labels.js
@@ -0,0 +1,44 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Labels = (function() {
+    function Labels() {
+      this.setSuggestedColor = bind(this.setSuggestedColor, this);
+      this.updateColorPreview = bind(this.updateColorPreview, this);
+      var form;
+      form = $('.label-form');
+      this.cleanBinding();
+      this.addBinding();
+      this.updateColorPreview();
+    }
+
+    Labels.prototype.addBinding = function() {
+      $(document).on('click', '.suggest-colors a', this.setSuggestedColor);
+      return $(document).on('input', 'input#label_color', this.updateColorPreview);
+    };
+
+    Labels.prototype.cleanBinding = function() {
+      $(document).off('click', '.suggest-colors a');
+      return $(document).off('input', 'input#label_color');
+    };
+
+    Labels.prototype.updateColorPreview = function() {
+      var previewColor;
+      previewColor = $('input#label_color').val();
+      return $('div.label-color-preview').css('background-color', previewColor);
+    };
+
+    Labels.prototype.setSuggestedColor = function(e) {
+      var color;
+      color = $(e.currentTarget).data('color');
+      $('input#label_color').val(color);
+      this.updateColorPreview();
+      $('.label-form').trigger('keyup');
+      return e.preventDefault();
+    };
+
+    return Labels;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee
deleted file mode 100644
index d05bacd749470c9134018448d19772cd164adbae..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/labels.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-class @Labels
-  constructor: ->
-    form = $('.label-form')
-    @cleanBinding()
-    @addBinding()
-    @updateColorPreview()
-
-  addBinding: ->
-    $(document).on 'click', '.suggest-colors a', @setSuggestedColor
-    $(document).on 'input', 'input#label_color', @updateColorPreview
-
-  cleanBinding: ->
-    $(document).off 'click', '.suggest-colors a'
-    $(document).off 'input', 'input#label_color'
-
-  # Updates the the preview color with the hex-color input
-  updateColorPreview: =>
-    previewColor = $('input#label_color').val()
-    $('div.label-color-preview').css('background-color', previewColor)
-
-  # Updates the preview color with a click on a suggested color
-  setSuggestedColor: (e) =>
-    color = $(e.currentTarget).data('color')
-    $('input#label_color').val(color)
-    @updateColorPreview()
-    # Notify the form, that color has changed
-    $('.label-form').trigger('keyup')
-    e.preventDefault()
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..675dd5b7ceaa45ff080e420152bbc1ef6200e6df
--- /dev/null
+++ b/app/assets/javascripts/labels_select.js
@@ -0,0 +1,377 @@
+(function() {
+  this.LabelsSelect = (function() {
+    function LabelsSelect() {
+      var _this;
+      _this = this;
+      $('.js-label-select').each(function(i, dropdown) {
+        var $block, $colorPreview, $dropdown, $form, $loading, $newLabelCreateButton, $newLabelError, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, newColorField, newLabelField, projectId, resetForm, saveLabel, saveLabelData, selectedLabel, showAny, showNo;
+        $dropdown = $(dropdown);
+        projectId = $dropdown.data('project-id');
+        labelUrl = $dropdown.data('labels');
+        issueUpdateURL = $dropdown.data('issueUpdate');
+        selectedLabel = $dropdown.data('selected');
+        if ((selectedLabel != null) && !$dropdown.hasClass('js-multiselect')) {
+          selectedLabel = selectedLabel.split(',');
+        }
+        newLabelField = $('#new_label_name');
+        newColorField = $('#new_label_color');
+        showNo = $dropdown.data('show-no');
+        showAny = $dropdown.data('show-any');
+        defaultLabel = $dropdown.data('default-label');
+        abilityName = $dropdown.data('ability-name');
+        $selectbox = $dropdown.closest('.selectbox');
+        $block = $selectbox.closest('.block');
+        $form = $dropdown.closest('form');
+        $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
+        $value = $block.find('.value');
+        $newLabelError = $('.js-label-error');
+        $colorPreview = $('.js-dropdown-label-color-preview');
+        $newLabelCreateButton = $('.js-new-label-btn');
+        $newLabelError.hide();
+        $loading = $block.find('.block-loading').fadeOut();
+        if (issueUpdateURL != null) {
+          issueURLSplit = issueUpdateURL.split('/');
+        }
+        if (issueUpdateURL) {
+          labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
+          labelNoneHTMLTemplate = '<span class="no-value">None</span>';
+        }
+        if (newLabelField.length) {
+          $('.suggest-colors-dropdown a').on("click", function(e) {
+            e.preventDefault();
+            e.stopPropagation();
+            newColorField.val($(this).data('color')).trigger('change');
+            return $colorPreview.css('background-color', $(this).data('color')).parent().addClass('is-active');
+          });
+          resetForm = function() {
+            newLabelField.val('').trigger('change');
+            newColorField.val('').trigger('change');
+            return $colorPreview.css('background-color', '').parent().removeClass('is-active');
+          };
+          $('.dropdown-menu-back').on('click', function() {
+            return resetForm();
+          });
+          $('.js-cancel-label-btn').on('click', function(e) {
+            e.preventDefault();
+            e.stopPropagation();
+            resetForm();
+            return $('.dropdown-menu-back', $dropdown.parent()).trigger('click');
+          });
+          enableLabelCreateButton = function() {
+            if (newLabelField.val() !== '' && newColorField.val() !== '') {
+              $newLabelError.hide();
+              return $newLabelCreateButton.enable();
+            } else {
+              return $newLabelCreateButton.disable();
+            }
+          };
+          saveLabel = function() {
+            return Api.newLabel(projectId, {
+              name: newLabelField.val(),
+              color: newColorField.val()
+            }, function(label) {
+              var errors;
+              $newLabelCreateButton.enable();
+              if (label.message != null) {
+                errors = _.map(label.message, function(value, key) {
+                  return key + " " + value[0];
+                });
+                return $newLabelError.html(errors.join("<br/>")).show();
+              } else {
+                return $('.dropdown-menu-back', $dropdown.parent()).trigger('click');
+              }
+            });
+          };
+          newLabelField.on('keyup change', enableLabelCreateButton);
+          newColorField.on('keyup change', enableLabelCreateButton);
+          $newLabelCreateButton.disable().on('click', function(e) {
+            e.preventDefault();
+            e.stopPropagation();
+            return saveLabel();
+          });
+        }
+        saveLabelData = function() {
+          var data, selected;
+          selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
+            return this.value;
+          }).get();
+          data = {};
+          data[abilityName] = {};
+          data[abilityName].label_ids = selected;
+          if (!selected.length) {
+            data[abilityName].label_ids = [''];
+          }
+          $loading.fadeIn();
+          $dropdown.trigger('loading.gl.dropdown');
+          return $.ajax({
+            type: 'PUT',
+            url: issueUpdateURL,
+            dataType: 'JSON',
+            data: data
+          }).done(function(data) {
+            var labelCount, template;
+            $loading.fadeOut();
+            $dropdown.trigger('loaded.gl.dropdown');
+            $selectbox.hide();
+            data.issueURLSplit = issueURLSplit;
+            labelCount = 0;
+            if (data.labels.length) {
+              template = labelHTMLTemplate(data);
+              labelCount = data.labels.length;
+            } else {
+              template = labelNoneHTMLTemplate;
+            }
+            $value.removeAttr('style').html(template);
+            $sidebarCollapsedValue.text(labelCount);
+            $('.has-tooltip', $value).tooltip({
+              container: 'body'
+            });
+            return $value.find('a').each(function(i) {
+              return setTimeout((function(_this) {
+                return function() {
+                  return gl.animate.animate($(_this), 'pulse');
+                };
+              })(this), 200 * i);
+            });
+          });
+        };
+        return $dropdown.glDropdown({
+          data: function(term, callback) {
+            return $.ajax({
+              url: labelUrl
+            }).done(function(data) {
+              data = _.chain(data).groupBy(function(label) {
+                return label.title;
+              }).map(function(label) {
+                var color;
+                color = _.map(label, function(dup) {
+                  return dup.color;
+                });
+                return {
+                  id: label[0].id,
+                  title: label[0].title,
+                  color: color,
+                  duplicate: color.length > 1
+                };
+              }).value();
+              if ($dropdown.hasClass('js-extra-options')) {
+                if (showNo) {
+                  data.unshift({
+                    id: 0,
+                    title: 'No Label'
+                  });
+                }
+                if (showAny) {
+                  data.unshift({
+                    isAny: true,
+                    title: 'Any Label'
+                  });
+                }
+                if (data.length > 2) {
+                  data.splice(2, 0, 'divider');
+                }
+              }
+              return callback(data);
+            });
+          },
+          renderRow: function(label, instance) {
+            var $a, $li, active, color, colorEl, indeterminate, removesAll, selectedClass, spacing;
+            $li = $('<li>');
+            $a = $('<a href="#">');
+            selectedClass = [];
+            removesAll = label.id === 0 || (label.id == null);
+            if ($dropdown.hasClass('js-filter-bulk-update')) {
+              indeterminate = instance.indeterminateIds;
+              active = instance.activeIds;
+              if (indeterminate.indexOf(label.id) !== -1) {
+                selectedClass.push('is-indeterminate');
+              }
+              if (active.indexOf(label.id) !== -1) {
+                i = selectedClass.indexOf('is-indeterminate');
+                if (i !== -1) {
+                  selectedClass.splice(i, 1);
+                }
+                selectedClass.push('is-active');
+                instance.addInput(this.fieldName, label.id);
+              }
+            }
+            if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
+              selectedClass.push('is-active');
+            }
+            if ($dropdown.hasClass('js-multiselect') && removesAll) {
+              selectedClass.push('dropdown-clear-active');
+            }
+            if (label.duplicate) {
+              spacing = 100 / label.color.length;
+              label.color = label.color.filter(function(color, i) {
+                return i < 4;
+              });
+              color = _.map(label.color, function(color, i) {
+                var percentFirst, percentSecond;
+                percentFirst = Math.floor(spacing * i);
+                percentSecond = Math.floor(spacing * (i + 1));
+                return color + " " + percentFirst + "%," + color + " " + percentSecond + "% ";
+              }).join(',');
+              color = "linear-gradient(" + color + ")";
+            } else {
+              if (label.color != null) {
+                color = label.color[0];
+              }
+            }
+            if (color) {
+              colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>";
+            } else {
+              colorEl = '';
+            }
+            if (label.id) {
+              selectedClass.push('label-item');
+              $a.attr('data-label-id', label.id);
+            }
+            $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
+            return $li.html($a).prop('outerHTML');
+          },
+          persistWhenHide: $dropdown.data('persistWhenHide'),
+          search: {
+            fields: ['title']
+          },
+          selectable: true,
+          filterable: true,
+          toggleLabel: function(selected, el) {
+            var selected_labels;
+            selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active');
+            if (selected && (selected.title != null)) {
+              if (selected_labels.length > 1) {
+                return selected.title + " +" + (selected_labels.length - 1) + " more";
+              } else {
+                return selected.title;
+              }
+            } else if (!selected && selected_labels.length !== 0) {
+              if (selected_labels.length > 1) {
+                return ($(selected_labels[0]).text()) + " +" + (selected_labels.length - 1) + " more";
+              } else if (selected_labels.length === 1) {
+                return $(selected_labels).text();
+              }
+            } else {
+              return defaultLabel;
+            }
+          },
+          fieldName: $dropdown.data('field-name'),
+          id: function(label) {
+            if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) {
+              return label.title;
+            } else {
+              return label.id;
+            }
+          },
+          hidden: function() {
+            var isIssueIndex, isMRIndex, page, selectedLabels;
+            page = $('body').data('page');
+            isIssueIndex = page === 'projects:issues:index';
+            isMRIndex = page === 'projects:merge_requests:index';
+            $selectbox.hide();
+            $value.removeAttr('style');
+            if ($dropdown.hasClass('js-multiselect')) {
+              if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+                selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']");
+                Issuable.filterResults($dropdown.closest('form'));
+              } else if ($dropdown.hasClass('js-filter-submit')) {
+                $dropdown.closest('form').submit();
+              } else {
+                if (!$dropdown.hasClass('js-filter-bulk-update')) {
+                  saveLabelData();
+                }
+              }
+            }
+            if ($dropdown.hasClass('js-filter-bulk-update')) {
+              if (!this.options.persistWhenHide) {
+                return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
+              }
+            }
+          },
+          multiSelect: $dropdown.hasClass('js-multiselect'),
+          clicked: function(label) {
+            var isIssueIndex, isMRIndex, page;
+            _this.enableBulkLabelDropdown();
+            if ($dropdown.hasClass('js-filter-bulk-update')) {
+              return;
+            }
+            page = $('body').data('page');
+            isIssueIndex = page === 'projects:issues:index';
+            isMRIndex = page === 'projects:merge_requests:index';
+            if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+              if (!$dropdown.hasClass('js-multiselect')) {
+                selectedLabel = label.title;
+                return Issuable.filterResults($dropdown.closest('form'));
+              }
+            } else if ($dropdown.hasClass('js-filter-submit')) {
+              return $dropdown.closest('form').submit();
+            } else {
+              if ($dropdown.hasClass('js-multiselect')) {
+
+              } else {
+                return saveLabelData();
+              }
+            }
+          },
+          setIndeterminateIds: function() {
+            if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+              return this.indeterminateIds = _this.getIndeterminateIds();
+            }
+          },
+          setActiveIds: function() {
+            if (this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
+              return this.activeIds = _this.getActiveIds();
+            }
+          }
+        });
+      });
+      this.bindEvents();
+    }
+
+    LabelsSelect.prototype.bindEvents = function() {
+      return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue);
+    };
+
+    LabelsSelect.prototype.onSelectCheckboxIssue = function() {
+      if ($('.selected_issue:checked').length) {
+        return;
+      }
+      $('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
+      return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
+    };
+
+    LabelsSelect.prototype.getIndeterminateIds = function() {
+      var label_ids;
+      label_ids = [];
+      $('.selected_issue:checked').each(function(i, el) {
+        var issue_id;
+        issue_id = $(el).data('id');
+        return label_ids.push($("#issue_" + issue_id).data('labels'));
+      });
+      return _.flatten(label_ids);
+    };
+
+    LabelsSelect.prototype.getActiveIds = function() {
+      var label_ids;
+      label_ids = [];
+      $('.selected_issue:checked').each(function(i, el) {
+        var issue_id;
+        issue_id = $(el).data('id');
+        return label_ids.push($("#issue_" + issue_id).data('labels'));
+      });
+      return _.intersection.apply(_, label_ids);
+    };
+
+    LabelsSelect.prototype.enableBulkLabelDropdown = function() {
+      var issuableBulkActions;
+      if ($('.selected_issue:checked').length) {
+        issuableBulkActions = $('.bulk-update').data('bulkActions');
+        return issuableBulkActions.willUpdateLabels = true;
+      }
+    };
+
+    return LabelsSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
deleted file mode 100644
index 7688609b301dfa5516266e322d006c24e3d76331..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/labels_select.js.coffee
+++ /dev/null
@@ -1,386 +0,0 @@
-class @LabelsSelect
-  constructor: ->
-    _this = @
-
-    $('.js-label-select').each (i, dropdown) ->
-      $dropdown = $(dropdown)
-      projectId = $dropdown.data('project-id')
-      labelUrl = $dropdown.data('labels')
-      issueUpdateURL = $dropdown.data('issueUpdate')
-      selectedLabel = $dropdown.data('selected')
-      if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
-        selectedLabel = selectedLabel.split(',')
-      newLabelField = $('#new_label_name')
-      newColorField = $('#new_label_color')
-      showNo = $dropdown.data('show-no')
-      showAny = $dropdown.data('show-any')
-      defaultLabel = $dropdown.data('default-label')
-      abilityName = $dropdown.data('ability-name')
-      $selectbox = $dropdown.closest('.selectbox')
-      $block = $selectbox.closest('.block')
-      $form = $dropdown.closest('form')
-      $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
-      $value = $block.find('.value')
-      $newLabelError = $('.js-label-error')
-      $colorPreview = $('.js-dropdown-label-color-preview')
-      $newLabelCreateButton = $('.js-new-label-btn')
-
-      $newLabelError.hide()
-      $loading = $block.find('.block-loading').fadeOut()
-
-      issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
-      if issueUpdateURL
-        labelHTMLTemplate = _.template(
-            '<% _.each(labels, function(label){ %>
-            <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>">
-            <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;">
-            <%- label.title %>
-            </span>
-            </a>
-            <% }); %>'
-        )
-        labelNoneHTMLTemplate = '<span class="no-value">None</span>'
-
-      if newLabelField.length
-
-        # Suggested colors in the dropdown to chose from pre-chosen colors
-        $('.suggest-colors-dropdown a').on "click", (e) ->
-          e.preventDefault()
-          e.stopPropagation()
-          newColorField
-            .val($(this).data('color'))
-            .trigger('change')
-          $colorPreview
-            .css 'background-color', $(this).data('color')
-            .parent()
-            .addClass 'is-active'
-
-        # Cancel button takes back to first page
-        resetForm = ->
-          newLabelField
-            .val ''
-            .trigger 'change'
-          newColorField
-            .val ''
-            .trigger 'change'
-          $colorPreview
-            .css 'background-color', ''
-            .parent()
-            .removeClass 'is-active'
-
-        $('.dropdown-menu-back').on 'click', ->
-          resetForm()
-
-        $('.js-cancel-label-btn').on 'click', (e) ->
-          e.preventDefault()
-          e.stopPropagation()
-          resetForm()
-          $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
-        # Listen for change and keyup events on label and color field
-        # This allows us to enable the button when ready
-        enableLabelCreateButton = ->
-          if newLabelField.val() isnt '' and newColorField.val() isnt ''
-            $newLabelError.hide()
-            $newLabelCreateButton.enable()
-          else
-            $newLabelCreateButton.disable()
-
-        saveLabel = ->
-          # Create new label with API
-          Api.newLabel projectId, {
-            name: newLabelField.val()
-            color: newColorField.val()
-          }, (label) ->
-            $newLabelCreateButton.enable()
-
-            if label.message?
-              errors = _.map label.message, (value, key) ->
-                "#{key} #{value[0]}"
-
-              $newLabelError
-                .html errors.join("<br/>")
-                .show()
-            else
-              $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
-
-        newLabelField.on 'keyup change', enableLabelCreateButton
-
-        newColorField.on 'keyup change', enableLabelCreateButton
-
-        # Send the API call to create the label
-        $newLabelCreateButton
-          .disable()
-          .on 'click', (e) ->
-            e.preventDefault()
-            e.stopPropagation()
-            saveLabel()
-
-      saveLabelData = ->
-        selected = $dropdown
-          .closest('.selectbox')
-          .find("input[name='#{$dropdown.data('field-name')}']")
-          .map(->
-            @value
-          ).get()
-        data = {}
-        data[abilityName] = {}
-        data[abilityName].label_ids = selected
-        if not selected.length
-          data[abilityName].label_ids = ['']
-        $loading.fadeIn()
-        $dropdown.trigger('loading.gl.dropdown')
-        $.ajax(
-          type: 'PUT'
-          url: issueUpdateURL
-          dataType: 'JSON'
-          data: data
-        ).done (data) ->
-          $loading.fadeOut()
-          $dropdown.trigger('loaded.gl.dropdown')
-          $selectbox.hide()
-          data.issueURLSplit = issueURLSplit
-          labelCount = 0
-          if data.labels.length
-            template = labelHTMLTemplate(data)
-            labelCount = data.labels.length
-          else
-            template = labelNoneHTMLTemplate
-          $value
-            .removeAttr('style')
-            .html(template)
-          $sidebarCollapsedValue.text(labelCount)
-
-          $('.has-tooltip', $value).tooltip(container: 'body')
-
-          $value
-            .find('a')
-            .each((i) ->
-              setTimeout(=>
-                gl.animate.animate($(@), 'pulse')
-              ,200 * i
-              )
-            )
-
-
-      $dropdown.glDropdown(
-        data: (term, callback) ->
-          $.ajax(
-            url: labelUrl
-          ).done (data) ->
-            data = _.chain data
-              .groupBy (label) ->
-                label.title
-              .map (label) ->
-                color = _.map label, (dup) ->
-                  dup.color
-
-                return {
-                  id: label[0].id
-                  title: label[0].title
-                  color: color
-                  duplicate: color.length > 1
-                }
-              .value()
-
-            if $dropdown.hasClass 'js-extra-options'
-              if showNo
-                data.unshift(
-                  id: 0
-                  title: 'No Label'
-                )
-
-              if showAny
-                data.unshift(
-                  isAny: true
-                  title: 'Any Label'
-                )
-
-              if data.length > 2
-                data.splice 2, 0, 'divider'
-
-            callback data
-
-        renderRow: (label, instance) ->
-          $li = $('<li>')
-          $a  = $('<a href="#">')
-
-          selectedClass = []
-          removesAll = label.id is 0 or not label.id?
-
-          if $dropdown.hasClass('js-filter-bulk-update')
-            indeterminate = instance.indeterminateIds
-            active = instance.activeIds
-
-            if indeterminate.indexOf(label.id) isnt -1
-              selectedClass.push 'is-indeterminate'
-
-            if active.indexOf(label.id) isnt -1
-              # Remove is-indeterminate class if the item will be marked as active
-              i = selectedClass.indexOf 'is-indeterminate'
-              selectedClass.splice i, 1 unless i is -1
-
-              selectedClass.push 'is-active'
-
-              # Add input manually
-              instance.addInput @fieldName, label.id
-
-          if $form.find("input[type='hidden']\
-            [name='#{$dropdown.data('fieldName')}']\
-            [value='#{this.id(label)}']").length
-            selectedClass.push 'is-active'
-
-          if $dropdown.hasClass('js-multiselect') and removesAll
-            selectedClass.push 'dropdown-clear-active'
-
-          if label.duplicate
-            spacing = 100 / label.color.length
-
-            # Reduce the colors to 4
-            label.color = label.color.filter (color, i) ->
-              i < 4
-
-            color = _.map(label.color, (color, i) ->
-              percentFirst = Math.floor(spacing * i)
-              percentSecond = Math.floor(spacing * (i + 1))
-              "#{color} #{percentFirst}%,#{color} #{percentSecond}% "
-            ).join(',')
-            color = "linear-gradient(#{color})"
-          else
-            if label.color?
-              color = label.color[0]
-
-          if color
-            colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
-          else
-            colorEl = ''
-
-          # We need to identify which items are actually labels
-          if label.id
-            selectedClass.push('label-item')
-            $a.attr('data-label-id', label.id)
-
-          $a.addClass(selectedClass.join(' '))
-            .html("#{colorEl} #{label.title}")
-
-          # Return generated html
-          $li.html($a).prop('outerHTML')
-        persistWhenHide: $dropdown.data('persistWhenHide')
-        search:
-          fields: ['title']
-        selectable: true
-        filterable: true
-        toggleLabel: (selected, el) ->
-          selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
-
-          if selected and selected.title?
-            if selected_labels.length > 1
-              "#{selected.title} +#{selected_labels.length - 1} more"
-            else
-              selected.title
-          else if not selected and selected_labels.length isnt 0
-            if selected_labels.length > 1
-              "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
-            else if selected_labels.length is 1
-              $(selected_labels).text()
-          else
-            defaultLabel
-        fieldName: $dropdown.data('field-name')
-        id: (label) ->
-          if $dropdown.hasClass("js-filter-submit") and not label.isAny?
-            label.title
-          else
-            label.id
-
-        hidden: ->
-          page = $('body').data 'page'
-          isIssueIndex = page is 'projects:issues:index'
-          isMRIndex = page is 'projects:merge_requests:index'
-
-          $selectbox.hide()
-          # display:block overrides the hide-collapse rule
-          $value.removeAttr('style')
-          if $dropdown.hasClass 'js-multiselect'
-            if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
-              selectedLabels = $dropdown
-                .closest('form')
-                .find("input:hidden[name='#{$dropdown.data('fieldName')}']")
-              Issuable.filterResults $dropdown.closest('form')
-            else if $dropdown.hasClass('js-filter-submit')
-              $dropdown.closest('form').submit()
-            else
-              if not $dropdown.hasClass 'js-filter-bulk-update'
-                saveLabelData()
-
-          if $dropdown.hasClass('js-filter-bulk-update')
-            # If we are persisting state we need the classes
-            if not @options.persistWhenHide
-              $dropdown.parent().find('.is-active, .is-indeterminate').removeClass()
-
-        multiSelect: $dropdown.hasClass 'js-multiselect'
-        clicked: (label) ->
-          _this.enableBulkLabelDropdown()
-
-          if $dropdown.hasClass('js-filter-bulk-update')
-            return
-
-          page = $('body').data 'page'
-          isIssueIndex = page is 'projects:issues:index'
-          isMRIndex = page is 'projects:merge_requests:index'
-          if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
-            if not $dropdown.hasClass 'js-multiselect'
-              selectedLabel = label.title
-              Issuable.filterResults $dropdown.closest('form')
-          else if $dropdown.hasClass 'js-filter-submit'
-            $dropdown.closest('form').submit()
-          else
-            if $dropdown.hasClass 'js-multiselect'
-              return
-            else
-              saveLabelData()
-
-        setIndeterminateIds: ->
-          if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
-            @indeterminateIds = _this.getIndeterminateIds()
-
-        setActiveIds: ->
-          if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
-            @activeIds = _this.getActiveIds()
-      )
-
-    @bindEvents()
-
-  bindEvents: ->
-    $('body').on 'change', '.selected_issue', @onSelectCheckboxIssue
-
-  onSelectCheckboxIssue: ->
-    return if $('.selected_issue:checked').length
-
-    # Remove inputs
-    $('.issues_bulk_update .labels-filter input[type="hidden"]').remove()
-
-    # Also restore button text
-    $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label')
-
-  getIndeterminateIds: ->
-    label_ids = []
-
-    $('.selected_issue:checked').each (i, el) ->
-      issue_id = $(el).data('id')
-      label_ids.push $("#issue_#{issue_id}").data('labels')
-
-    _.flatten(label_ids)
-
-  getActiveIds: ->
-    label_ids = []
-
-    $('.selected_issue:checked').each (i, el) ->
-      issue_id = $(el).data('id')
-      label_ids.push $("#issue_#{issue_id}").data('labels')
-
-    _.intersection.apply _, label_ids
-
-  enableBulkLabelDropdown: ->
-    if $('.selected_issue:checked').length
-      issuableBulkActions = $('.bulk-update').data('bulkActions')
-      issuableBulkActions.willUpdateLabels = true
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce472f3bcd0aac3d6e411a4e637295610dc2a1a2
--- /dev/null
+++ b/app/assets/javascripts/layout_nav.js
@@ -0,0 +1,27 @@
+(function() {
+  var hideEndFade;
+
+  hideEndFade = function($scrollingTabs) {
+    return $scrollingTabs.each(function() {
+      var $this;
+      $this = $(this);
+      return $this.siblings('.fade-right').toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'));
+    });
+  };
+
+  $(function() {
+    hideEndFade($('.scrolling-tabs'));
+    $(window).off('resize.nav').on('resize.nav', function() {
+      return hideEndFade($('.scrolling-tabs'));
+    });
+    return $('.scrolling-tabs').on('scroll', function(event) {
+      var $this, currentPosition, maxPosition;
+      $this = $(this);
+      currentPosition = $this.scrollLeft();
+      maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
+      $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
+      return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
+    });
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee
deleted file mode 100644
index f639f7f589278e96a212dd4261c9496e963428a1..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/layout_nav.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-hideEndFade = ($scrollingTabs) ->
-  $scrollingTabs.each ->
-    $this = $(@)
-
-    $this
-      .siblings('.fade-right')
-      .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
-
-$ ->
-
-  hideEndFade($('.scrolling-tabs'))
-
-  $(window)
-    .off 'resize.nav'
-    .on 'resize.nav', ->
-      hideEndFade($('.scrolling-tabs'))
-
-  $('.scrolling-tabs').on 'scroll', (event) ->
-    $this = $(this)
-    currentPosition = $this.scrollLeft()
-    maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
-
-    $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
-    $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js
new file mode 100644
index 0000000000000000000000000000000000000000..8d5e52286b7be5206cb7936ae77d5019d113b5c2
--- /dev/null
+++ b/app/assets/javascripts/lib/chart.js
@@ -0,0 +1,7 @@
+
+/*= require Chart */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/chart.js.coffee b/app/assets/javascripts/lib/chart.js.coffee
deleted file mode 100644
index 82217fc5107fd6e48665ab33e0273b2f0f62d4b7..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/chart.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require Chart
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ee81804513b831003744d9a772cd7849e029dc9
--- /dev/null
+++ b/app/assets/javascripts/lib/cropper.js
@@ -0,0 +1,7 @@
+
+/*= require cropper */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/cropper.js.coffee b/app/assets/javascripts/lib/cropper.js.coffee
deleted file mode 100644
index 32536d23fe3689f1b044389acbba51120182edb6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/cropper.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require cropper
diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js
new file mode 100644
index 0000000000000000000000000000000000000000..31e6033e75666a5a8f8844f9ebbbb0e6cdde88a1
--- /dev/null
+++ b/app/assets/javascripts/lib/d3.js
@@ -0,0 +1,7 @@
+
+/*= require d3 */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/d3.js.coffee b/app/assets/javascripts/lib/d3.js.coffee
deleted file mode 100644
index 74f0a0bb06aacc579896b9faa9df11589ed9b756..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/d3.js.coffee
+++ /dev/null
@@ -1 +0,0 @@
-#= require d3
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
new file mode 100644
index 0000000000000000000000000000000000000000..923c575dcfe637946f7344a714029775ad98f9d5
--- /dev/null
+++ b/app/assets/javascripts/lib/raphael.js
@@ -0,0 +1,13 @@
+
+/*= require raphael */
+
+
+/*= require g.raphael */
+
+
+/*= require g.bar */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/raphael.js.coffee b/app/assets/javascripts/lib/raphael.js.coffee
deleted file mode 100644
index ab8e5979b871fcbed55b5ae60fab143850882a7d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/raphael.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-#= require raphael
-#= require g.raphael
-#= require g.bar
diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js
new file mode 100644
index 0000000000000000000000000000000000000000..d36efdabc93854460e5bf15be8c3d1202478e46e
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/animate.js
@@ -0,0 +1,49 @@
+(function() {
+  (function(w) {
+    if (w.gl == null) {
+      w.gl = {};
+    }
+    if (gl.animate == null) {
+      gl.animate = {};
+    }
+    gl.animate.animate = function($el, animation, options, done) {
+      if ((options != null ? options.cssStart : void 0) != null) {
+        $el.css(options.cssStart);
+      }
+      $el.removeClass(animation + ' animated').addClass(animation + ' animated').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
+        $(this).removeClass(animation + ' animated');
+        if (done != null) {
+          done();
+        }
+        if ((options != null ? options.cssEnd : void 0) != null) {
+          $el.css(options.cssEnd);
+        }
+      });
+    };
+    gl.animate.animateEach = function($els, animation, time, options, done) {
+      var dfd;
+      dfd = $.Deferred();
+      if (!$els.length) {
+        dfd.resolve();
+      }
+      $els.each(function(i) {
+        setTimeout((function(_this) {
+          return function() {
+            var $this;
+            $this = $(_this);
+            return gl.animate.animate($this, animation, options, function() {
+              if (i === $els.length - 1) {
+                dfd.resolve();
+                if (done != null) {
+                  return done();
+                }
+              }
+            });
+          };
+        })(this), time * i);
+      });
+      return dfd.promise();
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/animate.js.coffee b/app/assets/javascripts/lib/utils/animate.js.coffee
deleted file mode 100644
index ec3b44d61263374ce7e5d8b5f01535cd0afbc3bd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/animate.js.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-((w) -> 
-  if not w.gl? then w.gl = {}
-  if not gl.animate? then gl.animate = {}
-
-  gl.animate.animate = ($el, animation, options, done) ->
-    if options?.cssStart?
-      $el.css(options.cssStart)
-    $el
-      .removeClass(animation + ' animated')
-      .addClass(animation + ' animated')
-      .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
-        $(this).removeClass(animation + ' animated')
-        if done?
-          done()
-        if options?.cssEnd?
-          $el.css(options.cssEnd)
-        return
-    return
-
-  gl.animate.animateEach = ($els, animation, time, options, done) ->
-    dfd = $.Deferred()
-    if not $els.length
-      dfd.resolve()
-    $els.each((i) ->
-      setTimeout(=>
-        $this = $(@)
-        gl.animate.animate($this, animation, options, =>
-          if i is $els.length - 1
-            dfd.resolve()
-            if done?
-              done()
-        )
-      ,time * i
-      )
-      return
-    )
-    return dfd.promise()
-  return 
-) window
\ No newline at end of file
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..9299d0eabd27f5f7702ec9c1346e3ca09396fd69
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -0,0 +1,60 @@
+(function() {
+  (function(w) {
+    var base;
+    w.gl || (w.gl = {});
+    (base = w.gl).utils || (base.utils = {});
+    w.gl.utils.isInGroupsPage = function() {
+      return gl.utils.getPagePath() === 'groups';
+    };
+    w.gl.utils.isInProjectPage = function() {
+      return gl.utils.getPagePath() === 'projects';
+    };
+    w.gl.utils.getProjectSlug = function() {
+      if (this.isInProjectPage()) {
+        return $('body').data('project');
+      } else {
+        return null;
+      }
+    };
+    w.gl.utils.getGroupSlug = function() {
+      if (this.isInGroupsPage()) {
+        return $('body').data('group');
+      } else {
+        return null;
+      }
+    };
+    gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
+      return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle');
+    };
+    gl.utils.preventDisabledButtons = function() {
+      return $('.btn').click(function(e) {
+        if ($(this).hasClass('disabled')) {
+          e.preventDefault();
+          e.stopImmediatePropagation();
+          return false;
+        }
+      });
+    };
+    gl.utils.getPagePath = function() {
+      return $('body').data('page').split(':')[0];
+    };
+    return jQuery.timefor = function(time, suffix, expiredLabel) {
+      var suffixFromNow, timefor;
+      if (!time) {
+        return '';
+      }
+      suffix || (suffix = 'remaining');
+      expiredLabel || (expiredLabel = 'Past due');
+      jQuery.timeago.settings.allowFuture = true;
+      suffixFromNow = jQuery.timeago.settings.strings.suffixFromNow;
+      jQuery.timeago.settings.strings.suffixFromNow = suffix;
+      timefor = $.timeago(time);
+      if (timefor.indexOf('ago') > -1) {
+        timefor = expiredLabel;
+      }
+      jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow;
+      return timefor;
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee
deleted file mode 100644
index d4dd3dc329a9825a481bd3f0ea758f9b6310ce6f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/common_utils.js.coffee
+++ /dev/null
@@ -1,68 +0,0 @@
-((w) ->
-
-  w.gl       or= {}
-  w.gl.utils or= {}
-
-  w.gl.utils.isInGroupsPage = ->
-
-    return gl.utils.getPagePath() is 'groups'
-
-
-  w.gl.utils.isInProjectPage = ->
-
-    return gl.utils.getPagePath() is 'projects'
-
-
-  w.gl.utils.getProjectSlug = ->
-
-    return if @isInProjectPage() then $('body').data 'project' else null
-
-
-  w.gl.utils.getGroupSlug = ->
-
-    return if @isInGroupsPage() then $('body').data 'group' else null
-
-
-
-  gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
-
-    $tooltipEl
-      .tooltip 'destroy'
-      .attr    'title', newTitle
-      .tooltip 'fixTitle'
-
-
-  gl.utils.preventDisabledButtons = ->
-
-    $('.btn').click (e) ->
-      if $(this).hasClass 'disabled'
-        e.preventDefault()
-        e.stopImmediatePropagation()
-        return false
-
-  gl.utils.getPagePath = ->
-    return $('body').data('page').split(':')[0]
-
-
-  jQuery.timefor = (time, suffix, expiredLabel) ->
-
-    return '' unless time
-
-    suffix       or= 'remaining'
-    expiredLabel or= 'Past due'
-
-    jQuery.timeago.settings.allowFuture = yes
-
-    { suffixFromNow } = jQuery.timeago.settings.strings
-    jQuery.timeago.settings.strings.suffixFromNow = suffix
-
-    timefor = $.timeago time
-
-    if timefor.indexOf('ago') > -1
-      timefor = expiredLabel
-
-    jQuery.timeago.settings.strings.suffixFromNow = suffixFromNow
-
-    return timefor
-
-) window
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..e817261f2103ae6b07ed650ac972e5d3a8aaefe8
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -0,0 +1,36 @@
+(function() {
+  (function(w) {
+    var base;
+    if (w.gl == null) {
+      w.gl = {};
+    }
+    if ((base = w.gl).utils == null) {
+      base.utils = {};
+    }
+    w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+    w.gl.utils.formatDate = function(datetime) {
+      return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+    };
+    w.gl.utils.getDayName = function(date) {
+      return this.days[date.getDay()];
+    };
+    return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
+      if (setTimeago == null) {
+        setTimeago = true;
+      }
+      $timeagoEls.each(function() {
+        var $el;
+        $el = $(this);
+        return $el.attr('title', gl.utils.formatDate($el.attr('datetime')));
+      });
+      if (setTimeago) {
+        $timeagoEls.timeago();
+        $timeagoEls.tooltip('destroy');
+        return $timeagoEls.tooltip({
+          template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+        });
+      }
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
deleted file mode 100644
index 2371e913844ca27bc7282d99431bcc0d24e5c273..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
+++ /dev/null
@@ -1,28 +0,0 @@
-((w) ->
-
-  w.gl ?= {}
-  w.gl.utils ?= {}
-  w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
-
-  w.gl.utils.formatDate = (datetime) ->
-    dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
-
-  w.gl.utils.getDayName = (date) ->
-    this.days[date.getDay()]
-
-  w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
-    $timeagoEls.each( ->
-          $el = $(@)
-          $el.attr('title', gl.utils.formatDate($el.attr('datetime')))
-    )
-
-    if setTimeago
-      $timeagoEls.timeago()
-      $timeagoEls.tooltip('destroy')
-
-      # Recreate with custom template
-      $timeagoEls.tooltip(
-        template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
-      )
-
-) window
diff --git a/app/assets/javascripts/lib/utils/md5.js b/app/assets/javascripts/lib/utils/md5.js
deleted file mode 100644
index b63716eaad2f8f90678b99ccac2eb7f226214847..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/md5.js
+++ /dev/null
@@ -1,211 +0,0 @@
-function md5 (str) {
-  // http://kevin.vanzonneveld.net
-  // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
-  // + namespaced by: Michael White (http://getsprink.com)
-  // +    tweaked by: Jack
-  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
-  // +      input by: Brett Zamir (http://brett-zamir.me)
-  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
-  // -    depends on: utf8_encode
-  // *     example 1: md5('Kevin van Zonneveld');
-  // *     returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
-  var xl;
-
-  var rotateLeft = function (lValue, iShiftBits) {
-    return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
-  };
-
-  var addUnsigned = function (lX, lY) {
-    var lX4, lY4, lX8, lY8, lResult;
-    lX8 = (lX & 0x80000000);
-    lY8 = (lY & 0x80000000);
-    lX4 = (lX & 0x40000000);
-    lY4 = (lY & 0x40000000);
-    lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
-    if (lX4 & lY4) {
-      return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
-    }
-    if (lX4 | lY4) {
-      if (lResult & 0x40000000) {
-        return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
-      } else {
-        return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
-      }
-    } else {
-      return (lResult ^ lX8 ^ lY8);
-    }
-  };
-
-  var _F = function (x, y, z) {
-    return (x & y) | ((~x) & z);
-  };
-  var _G = function (x, y, z) {
-    return (x & z) | (y & (~z));
-  };
-  var _H = function (x, y, z) {
-    return (x ^ y ^ z);
-  };
-  var _I = function (x, y, z) {
-    return (y ^ (x | (~z)));
-  };
-
-  var _FF = function (a, b, c, d, x, s, ac) {
-    a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
-    return addUnsigned(rotateLeft(a, s), b);
-  };
-
-  var _GG = function (a, b, c, d, x, s, ac) {
-    a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
-    return addUnsigned(rotateLeft(a, s), b);
-  };
-
-  var _HH = function (a, b, c, d, x, s, ac) {
-    a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
-    return addUnsigned(rotateLeft(a, s), b);
-  };
-
-  var _II = function (a, b, c, d, x, s, ac) {
-    a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
-    return addUnsigned(rotateLeft(a, s), b);
-  };
-
-  var convertToWordArray = function (str) {
-    var lWordCount;
-    var lMessageLength = str.length;
-    var lNumberOfWords_temp1 = lMessageLength + 8;
-    var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
-    var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
-    var lWordArray = new Array(lNumberOfWords - 1);
-    var lBytePosition = 0;
-    var lByteCount = 0;
-    while (lByteCount < lMessageLength) {
-      lWordCount = (lByteCount - (lByteCount % 4)) / 4;
-      lBytePosition = (lByteCount % 4) * 8;
-      lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
-      lByteCount++;
-    }
-    lWordCount = (lByteCount - (lByteCount % 4)) / 4;
-    lBytePosition = (lByteCount % 4) * 8;
-    lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
-    lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
-    lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
-    return lWordArray;
-  };
-
-  var wordToHex = function (lValue) {
-    var wordToHexValue = "",
-      wordToHexValue_temp = "",
-      lByte, lCount;
-    for (lCount = 0; lCount <= 3; lCount++) {
-      lByte = (lValue >>> (lCount * 8)) & 255;
-      wordToHexValue_temp = "0" + lByte.toString(16);
-      wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
-    }
-    return wordToHexValue;
-  };
-
-  var x = [],
-    k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
-    S12 = 12,
-    S13 = 17,
-    S14 = 22,
-    S21 = 5,
-    S22 = 9,
-    S23 = 14,
-    S24 = 20,
-    S31 = 4,
-    S32 = 11,
-    S33 = 16,
-    S34 = 23,
-    S41 = 6,
-    S42 = 10,
-    S43 = 15,
-    S44 = 21;
-
-  str = this.utf8_encode(str);
-  x = convertToWordArray(str);
-  a = 0x67452301;
-  b = 0xEFCDAB89;
-  c = 0x98BADCFE;
-  d = 0x10325476;
-
-  xl = x.length;
-  for (k = 0; k < xl; k += 16) {
-    AA = a;
-    BB = b;
-    CC = c;
-    DD = d;
-    a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
-    d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
-    c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
-    b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
-    a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
-    d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
-    c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
-    b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
-    a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
-    d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
-    c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
-    b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
-    a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
-    d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
-    c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
-    b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
-    a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
-    d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
-    c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
-    b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
-    a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
-    d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
-    c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
-    b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
-    a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
-    d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
-    c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
-    b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
-    a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
-    d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
-    c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
-    b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
-    a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
-    d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
-    c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
-    b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
-    a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
-    d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
-    c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
-    b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
-    a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
-    d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
-    c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
-    b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
-    a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
-    d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
-    c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
-    b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
-    a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
-    d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
-    c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
-    b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
-    a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
-    d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
-    c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
-    b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
-    a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
-    d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
-    c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
-    b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
-    a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
-    d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
-    c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
-    b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
-    a = addUnsigned(a, AA);
-    b = addUnsigned(b, BB);
-    c = addUnsigned(c, CC);
-    d = addUnsigned(d, DD);
-  }
-
-  var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
-
-  return temp.toLowerCase();
-}
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
new file mode 100644
index 0000000000000000000000000000000000000000..42b6ac0589ed3ac8361dee2a34be4e80f62b3516
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -0,0 +1,41 @@
+(function() {
+  (function(w) {
+    var notificationGranted, notifyMe, notifyPermissions;
+    notificationGranted = function(message, opts, onclick) {
+      var notification;
+      notification = new Notification(message, opts);
+      setTimeout(function() {
+        return notification.close();
+      }, 8000);
+      if (onclick) {
+        return notification.onclick = onclick;
+      }
+    };
+    notifyPermissions = function() {
+      if ('Notification' in window) {
+        return Notification.requestPermission();
+      }
+    };
+    notifyMe = function(message, body, icon, onclick) {
+      var opts;
+      opts = {
+        body: body,
+        icon: icon
+      };
+      if (!('Notification' in window)) {
+
+      } else if (Notification.permission === 'granted') {
+        return notificationGranted(message, opts, onclick);
+      } else if (Notification.permission !== 'denied') {
+        return Notification.requestPermission(function(permission) {
+          if (permission === 'granted') {
+            return notificationGranted(message, opts, onclick);
+          }
+        });
+      }
+    };
+    w.notify = notifyMe;
+    return w.notifyPermissions = notifyPermissions;
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/notify.js.coffee b/app/assets/javascripts/lib/utils/notify.js.coffee
deleted file mode 100644
index 9e28353ac34e615fce0c1b3ea495f56d71e4e9e1..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/notify.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-((w) ->
-  notificationGranted = (message, opts, onclick) ->
-    notification = new Notification(message, opts)
-
-    # Hide the notification after X amount of seconds
-    setTimeout ->
-      notification.close()
-    , 8000
-
-    if onclick
-      notification.onclick = onclick
-
-  notifyPermissions = ->
-    if 'Notification' of window
-      Notification.requestPermission()
-
-  notifyMe = (message, body, icon, onclick) ->
-    opts =
-      body: body
-      icon: icon
-    # Let's check if the browser supports notifications
-    if !('Notification' of window)
-      # do nothing
-    else if Notification.permission == 'granted'
-      # If it's okay let's create a notification
-      notificationGranted message, opts, onclick
-    else if Notification.permission != 'denied'
-      Notification.requestPermission (permission) ->
-        # If the user accepts, let's create a notification
-        if permission == 'granted'
-          notificationGranted message, opts, onclick
-
-  w.notify = notifyMe
-  w.notifyPermissions = notifyPermissions
-) window
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..130479642f32deb27ed79e278bce1df8465cbfcb
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -0,0 +1,112 @@
+(function() {
+  (function(w) {
+    var base;
+    if (w.gl == null) {
+      w.gl = {};
+    }
+    if ((base = w.gl).text == null) {
+      base.text = {};
+    }
+    gl.text.randomString = function() {
+      return Math.random().toString(36).substring(7);
+    };
+    gl.text.replaceRange = function(s, start, end, substitute) {
+      return s.substring(0, start) + substitute + s.substring(end);
+    };
+    gl.text.selectedText = function(text, textarea) {
+      return text.substring(textarea.selectionStart, textarea.selectionEnd);
+    };
+    gl.text.lineBefore = function(text, textarea) {
+      var split;
+      split = text.substring(0, textarea.selectionStart).trim().split('\n');
+      return split[split.length - 1];
+    };
+    gl.text.lineAfter = function(text, textarea) {
+      return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+    };
+    gl.text.blockTagText = function(text, textArea, blockTag, selected) {
+      var lineAfter, lineBefore;
+      lineBefore = this.lineBefore(text, textArea);
+      lineAfter = this.lineAfter(text, textArea);
+      if (lineBefore === blockTag && lineAfter === blockTag) {
+        if (blockTag != null) {
+          textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
+          textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
+        }
+        return selected;
+      } else {
+        return blockTag + "\n" + selected + "\n" + blockTag;
+      }
+    };
+    gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
+      var insertText, inserted, selectedSplit, startChar;
+      selectedSplit = selected.split('\n');
+      startChar = !wrap && textArea.selectionStart > 0 ? '\n' : '';
+      if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
+        if (blockTag != null) {
+          insertText = this.blockTagText(text, textArea, blockTag, selected);
+        } else {
+          insertText = selectedSplit.map(function(val) {
+            if (val.indexOf(tag) === 0) {
+              return "" + (val.replace(tag, ''));
+            } else {
+              return "" + tag + val;
+            }
+          }).join('\n');
+        }
+      } else {
+        insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
+      }
+      if (document.queryCommandSupported('insertText')) {
+        inserted = document.execCommand('insertText', false, insertText);
+      }
+      if (!inserted) {
+        try {
+          document.execCommand("ms-beginUndoUnit");
+        } catch (undefined) {}
+        textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
+        try {
+          document.execCommand("ms-endUndoUnit");
+        } catch (undefined) {}
+      }
+      return this.moveCursor(textArea, tag, wrap);
+    };
+    gl.text.moveCursor = function(textArea, tag, wrapped) {
+      var pos;
+      if (!textArea.setSelectionRange) {
+        return;
+      }
+      if (textArea.selectionStart === textArea.selectionEnd) {
+        if (wrapped) {
+          pos = textArea.selectionStart - tag.length;
+        } else {
+          pos = textArea.selectionStart;
+        }
+        return textArea.setSelectionRange(pos, pos);
+      }
+    };
+    gl.text.updateText = function(textArea, tag, blockTag, wrap) {
+      var $textArea, oldVal, selected, text;
+      $textArea = $(textArea);
+      oldVal = $textArea.val();
+      textArea = $textArea.get(0);
+      text = $textArea.val();
+      selected = this.selectedText(text, textArea);
+      $textArea.focus();
+      return this.insertText(textArea, text, tag, blockTag, selected, wrap);
+    };
+    gl.text.init = function(form) {
+      var self;
+      self = this;
+      return $('.js-md', form).off('click').on('click', function() {
+        var $this;
+        $this = $(this);
+        return self.updateText($this.closest('.md-area').find('textarea'), $this.data('md-tag'), $this.data('md-block'), !$this.data('md-prepend'));
+      });
+    };
+    return gl.text.removeListeners = function(form) {
+      return $('.js-md', form).off();
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee
deleted file mode 100644
index 2e1407f8738aa1f45761ae22829be10c3a66d3cb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/text_utility.js.coffee
+++ /dev/null
@@ -1,105 +0,0 @@
-((w) ->
-  w.gl ?= {}
-  w.gl.text ?= {}
-
-  gl.text.randomString = -> Math.random().toString(36).substring(7)
-
-  gl.text.replaceRange = (s, start, end, substitute) ->
-    s.substring(0, start) + substitute + s.substring(end);
-
-  gl.text.selectedText = (text, textarea) ->
-    text.substring(textarea.selectionStart, textarea.selectionEnd)
-
-  gl.text.lineBefore = (text, textarea) ->
-    split = text.substring(0, textarea.selectionStart).trim().split('\n')
-    split[split.length - 1]
-
-  gl.text.lineAfter = (text, textarea) ->
-    text.substring(textarea.selectionEnd).trim().split('\n')[0]
-
-  gl.text.blockTagText = (text, textArea, blockTag, selected) ->
-    lineBefore = @lineBefore(text, textArea)
-    lineAfter = @lineAfter(text, textArea)
-
-    if lineBefore is blockTag and lineAfter is blockTag
-      # To remove the block tag we have to select the line before & after
-      if blockTag?
-        textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1)
-        textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1)
-
-      selected
-    else
-      "#{blockTag}\n#{selected}\n#{blockTag}"
-
-  gl.text.insertText = (textArea, text, tag, blockTag, selected, wrap) ->
-    selectedSplit = selected.split('\n')
-    startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
-
-    if selectedSplit.length > 1 and (not wrap or blockTag?)
-      if blockTag?
-        insertText = @blockTagText(text, textArea, blockTag, selected)
-      else
-        insertText = selectedSplit.map((val) ->
-          if val.indexOf(tag) is 0
-            "#{val.replace(tag, '')}"
-          else
-            "#{tag}#{val}"
-        ).join('\n')
-    else
-      insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
-
-    if document.queryCommandSupported('insertText')
-      inserted = document.execCommand 'insertText', false, insertText
-
-    unless inserted
-      try
-        document.execCommand("ms-beginUndoUnit")
-
-      textArea.value = @replaceRange(
-          text,
-          textArea.selectionStart,
-          textArea.selectionEnd,
-          insertText)
-      try
-        document.execCommand("ms-endUndoUnit")
-
-    @moveCursor(textArea, tag, wrap)
-
-  gl.text.moveCursor = (textArea, tag, wrapped) ->
-    return unless textArea.setSelectionRange
-
-    if textArea.selectionStart is textArea.selectionEnd
-      if wrapped
-        pos = textArea.selectionStart - tag.length
-      else
-        pos = textArea.selectionStart
-
-      textArea.setSelectionRange pos, pos
-
-  gl.text.updateText = (textArea, tag, blockTag, wrap) ->
-    $textArea = $(textArea)
-    oldVal = $textArea.val()
-    textArea = $textArea.get(0)
-    text = $textArea.val()
-    selected = @selectedText(text, textArea)
-    $textArea.focus()
-
-    @insertText(textArea, text, tag, blockTag, selected, wrap)
-
-  gl.text.init = (form) ->
-    self = @
-    $('.js-md', form)
-      .off 'click'
-      .on 'click', ->
-        $this = $(@)
-        self.updateText(
-          $this.closest('.md-area').find('textarea'),
-          $this.data('md-tag'),
-          $this.data('md-block'),
-          not $this.data('md-prepend')
-        )
-
-  gl.text.removeListeners = (form) ->
-    $('.js-md', form).off()
-
-) window
diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc30babd645d9d5f4b3be00fd8af55b7abf8eba4
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/type_utility.js
@@ -0,0 +1,15 @@
+(function() {
+  (function(w) {
+    var base;
+    if (w.gl == null) {
+      w.gl = {};
+    }
+    if ((base = w.gl).utils == null) {
+      base.utils = {};
+    }
+    return w.gl.utils.isObject = function(obj) {
+      return (obj != null) && (obj.constructor === Object);
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/type_utility.js.coffee b/app/assets/javascripts/lib/utils/type_utility.js.coffee
deleted file mode 100644
index 957f0d86b36ec7f8e5d59980ee69ecf0f18165d5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/type_utility.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-((w) ->
-
-  w.gl ?= {}
-  w.gl.utils ?= {}
-
-  w.gl.utils.isObject = (obj) ->
-    obj? and (obj.constructor is Object)
-
-) window
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..fffbfd19745d7887c2533f130ef3ecefb44a886e
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -0,0 +1,64 @@
+(function() {
+  (function(w) {
+    var base;
+    if (w.gl == null) {
+      w.gl = {};
+    }
+    if ((base = w.gl).utils == null) {
+      base.utils = {};
+    }
+    w.gl.utils.getParameterValues = function(sParam) {
+      var i, sPageURL, sParameterName, sURLVariables, values;
+      sPageURL = decodeURIComponent(window.location.search.substring(1));
+      sURLVariables = sPageURL.split('&');
+      sParameterName = void 0;
+      values = [];
+      i = 0;
+      while (i < sURLVariables.length) {
+        sParameterName = sURLVariables[i].split('=');
+        if (sParameterName[0] === sParam) {
+          values.push(sParameterName[1]);
+        }
+        i++;
+      }
+      return values;
+    };
+    w.gl.utils.mergeUrlParams = function(params, url) {
+      var lastChar, newUrl, paramName, paramValue, pattern;
+      newUrl = decodeURIComponent(url);
+      for (paramName in params) {
+        paramValue = params[paramName];
+        pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
+        if (paramValue == null) {
+          newUrl = newUrl.replace(pattern, '');
+        } else if (url.search(pattern) !== -1) {
+          newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
+        } else {
+          newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
+        }
+      }
+      lastChar = newUrl[newUrl.length - 1];
+      if (lastChar === '&') {
+        newUrl = newUrl.slice(0, -1);
+      }
+      return newUrl;
+    };
+    return w.gl.utils.removeParamQueryString = function(url, param) {
+      var urlVariables, variables;
+      url = decodeURIComponent(url);
+      urlVariables = url.split('&');
+      return ((function() {
+        var j, len, results;
+        results = [];
+        for (j = 0, len = urlVariables.length; j < len; j++) {
+          variables = urlVariables[j];
+          if (variables.indexOf(param) === -1) {
+            results.push(variables);
+          }
+        }
+        return results;
+      })()).join('&');
+    };
+  })(window);
+
+}).call(this);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js.coffee b/app/assets/javascripts/lib/utils/url_utility.js.coffee
deleted file mode 100644
index e8085e1c2e45b4ee4921c5123db3f623db2c6ab6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/url_utility.js.coffee
+++ /dev/null
@@ -1,52 +0,0 @@
-((w) ->
-
-  w.gl ?= {}
-  w.gl.utils ?= {}
-
-  # Returns an array containing the value(s) of the
-  # of the key passed as an argument
-  w.gl.utils.getParameterValues = (sParam) ->
-    sPageURL = decodeURIComponent(window.location.search.substring(1))
-    sURLVariables = sPageURL.split('&')
-    sParameterName = undefined
-    values = []
-    i = 0
-    while i < sURLVariables.length
-      sParameterName = sURLVariables[i].split('=')
-      if sParameterName[0] is sParam
-        values.push(sParameterName[1])
-      i++
-    values
-
-  # #
-  #  @param {Object} params - url keys and value to merge
-  #  @param {String} url
-  # #
-  w.gl.utils.mergeUrlParams = (params, url) ->
-    newUrl = decodeURIComponent(url)
-    for paramName, paramValue of params
-      pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
-      if not paramValue?
-        newUrl = newUrl.replace pattern, ''
-      else if url.search(pattern) isnt -1
-        newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
-      else
-        newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
-
-    # Remove a trailing ampersand
-    lastChar = newUrl[newUrl.length - 1]
-
-    if lastChar is '&'
-        newUrl = newUrl.slice 0, -1
-
-    newUrl
-
-  # removes parameter query string from url. returns the modified url
-  w.gl.utils.removeParamQueryString = (url, param) ->
-    url = decodeURIComponent(url)
-    urlVariables = url.split('&')
-    (
-      variables for variables in urlVariables when variables.indexOf(param) is -1
-    ).join('&')
-
-) window
diff --git a/app/assets/javascripts/lib/utils/utf8_encode.js b/app/assets/javascripts/lib/utils/utf8_encode.js
deleted file mode 100644
index 39ffe44dae0ef78c6e22dbe5f0d60bb3265c94af..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/lib/utils/utf8_encode.js
+++ /dev/null
@@ -1,70 +0,0 @@
-function utf8_encode (argString) {
-  // http://kevin.vanzonneveld.net
-  // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
-  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
-  // +   improved by: sowberry
-  // +    tweaked by: Jack
-  // +   bugfixed by: Onno Marsman
-  // +   improved by: Yves Sucaet
-  // +   bugfixed by: Onno Marsman
-  // +   bugfixed by: Ulrich
-  // +   bugfixed by: Rafal Kukawski
-  // +   improved by: kirilloid
-  // +   bugfixed by: kirilloid
-  // *     example 1: utf8_encode('Kevin van Zonneveld');
-  // *     returns 1: 'Kevin van Zonneveld'
-
-  if (argString === null || typeof argString === "undefined") {
-    return "";
-  }
-
-  var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
-  var utftext = '',
-    start, end, stringl = 0;
-
-  start = end = 0;
-  stringl = string.length;
-  for (var n = 0; n < stringl; n++) {
-    var c1 = string.charCodeAt(n);
-    var enc = null;
-
-    if (c1 < 128) {
-      end++;
-    } else if (c1 > 127 && c1 < 2048) {
-      enc = String.fromCharCode(
-         (c1 >> 6)        | 192,
-        ( c1        & 63) | 128
-      );
-    } else if (c1 & 0xF800 != 0xD800) {
-      enc = String.fromCharCode(
-         (c1 >> 12)       | 224,
-        ((c1 >> 6)  & 63) | 128,
-        ( c1        & 63) | 128
-      );
-    } else { // surrogate pairs
-      if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); }
-      var c2 = string.charCodeAt(++n);
-      if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); }
-      c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
-      enc = String.fromCharCode(
-         (c1 >> 18)       | 240,
-        ((c1 >> 12) & 63) | 128,
-        ((c1 >> 6)  & 63) | 128,
-        ( c1        & 63) | 128
-      );
-    }
-    if (enc !== null) {
-      if (end > start) {
-        utftext += string.slice(start, end);
-      }
-      utftext += enc;
-      start = end = n + 1;
-    }
-  }
-
-  if (end > start) {
-    utftext += string.slice(start, stringl);
-  }
-
-  return utftext;
-}
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
new file mode 100644
index 0000000000000000000000000000000000000000..f145bd3ad74a4658b25a34a12409930d844ada8d
--- /dev/null
+++ b/app/assets/javascripts/line_highlighter.js
@@ -0,0 +1,115 @@
+
+/*= require jquery.scrollTo */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.LineHighlighter = (function() {
+    LineHighlighter.prototype.highlightClass = 'hll';
+
+    LineHighlighter.prototype._hash = '';
+
+    function LineHighlighter(hash) {
+      var range;
+      if (hash == null) {
+        hash = location.hash;
+      }
+      this.setHash = bind(this.setHash, this);
+      this.highlightLine = bind(this.highlightLine, this);
+      this.clickHandler = bind(this.clickHandler, this);
+      this._hash = hash;
+      this.bindEvents();
+      if (hash !== '') {
+        range = this.hashToRange(hash);
+        if (range[0]) {
+          this.highlightRange(range);
+          $.scrollTo("#L" + range[0], {
+            offset: -150
+          });
+        }
+      }
+    }
+
+    LineHighlighter.prototype.bindEvents = function() {
+      $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
+      return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
+        return event.preventDefault();
+      });
+    };
+
+    LineHighlighter.prototype.clickHandler = function(event) {
+      var current, lineNumber, range;
+      event.preventDefault();
+      this.clearHighlight();
+      lineNumber = $(event.target).closest('a').data('line-number');
+      current = this.hashToRange(this._hash);
+      if (!(current[0] && event.shiftKey)) {
+        this.setHash(lineNumber);
+        return this.highlightLine(lineNumber);
+      } else if (event.shiftKey) {
+        if (lineNumber < current[0]) {
+          range = [lineNumber, current[0]];
+        } else {
+          range = [current[0], lineNumber];
+        }
+        this.setHash(range[0], range[1]);
+        return this.highlightRange(range);
+      }
+    };
+
+    LineHighlighter.prototype.clearHighlight = function() {
+      return $("." + this.highlightClass).removeClass(this.highlightClass);
+    };
+
+    LineHighlighter.prototype.hashToRange = function(hash) {
+      var first, last, matches;
+      matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
+      if (matches && matches.length) {
+        first = parseInt(matches[1]);
+        last = matches[2] ? parseInt(matches[2]) : null;
+        return [first, last];
+      } else {
+        return [null, null];
+      }
+    };
+
+    LineHighlighter.prototype.highlightLine = function(lineNumber) {
+      return $("#LC" + lineNumber).addClass(this.highlightClass);
+    };
+
+    LineHighlighter.prototype.highlightRange = function(range) {
+      var i, lineNumber, ref, ref1, results;
+      if (range[1]) {
+        results = [];
+        for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? ++i : --i) {
+          results.push(this.highlightLine(lineNumber));
+        }
+        return results;
+      } else {
+        return this.highlightLine(range[0]);
+      }
+    };
+
+    LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
+      var hash;
+      if (lastLineNumber) {
+        hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+      } else {
+        hash = "#L" + firstLineNumber;
+      }
+      this._hash = hash;
+      return this.__setLocationHash__(hash);
+    };
+
+    LineHighlighter.prototype.__setLocationHash__ = function(value) {
+      return history.pushState({
+        turbolinks: false,
+        url: value
+      }, document.title, value);
+    };
+
+    return LineHighlighter;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
deleted file mode 100644
index 2254a3f91aeb35dc08e06d43aea64a129d8598a2..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/line_highlighter.js.coffee
+++ /dev/null
@@ -1,148 +0,0 @@
-# LineHighlighter
-#
-# Handles single- and multi-line selection and highlight for blob views.
-#
-#= require jquery.scrollTo
-#
-# ### Example Markup
-#
-#   <div id="blob-content-holder">
-#     <div class="file-content">
-#       <div class="line-numbers">
-#         <a href="#L1" id="L1" data-line-number="1">1</a>
-#         <a href="#L2" id="L2" data-line-number="2">2</a>
-#         <a href="#L3" id="L3" data-line-number="3">3</a>
-#         <a href="#L4" id="L4" data-line-number="4">4</a>
-#         <a href="#L5" id="L5" data-line-number="5">5</a>
-#       </div>
-#       <pre class="code highlight">
-#         <code>
-#           <span id="LC1" class="line">...</span>
-#           <span id="LC2" class="line">...</span>
-#           <span id="LC3" class="line">...</span>
-#           <span id="LC4" class="line">...</span>
-#           <span id="LC5" class="line">...</span>
-#         </code>
-#       </pre>
-#     </div>
-#   </div>
-#
-class @LineHighlighter
-  # CSS class applied to highlighted lines
-  highlightClass: 'hll'
-
-  # Internal copy of location.hash so we're not dependent on `location` in tests
-  _hash: ''
-
-  # Initialize a LineHighlighter object
-  #
-  # hash - String URL hash for dependency injection in tests
-  constructor: (hash = location.hash) ->
-    @_hash = hash
-
-    @bindEvents()
-
-    unless hash == ''
-      range = @hashToRange(hash)
-
-      if range[0]
-        @highlightRange(range)
-
-        # Scroll to the first highlighted line on initial load
-        # Offset -50 for the sticky top bar, and another -100 for some context
-        $.scrollTo("#L#{range[0]}", offset: -150)
-
-  bindEvents: ->
-    $('#blob-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
-
-    # While it may seem odd to bind to the mousedown event and then throw away
-    # the click event, there is a method to our madness.
-    #
-    # If not done this way, the line number anchor will sometimes keep its
-    # active state even when the event is cancelled, resulting in an ugly border
-    # around the link and/or a persisted underline text decoration.
-
-    $('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
-      event.preventDefault()
-
-  clickHandler: (event) =>
-    event.preventDefault()
-
-    @clearHighlight()
-
-    lineNumber = $(event.target).closest('a').data('line-number')
-    current = @hashToRange(@_hash)
-
-    unless current[0] && event.shiftKey
-      # If there's no current selection, or there is but Shift wasn't held,
-      # treat this like a single-line selection.
-      @setHash(lineNumber)
-      @highlightLine(lineNumber)
-    else if event.shiftKey
-      if lineNumber < current[0]
-        range = [lineNumber, current[0]]
-      else
-        range = [current[0], lineNumber]
-
-      @setHash(range[0], range[1])
-      @highlightRange(range)
-
-  # Unhighlight previously highlighted lines
-  clearHighlight: ->
-    $(".#{@highlightClass}").removeClass(@highlightClass)
-
-  # Convert a URL hash String into line numbers
-  #
-  # hash - Hash String
-  #
-  # Examples:
-  #
-  #   hashToRange('#L5')    # => [5, null]
-  #   hashToRange('#L5-15') # => [5, 15]
-  #   hashToRange('#foo')   # => [null, null]
-  #
-  # Returns an Array
-  hashToRange: (hash) ->
-    matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
-
-    if matches && matches.length
-      first = parseInt(matches[1])
-      last  = if matches[2] then parseInt(matches[2]) else null
-
-      [first, last]
-    else
-      [null, null]
-
-  # Highlight a single line
-  #
-  # lineNumber - Line number to highlight
-  highlightLine: (lineNumber) =>
-    $("#LC#{lineNumber}").addClass(@highlightClass)
-
-  # Highlight all lines within a range
-  #
-  # range - Array containing the starting and ending line numbers
-  highlightRange: (range) ->
-    if range[1]
-      for lineNumber in [range[0]..range[1]]
-        @highlightLine(lineNumber)
-    else
-      @highlightLine(range[0])
-
-  # Set the URL hash string
-  setHash: (firstLineNumber, lastLineNumber) =>
-    if lastLineNumber
-      hash = "#L#{firstLineNumber}-#{lastLineNumber}"
-    else
-      hash = "#L#{firstLineNumber}"
-
-    @_hash = hash
-    @__setLocationHash__(hash)
-
-  # Make the actual hash change in the browser
-  #
-  # This method is stubbed in tests.
-  __setLocationHash__: (value) ->
-    # We're using pushState instead of assigning location.hash directly to
-    # prevent the page from scrolling on the hashchange event
-    history.pushState({turbolinks: false, url: value}, document.title, value)
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
new file mode 100644
index 0000000000000000000000000000000000000000..218f24fe908cdcb3debbdc1f1487f173d9980c1c
--- /dev/null
+++ b/app/assets/javascripts/logo.js
@@ -0,0 +1,54 @@
+(function() {
+  var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
+
+  Turbolinks.enableProgressBar();
+
+  defaultClass = 'tanuki-shape';
+
+  pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
+
+  pieceIndex = 0;
+
+  firstPiece = pieces[0];
+
+  currentTimer = null;
+
+  delay = 150;
+
+  clearHighlights = function() {
+    return $("." + defaultClass + ".highlight").attr('class', defaultClass);
+  };
+
+  start = function() {
+    clearHighlights();
+    pieceIndex = 0;
+    if (pieces[0] !== firstPiece) {
+      pieces.reverse();
+    }
+    if (currentTimer) {
+      clearInterval(currentTimer);
+    }
+    return currentTimer = setInterval(work, delay);
+  };
+
+  stop = function() {
+    clearInterval(currentTimer);
+    return clearHighlights();
+  };
+
+  work = function() {
+    clearHighlights();
+    $(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
+    if (pieceIndex === pieces.length - 1) {
+      pieceIndex = 0;
+      return pieces.reverse();
+    } else {
+      return pieceIndex++;
+    }
+  };
+
+  $(document).on('page:fetch', start);
+
+  $(document).on('page:change', stop);
+
+}).call(this);
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
deleted file mode 100644
index dc2590a03557b23f2bb79da64f1530d0713a82bd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/logo.js.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-Turbolinks.enableProgressBar();
-
-defaultClass = 'tanuki-shape'
-pieces = [
-  'path#tanuki-right-cheek',
-  'path#tanuki-right-eye, path#tanuki-right-ear',
-  'path#tanuki-nose',
-  'path#tanuki-left-eye, path#tanuki-left-ear',
-  'path#tanuki-left-cheek',
-]
-pieceIndex = 0
-firstPiece = pieces[0]
-
-currentTimer = null
-delay        = 150
-
-clearHighlights = ->
-  $(".#{defaultClass}.highlight").attr('class', defaultClass)
-
-start = ->
-  clearHighlights()
-  pieceIndex = 0
-  pieces.reverse() unless pieces[0] == firstPiece
-  clearInterval(currentTimer) if currentTimer
-  currentTimer = setInterval(work, delay)
-
-stop = ->
-  clearInterval(currentTimer)
-  clearHighlights()
-
-work = ->
-  clearHighlights()
-  $(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight")
-
-  # If we hit the last piece, reset the index and then reverse the array to
-  # get a nice back-and-forth sweeping look
-  if pieceIndex == pieces.length - 1
-    pieceIndex = 0
-    pieces.reverse()
-  else
-    pieceIndex++
-
-$(document).on('page:fetch',  start)
-$(document).on('page:change', stop)
diff --git a/app/assets/javascripts/markdown_preview.js b/app/assets/javascripts/markdown_preview.js
new file mode 100644
index 0000000000000000000000000000000000000000..18fc7bae09a6e5f65a3fbbce792b11097bde90fa
--- /dev/null
+++ b/app/assets/javascripts/markdown_preview.js
@@ -0,0 +1,150 @@
+(function() {
+  var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
+
+  this.MarkdownPreview = (function() {
+    function MarkdownPreview() {}
+
+    MarkdownPreview.prototype.referenceThreshold = 10;
+
+    MarkdownPreview.prototype.ajaxCache = {};
+
+    MarkdownPreview.prototype.showPreview = function(form) {
+      var mdText, preview;
+      preview = form.find('.js-md-preview');
+      mdText = form.find('textarea.markdown-area').val();
+      if (mdText.trim().length === 0) {
+        preview.text('Nothing to preview.');
+        return this.hideReferencedUsers(form);
+      } else {
+        preview.text('Loading...');
+        return this.renderMarkdown(mdText, (function(_this) {
+          return function(response) {
+            preview.html(response.body);
+            preview.syntaxHighlight();
+            return _this.renderReferencedUsers(response.references.users, form);
+          };
+        })(this));
+      }
+    };
+
+    MarkdownPreview.prototype.renderMarkdown = function(text, success) {
+      if (!window.markdown_preview_path) {
+        return;
+      }
+      if (text === this.ajaxCache.text) {
+        return success(this.ajaxCache.response);
+      }
+      return $.ajax({
+        type: 'POST',
+        url: window.markdown_preview_path,
+        data: {
+          text: text
+        },
+        dataType: 'json',
+        success: (function(_this) {
+          return function(response) {
+            _this.ajaxCache = {
+              text: text,
+              response: response
+            };
+            return success(response);
+          };
+        })(this)
+      });
+    };
+
+    MarkdownPreview.prototype.hideReferencedUsers = function(form) {
+      var referencedUsers;
+      referencedUsers = form.find('.referenced-users');
+      return referencedUsers.hide();
+    };
+
+    MarkdownPreview.prototype.renderReferencedUsers = function(users, form) {
+      var referencedUsers;
+      referencedUsers = form.find('.referenced-users');
+      if (referencedUsers.length) {
+        if (users.length >= this.referenceThreshold) {
+          referencedUsers.show();
+          return referencedUsers.find('.js-referenced-users-count').text(users.length);
+        } else {
+          return referencedUsers.hide();
+        }
+      }
+    };
+
+    return MarkdownPreview;
+
+  })();
+
+  markdownPreview = new MarkdownPreview();
+
+  previewButtonSelector = '.js-md-preview-button';
+
+  writeButtonSelector = '.js-md-write-button';
+
+  lastTextareaPreviewed = null;
+
+  $.fn.setupMarkdownPreview = function() {
+    var $form, form_textarea;
+    $form = $(this);
+    form_textarea = $form.find('textarea.markdown-area');
+    form_textarea.on('input', function() {
+      return markdownPreview.hideReferencedUsers($form);
+    });
+    return form_textarea.on('blur', function() {
+      return markdownPreview.showPreview($form);
+    });
+  };
+
+  $(document).on('markdown-preview:show', function(e, $form) {
+    if (!$form) {
+      return;
+    }
+    lastTextareaPreviewed = $form.find('textarea.markdown-area');
+    $form.find(writeButtonSelector).parent().removeClass('active');
+    $form.find(previewButtonSelector).parent().addClass('active');
+    $form.find('.md-write-holder').hide();
+    $form.find('.md-preview-holder').show();
+    return markdownPreview.showPreview($form);
+  });
+
+  $(document).on('markdown-preview:hide', function(e, $form) {
+    if (!$form) {
+      return;
+    }
+    lastTextareaPreviewed = null;
+    $form.find(writeButtonSelector).parent().addClass('active');
+    $form.find(previewButtonSelector).parent().removeClass('active');
+    $form.find('.md-write-holder').show();
+    $form.find('textarea.markdown-area').focus();
+    return $form.find('.md-preview-holder').hide();
+  });
+
+  $(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
+    var $target;
+    $target = $(keyboardEvent.target);
+    if ($target.is('textarea.markdown-area')) {
+      $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]);
+      return keyboardEvent.preventDefault();
+    } else if (lastTextareaPreviewed) {
+      $target = lastTextareaPreviewed;
+      $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]);
+      return keyboardEvent.preventDefault();
+    }
+  });
+
+  $(document).on('click', previewButtonSelector, function(e) {
+    var $form;
+    e.preventDefault();
+    $form = $(this).closest('form');
+    return $(document).triggerHandler('markdown-preview:show', [$form]);
+  });
+
+  $(document).on('click', writeButtonSelector, function(e) {
+    var $form;
+    e.preventDefault();
+    $form = $(this).closest('form');
+    return $(document).triggerHandler('markdown-preview:hide', [$form]);
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee
deleted file mode 100644
index 2a0b94794450e75a80d454fc4e37e35ab477bd08..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/markdown_preview.js.coffee
+++ /dev/null
@@ -1,119 +0,0 @@
-# MarkdownPreview
-#
-# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
-# and showing a warning when more than `x` users are referenced.
-#
-class @MarkdownPreview
-  # Minimum number of users referenced before triggering a warning
-  referenceThreshold: 10
-  ajaxCache: {}
-
-  showPreview: (form) ->
-    preview = form.find('.js-md-preview')
-    mdText  = form.find('textarea.markdown-area').val()
-
-    if mdText.trim().length == 0
-      preview.text('Nothing to preview.')
-      @hideReferencedUsers(form)
-    else
-      preview.text('Loading...')
-      @renderMarkdown mdText, (response) =>
-        preview.html(response.body)
-        preview.syntaxHighlight()
-        @renderReferencedUsers(response.references.users, form)
-
-  renderMarkdown: (text, success) ->
-    return unless window.markdown_preview_path
-
-    return success(@ajaxCache.response) if text == @ajaxCache.text
-
-    $.ajax
-      type: 'POST'
-      url: window.markdown_preview_path
-      data: { text: text }
-      dataType: 'json'
-      success: (response) =>
-        @ajaxCache = text: text, response: response
-        success(response)
-
-  hideReferencedUsers: (form) ->
-    referencedUsers = form.find('.referenced-users')
-    referencedUsers.hide()
-
-  renderReferencedUsers: (users, form) ->
-    referencedUsers = form.find('.referenced-users')
-
-    if referencedUsers.length
-      if users.length >= @referenceThreshold
-        referencedUsers.show()
-        referencedUsers.find('.js-referenced-users-count').text(users.length)
-      else
-        referencedUsers.hide()
-
-markdownPreview = new MarkdownPreview()
-
-previewButtonSelector = '.js-md-preview-button'
-writeButtonSelector   = '.js-md-write-button'
-lastTextareaPreviewed = null
-
-$.fn.setupMarkdownPreview = ->
-  $form = $(this)
-
-  form_textarea = $form.find('textarea.markdown-area')
-
-  form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
-  form_textarea.on 'blur',  -> markdownPreview.showPreview($form)
-
-$(document).on 'markdown-preview:show', (e, $form) ->
-  return unless $form
-
-  lastTextareaPreviewed = $form.find('textarea.markdown-area')
-
-  # toggle tabs
-  $form.find(writeButtonSelector).parent().removeClass('active')
-  $form.find(previewButtonSelector).parent().addClass('active')
-
-  # toggle content
-  $form.find('.md-write-holder').hide()
-  $form.find('.md-preview-holder').show()
-
-  markdownPreview.showPreview($form)
-
-$(document).on 'markdown-preview:hide', (e, $form) ->
-  return unless $form
-
-  lastTextareaPreviewed = null
-
-  # toggle tabs
-  $form.find(writeButtonSelector).parent().addClass('active')
-  $form.find(previewButtonSelector).parent().removeClass('active')
-
-  # toggle content
-  $form.find('.md-write-holder').show()
-  $form.find('textarea.markdown-area').focus()
-  $form.find('.md-preview-holder').hide()
-
-$(document).on 'markdown-preview:toggle', (e, keyboardEvent) ->
-  $target = $(keyboardEvent.target)
-
-  if $target.is('textarea.markdown-area')
-    $(document).triggerHandler('markdown-preview:show', [$target.closest('form')])
-    keyboardEvent.preventDefault()
-  else if lastTextareaPreviewed
-    $target = lastTextareaPreviewed
-    $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')])
-    keyboardEvent.preventDefault()
-
-$(document).on 'click', previewButtonSelector, (e) ->
-  e.preventDefault()
-
-  $form = $(this).closest('form')
-
-  $(document).triggerHandler('markdown-preview:show', [$form])
-
-$(document).on 'click', writeButtonSelector, (e) ->
-  e.preventDefault()
-
-  $form = $(this).closest('form')
-
-  $(document).triggerHandler('markdown-preview:hide', [$form])
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
new file mode 100644
index 0000000000000000000000000000000000000000..47e6dd1084d2a6a6527d49e0dedc0f46ee58c9c8
--- /dev/null
+++ b/app/assets/javascripts/merge_request.js
@@ -0,0 +1,105 @@
+
+/*= require jquery.waitforimages */
+
+
+/*= require task_list */
+
+
+/*= require merge_request_tabs */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.MergeRequest = (function() {
+    function MergeRequest(opts) {
+      this.opts = opts != null ? opts : {};
+      this.submitNoteForm = bind(this.submitNoteForm, this);
+      this.$el = $('.merge-request');
+      this.$('.show-all-commits').on('click', (function(_this) {
+        return function() {
+          return _this.showAllCommits();
+        };
+      })(this));
+      this.initTabs();
+      this.disableTaskList();
+      this.initMRBtnListeners();
+      if ($("a.btn-close").length) {
+        this.initTaskList();
+      }
+    }
+
+    MergeRequest.prototype.$ = function(selector) {
+      return this.$el.find(selector);
+    };
+
+    MergeRequest.prototype.initTabs = function() {
+      if (this.opts.action !== 'new') {
+        return new MergeRequestTabs(this.opts);
+      } else {
+        return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
+      }
+    };
+
+    MergeRequest.prototype.showAllCommits = function() {
+      this.$('.first-commits').remove();
+      return this.$('.all-commits').removeClass('hide');
+    };
+
+    MergeRequest.prototype.initTaskList = function() {
+      $('.detail-page-description .js-task-list-container').taskList('enable');
+      return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
+    };
+
+    MergeRequest.prototype.initMRBtnListeners = function() {
+      var _this;
+      _this = this;
+      return $('a.btn-close, a.btn-reopen').on('click', function(e) {
+        var $this, shouldSubmit;
+        $this = $(this);
+        shouldSubmit = $this.hasClass('btn-comment');
+        if (shouldSubmit && $this.data('submitted')) {
+          return;
+        }
+        if (shouldSubmit) {
+          if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
+            e.preventDefault();
+            e.stopImmediatePropagation();
+            return _this.submitNoteForm($this.closest('form'), $this);
+          }
+        }
+      });
+    };
+
+    MergeRequest.prototype.submitNoteForm = function(form, $button) {
+      var noteText;
+      noteText = form.find("textarea.js-note-text").val();
+      if (noteText.trim().length > 0) {
+        form.submit();
+        $button.data('submitted', true);
+        return $button.trigger('click');
+      }
+    };
+
+    MergeRequest.prototype.disableTaskList = function() {
+      $('.detail-page-description .js-task-list-container').taskList('disable');
+      return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
+    };
+
+    MergeRequest.prototype.updateTaskList = function() {
+      var patchData;
+      patchData = {};
+      patchData['merge_request'] = {
+        'description': $('.js-task-list-field', this).val()
+      };
+      return $.ajax({
+        type: 'PATCH',
+        url: $('form.js-issuable-update').attr('action'),
+        data: patchData
+      });
+    };
+
+    return MergeRequest;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
deleted file mode 100644
index dabfd91cf14206319159df734948c7e49d385276..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merge_request.js.coffee
+++ /dev/null
@@ -1,82 +0,0 @@
-#= require jquery.waitforimages
-#= require task_list
-
-#= require merge_request_tabs
-
-class @MergeRequest
-  # Initialize MergeRequest behavior
-  #
-  # Options:
-  #   action - String, current controller action
-  #
-  constructor: (@opts = {}) ->
-    this.$el = $('.merge-request')
-
-    this.$('.show-all-commits').on 'click', =>
-      this.showAllCommits()
-
-    @initTabs()
-
-    # Prevent duplicate event bindings
-    @disableTaskList()
-    @initMRBtnListeners()
-
-    if $("a.btn-close").length
-      @initTaskList()
-
-  # Local jQuery finder
-  $: (selector) ->
-    this.$el.find(selector)
-
-  initTabs: ->
-    if @opts.action != 'new'
-      # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
-      new MergeRequestTabs(@opts)
-    else
-      # Show the first tab (Commits)
-      $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
-
-  showAllCommits: ->
-    this.$('.first-commits').remove()
-    this.$('.all-commits').removeClass 'hide'
-
-  initTaskList: ->
-    $('.detail-page-description .js-task-list-container').taskList('enable')
-    $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
-
-  initMRBtnListeners: ->
-    _this = @
-    $('a.btn-close, a.btn-reopen').on 'click', (e) ->
-      $this = $(this)
-      shouldSubmit = $this.hasClass('btn-comment')
-      if shouldSubmit && $this.data('submitted')
-        return
-      if shouldSubmit
-        if $this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')
-          e.preventDefault()
-          e.stopImmediatePropagation()
-          _this.submitNoteForm($this.closest('form'),$this)
-
-
-  submitNoteForm: (form, $button) =>
-    noteText = form.find("textarea.js-note-text").val()
-    if noteText.trim().length > 0
-      form.submit()
-      $button.data('submitted',true)
-      $button.trigger('click')
-
-
-  disableTaskList: ->
-    $('.detail-page-description .js-task-list-container').taskList('disable')
-    $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
-
-  # TODO (rspeicher): Make the merge request description inline-editable like a
-  # note so that we can re-use its form here
-  updateTaskList: ->
-    patchData = {}
-    patchData['merge_request'] = {'description': $('.js-task-list-field', this).val()}
-
-    $.ajax
-      type: 'PATCH'
-      url: $('form.js-issuable-update').attr('action')
-      data: patchData
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
new file mode 100644
index 0000000000000000000000000000000000000000..52c2ed6101259ca038913ee0cb46f02a8e1218a2
--- /dev/null
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -0,0 +1,239 @@
+
+/*= require jquery.cookie */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.MergeRequestTabs = (function() {
+    MergeRequestTabs.prototype.diffsLoaded = false;
+
+    MergeRequestTabs.prototype.buildsLoaded = false;
+
+    MergeRequestTabs.prototype.commitsLoaded = false;
+
+    function MergeRequestTabs(opts) {
+      this.opts = opts != null ? opts : {};
+      this.setCurrentAction = bind(this.setCurrentAction, this);
+      this.tabShown = bind(this.tabShown, this);
+      this.showTab = bind(this.showTab, this);
+      this._location = location;
+      this.bindEvents();
+      this.activateTab(this.opts.action);
+    }
+
+    MergeRequestTabs.prototype.bindEvents = function() {
+      $(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
+      return $(document).on('click', '.js-show-tab', this.showTab);
+    };
+
+    MergeRequestTabs.prototype.showTab = function(event) {
+      event.preventDefault();
+      return this.activateTab($(event.target).data('action'));
+    };
+
+    MergeRequestTabs.prototype.tabShown = function(event) {
+      var $target, action, navBarHeight;
+      $target = $(event.target);
+      action = $target.data('action');
+      if (action === 'commits') {
+        this.loadCommits($target.attr('href'));
+        this.expandView();
+      } else if (action === 'diffs') {
+        this.loadDiff($target.attr('href'));
+        if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
+          this.shrinkView();
+        }
+        navBarHeight = $('.navbar-gitlab').outerHeight();
+        $.scrollTo(".merge-request-details .merge-request-tabs", {
+          offset: -navBarHeight
+        });
+      } else if (action === 'builds') {
+        this.loadBuilds($target.attr('href'));
+        this.expandView();
+      } else {
+        this.expandView();
+      }
+      return this.setCurrentAction(action);
+    };
+
+    MergeRequestTabs.prototype.scrollToElement = function(container) {
+      var $el, navBarHeight;
+      if (window.location.hash) {
+        navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
+        $el = $(container + " " + window.location.hash + ":not(.match)");
+        if ($el.length) {
+          return $.scrollTo(container + " " + window.location.hash + ":not(.match)", {
+            offset: -navBarHeight
+          });
+        }
+      }
+    };
+
+    MergeRequestTabs.prototype.activateTab = function(action) {
+      if (action === 'show') {
+        action = 'notes';
+      }
+      return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
+    };
+
+    MergeRequestTabs.prototype.setCurrentAction = function(action) {
+      var new_state;
+      if (action === 'show') {
+        action = 'notes';
+      }
+      new_state = this._location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '');
+      if (action !== 'notes') {
+        new_state += "/" + action;
+      }
+      new_state += this._location.search + this._location.hash;
+      history.replaceState({
+        turbolinks: true,
+        url: new_state
+      }, document.title, new_state);
+      return new_state;
+    };
+
+    MergeRequestTabs.prototype.loadCommits = function(source) {
+      if (this.commitsLoaded) {
+        return;
+      }
+      return this._get({
+        url: source + ".json",
+        success: (function(_this) {
+          return function(data) {
+            document.querySelector("div#commits").innerHTML = data.html;
+            gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
+            _this.commitsLoaded = true;
+            return _this.scrollToElement("#commits");
+          };
+        })(this)
+      });
+    };
+
+    MergeRequestTabs.prototype.loadDiff = function(source) {
+      if (this.diffsLoaded) {
+        return;
+      }
+      return this._get({
+        url: (source + ".json") + this._location.search,
+        success: (function(_this) {
+          return function(data) {
+            $('#diffs').html(data.html);
+            gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
+            $('#diffs .js-syntax-highlight').syntaxHighlight();
+            $('#diffs .diff-file').singleFileDiff();
+            if (_this.diffViewType() === 'parallel') {
+              _this.expandViewContainer();
+            }
+            _this.diffsLoaded = true;
+            _this.scrollToElement("#diffs");
+            _this.highlighSelectedLine();
+            _this.filesCommentButton = $('.files .diff-file').filesCommentButton();
+            return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) {
+              e.preventDefault();
+              window.location.hash = $(e.currentTarget).attr('href');
+              _this.highlighSelectedLine();
+              return _this.scrollToElement("#diffs");
+            });
+          };
+        })(this)
+      });
+    };
+
+    MergeRequestTabs.prototype.highlighSelectedLine = function() {
+      var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight;
+      $('.hll').removeClass('hll');
+      locationHash = window.location.hash;
+      if (locationHash !== '') {
+        hashClassString = "." + (locationHash.replace('#', ''));
+        $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
+        if (!$diffLine.is('tr')) {
+          $diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString);
+        } else {
+          $diffLine = $diffLine.find('td');
+        }
+        if ($diffLine.length) {
+          $diffLine.addClass('hll');
+          diffLineTop = $diffLine.offset().top;
+          return navBarHeight = $('.navbar-gitlab').outerHeight();
+        }
+      }
+    };
+
+    MergeRequestTabs.prototype.loadBuilds = function(source) {
+      if (this.buildsLoaded) {
+        return;
+      }
+      return this._get({
+        url: source + ".json",
+        success: (function(_this) {
+          return function(data) {
+            document.querySelector("div#builds").innerHTML = data.html;
+            gl.utils.localTimeAgo($('.js-timeago', 'div#builds'));
+            _this.buildsLoaded = true;
+            return _this.scrollToElement("#builds");
+          };
+        })(this)
+      });
+    };
+
+    MergeRequestTabs.prototype.toggleLoading = function(status) {
+      return $('.mr-loading-status .loading').toggle(status);
+    };
+
+    MergeRequestTabs.prototype._get = function(options) {
+      var defaults;
+      defaults = {
+        beforeSend: (function(_this) {
+          return function() {
+            return _this.toggleLoading(true);
+          };
+        })(this),
+        complete: (function(_this) {
+          return function() {
+            return _this.toggleLoading(false);
+          };
+        })(this),
+        dataType: 'json',
+        type: 'GET'
+      };
+      options = $.extend({}, defaults, options);
+      return $.ajax(options);
+    };
+
+    MergeRequestTabs.prototype.diffViewType = function() {
+      return $('.inline-parallel-buttons a.active').data('view-type');
+    };
+
+    MergeRequestTabs.prototype.expandViewContainer = function() {
+      return $('.container-fluid').removeClass('container-limited');
+    };
+
+    MergeRequestTabs.prototype.shrinkView = function() {
+      var $gutterIcon;
+      $gutterIcon = $('.js-sidebar-toggle i:visible');
+      return setTimeout(function() {
+        if ($gutterIcon.is('.fa-angle-double-right')) {
+          return $gutterIcon.closest('a').trigger('click', [true]);
+        }
+      }, 0);
+    };
+
+    MergeRequestTabs.prototype.expandView = function() {
+      var $gutterIcon;
+      if ($.cookie('collapsed_gutter') === 'true') {
+        return;
+      }
+      $gutterIcon = $('.js-sidebar-toggle i:visible');
+      return setTimeout(function() {
+        if ($gutterIcon.is('.fa-angle-double-left')) {
+          return $gutterIcon.closest('a').trigger('click', [true]);
+        }
+      }, 0);
+    };
+
+    return MergeRequestTabs;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
deleted file mode 100644
index 86539e0d725a81605b1143a1fba9f8acf04c3b63..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ /dev/null
@@ -1,252 +0,0 @@
-# MergeRequestTabs
-#
-# Handles persisting and restoring the current tab selection and lazily-loading
-# content on the MergeRequests#show page.
-#
-#= require jquery.cookie
-#
-# ### Example Markup
-#
-#   <ul class="nav-links merge-request-tabs">
-#     <li class="notes-tab active">
-#       <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
-#         Discussion
-#       </a>
-#     </li>
-#     <li class="commits-tab">
-#       <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
-#         Commits
-#       </a>
-#     </li>
-#     <li class="diffs-tab">
-#       <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
-#         Diffs
-#       </a>
-#     </li>
-#   </ul>
-#
-#   <div class="tab-content">
-#     <div class="notes tab-pane active" id="notes">
-#       Notes Content
-#     </div>
-#     <div class="commits tab-pane" id="commits">
-#       Commits Content
-#     </div>
-#     <div class="diffs tab-pane" id="diffs">
-#       Diffs Content
-#     </div>
-#   </div>
-#
-#   <div class="mr-loading-status">
-#     <div class="loading">
-#       Loading Animation
-#     </div>
-#   </div>
-#
-class @MergeRequestTabs
-  diffsLoaded: false
-  buildsLoaded: false
-  commitsLoaded: false
-
-  constructor: (@opts = {}) ->
-    # Store the `location` object, allowing for easier stubbing in tests
-    @_location = location
-
-    @bindEvents()
-    @activateTab(@opts.action)
-
-  bindEvents: ->
-    $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
-    $(document).on 'click', '.js-show-tab', @showTab
-
-  showTab: (event) =>
-    event.preventDefault()
-
-    @activateTab $(event.target).data('action')
-
-  tabShown: (event) =>
-    $target = $(event.target)
-    action = $target.data('action')
-
-    if action == 'commits'
-      @loadCommits($target.attr('href'))
-      @expandView()
-    else if action == 'diffs'
-      @loadDiff($target.attr('href'))
-      if bp? and bp.getBreakpointSize() isnt 'lg'
-        @shrinkView()
-
-      navBarHeight = $('.navbar-gitlab').outerHeight()
-      $.scrollTo(".merge-request-details .merge-request-tabs", offset: -navBarHeight)
-    else if action == 'builds'
-      @loadBuilds($target.attr('href'))
-      @expandView()
-    else
-      @expandView()
-
-    @setCurrentAction(action)
-
-  scrollToElement: (container) ->
-    if window.location.hash
-      navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
-
-      $el = $("#{container} #{window.location.hash}:not(.match)")
-      $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
-
-  # Activate a tab based on the current action
-  activateTab: (action) ->
-    action = 'notes' if action == 'show'
-    $(".merge-request-tabs a[data-action='#{action}']").tab('show')
-
-  # Replaces the current Merge Request-specific action in the URL with a new one
-  #
-  # If the action is "notes", the URL is reset to the standard
-  # `MergeRequests#show` route.
-  #
-  # Examples:
-  #
-  #   location.pathname # => "/namespace/project/merge_requests/1"
-  #   setCurrentAction('diffs')
-  #   location.pathname # => "/namespace/project/merge_requests/1/diffs"
-  #
-  #   location.pathname # => "/namespace/project/merge_requests/1/diffs"
-  #   setCurrentAction('notes')
-  #   location.pathname # => "/namespace/project/merge_requests/1"
-  #
-  #   location.pathname # => "/namespace/project/merge_requests/1/diffs"
-  #   setCurrentAction('commits')
-  #   location.pathname # => "/namespace/project/merge_requests/1/commits"
-  #
-  # Returns the new URL String
-  setCurrentAction: (action) =>
-    # Normalize action, just to be safe
-    action = 'notes' if action == 'show'
-
-    # Remove a trailing '/commits' or '/diffs'
-    new_state = @_location.pathname.replace(/\/(commits|diffs|builds)(\.html)?\/?$/, '')
-
-    # Append the new action if we're on a tab other than 'notes'
-    unless action == 'notes'
-      new_state += "/#{action}"
-
-    # Ensure parameters and hash come along for the ride
-    new_state += @_location.search + @_location.hash
-
-    # Replace the current history state with the new one without breaking
-    # Turbolinks' history.
-    #
-    # See https://github.com/rails/turbolinks/issues/363
-    history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
-
-    new_state
-
-  loadCommits: (source) ->
-    return if @commitsLoaded
-
-    @_get
-      url: "#{source}.json"
-      success: (data) =>
-        document.querySelector("div#commits").innerHTML = data.html
-        gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
-        @commitsLoaded = true
-        @scrollToElement("#commits")
-
-  loadDiff: (source) ->
-    return if @diffsLoaded
-    @_get
-      url: "#{source}.json" + @_location.search
-      success: (data) =>
-        $('#diffs').html data.html
-        gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
-        $('#diffs .js-syntax-highlight').syntaxHighlight()
-        $('#diffs .diff-file').singleFileDiff()
-        @expandViewContainer() if @diffViewType() is 'parallel'
-        @diffsLoaded = true
-        @scrollToElement("#diffs")
-        @highlighSelectedLine()
-        @filesCommentButton = $('.files .diff-file').filesCommentButton()
-
-        $(document)
-          .off 'click', '.diff-line-num a'
-          .on 'click', '.diff-line-num a', (e) =>
-            e.preventDefault()
-            window.location.hash = $(e.currentTarget).attr 'href'
-            @highlighSelectedLine()
-            @scrollToElement("#diffs")
-
-  highlighSelectedLine: ->
-    $('.hll').removeClass 'hll'
-    locationHash = window.location.hash
-
-    if locationHash isnt ''
-      hashClassString = ".#{locationHash.replace('#', '')}"
-      $diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
-
-      if not $diffLine.is 'tr'
-        $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
-      else
-        $diffLine = $diffLine.find('td')
-
-      if $diffLine.length
-        $diffLine.addClass 'hll'
-        diffLineTop = $diffLine.offset().top
-        navBarHeight = $('.navbar-gitlab').outerHeight()
-
-  loadBuilds: (source) ->
-    return if @buildsLoaded
-
-    @_get
-      url: "#{source}.json"
-      success: (data) =>
-        document.querySelector("div#builds").innerHTML = data.html
-        gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
-        @buildsLoaded = true
-        @scrollToElement("#builds")
-
-  # Show or hide the loading spinner
-  #
-  # status - Boolean, true to show, false to hide
-  toggleLoading: (status) ->
-    $('.mr-loading-status .loading').toggle(status)
-
-  _get: (options) ->
-    defaults = {
-      beforeSend: => @toggleLoading(true)
-      complete:   => @toggleLoading(false)
-      dataType: 'json'
-      type: 'GET'
-    }
-
-    options = $.extend({}, defaults, options)
-
-    $.ajax(options)
-
-  # Returns diff view type
-  diffViewType: ->
-    $('.inline-parallel-buttons a.active').data('view-type')
-
-  expandViewContainer: ->
-    $('.container-fluid').removeClass('container-limited')
-
-  shrinkView: ->
-    $gutterIcon = $('.js-sidebar-toggle i:visible')
-
-    # Wait until listeners are set
-    setTimeout( ->
-      # Only when sidebar is expanded
-      if $gutterIcon.is('.fa-angle-double-right')
-        $gutterIcon.closest('a').trigger('click', [true])
-    , 0)
-
-  # Expand the issuable sidebar unless the user explicitly collapsed it
-  expandView: ->
-    return if $.cookie('collapsed_gutter') == 'true'
-
-    $gutterIcon = $('.js-sidebar-toggle i:visible')
-
-    # Wait until listeners are set
-    setTimeout( ->
-      # Only when sidebar is collapsed
-      if $gutterIcon.is('.fa-angle-double-left')
-        $gutterIcon.closest('a').trigger('click', [true])
-    , 0)
diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..362aaa906d033ce110e6c838cc7cbc14fd9d0dfd
--- /dev/null
+++ b/app/assets/javascripts/merge_request_widget.js
@@ -0,0 +1,185 @@
+(function() {
+  var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+  this.MergeRequestWidget = (function() {
+    function MergeRequestWidget(opts) {
+      this.opts = opts;
+      $('#modal_merge_info').modal({
+        show: false
+      });
+      this.firstCICheck = true;
+      this.readyForCICheck = false;
+      this.cancel = false;
+      clearInterval(this.fetchBuildStatusInterval);
+      this.clearEventListeners();
+      this.addEventListeners();
+      this.getCIStatus(false);
+      this.pollCIStatus();
+      notifyPermissions();
+    }
+
+    MergeRequestWidget.prototype.clearEventListeners = function() {
+      return $(document).off('page:change.merge_request');
+    };
+
+    MergeRequestWidget.prototype.cancelPolling = function() {
+      return this.cancel = true;
+    };
+
+    MergeRequestWidget.prototype.addEventListeners = function() {
+      var allowedPages;
+      allowedPages = ['show', 'commits', 'builds', 'changes'];
+      return $(document).on('page:change.merge_request', (function(_this) {
+        return function() {
+          var page;
+          page = $('body').data('page').split(':').last();
+          if (allowedPages.indexOf(page) < 0) {
+            clearInterval(_this.fetchBuildStatusInterval);
+            _this.cancelPolling();
+            return _this.clearEventListeners();
+          }
+        };
+      })(this));
+    };
+
+    MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
+      if (deleteSourceBranch == null) {
+        deleteSourceBranch = false;
+      }
+      return $.ajax({
+        type: 'GET',
+        url: $('.merge-request').data('url'),
+        success: (function(_this) {
+          return function(data) {
+            var callback, urlSuffix;
+            if (data.state === "merged") {
+              urlSuffix = deleteSourceBranch ? '?delete_source=true' : '';
+              return window.location.href = window.location.pathname + urlSuffix;
+            } else if (data.merge_error) {
+              return $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>");
+            } else {
+              callback = function() {
+                return merge_request_widget.mergeInProgress(deleteSourceBranch);
+              };
+              return setTimeout(callback, 2000);
+            }
+          };
+        })(this),
+        dataType: 'json'
+      });
+    };
+
+    MergeRequestWidget.prototype.getMergeStatus = function() {
+      return $.get(this.opts.merge_check_url, function(data) {
+        return $('.mr-state-widget').replaceWith(data);
+      });
+    };
+
+    MergeRequestWidget.prototype.ciLabelForStatus = function(status) {
+      switch (status) {
+        case 'success':
+          return 'passed';
+        case 'success_with_warnings':
+          return 'passed with warnings';
+        default:
+          return status;
+      }
+    };
+
+    MergeRequestWidget.prototype.pollCIStatus = function() {
+      return this.fetchBuildStatusInterval = setInterval(((function(_this) {
+        return function() {
+          if (!_this.readyForCICheck) {
+            return;
+          }
+          _this.getCIStatus(true);
+          return _this.readyForCICheck = false;
+        };
+      })(this)), 10000);
+    };
+
+    MergeRequestWidget.prototype.getCIStatus = function(showNotification) {
+      var _this;
+      _this = this;
+      $('.ci-widget-fetching').show();
+      return $.getJSON(this.opts.ci_status_url, (function(_this) {
+        return function(data) {
+          var message, status, title;
+          if (_this.cancel) {
+            return;
+          }
+          _this.readyForCICheck = true;
+          if (data.status === '') {
+            return;
+          }
+          if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) {
+            _this.opts.ci_status = data.status;
+            _this.showCIStatus(data.status);
+            if (data.coverage) {
+              _this.showCICoverage(data.coverage);
+            }
+            if (showNotification && !_this.firstCICheck) {
+              status = _this.ciLabelForStatus(data.status);
+              if (status === "preparing") {
+                title = _this.opts.ci_title.preparing;
+                status = status.charAt(0).toUpperCase() + status.slice(1);
+                message = _this.opts.ci_message.preparing.replace('{{status}}', status);
+              } else {
+                title = _this.opts.ci_title.normal;
+                message = _this.opts.ci_message.normal.replace('{{status}}', status);
+              }
+              title = title.replace('{{status}}', status);
+              message = message.replace('{{sha}}', data.sha);
+              message = message.replace('{{title}}', data.title);
+              notify(title, message, _this.opts.gitlab_icon, function() {
+                this.close();
+                return Turbolinks.visit(_this.opts.builds_path);
+              });
+            }
+            return _this.firstCICheck = false;
+          }
+        };
+      })(this));
+    };
+
+    MergeRequestWidget.prototype.showCIStatus = function(state) {
+      var allowed_states;
+      if (state == null) {
+        return;
+      }
+      $('.ci_widget').hide();
+      allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
+      if (indexOf.call(allowed_states, state) >= 0) {
+        $('.ci_widget.ci-' + state).show();
+        switch (state) {
+          case "failed":
+          case "canceled":
+          case "not_found":
+            return this.setMergeButtonClass('btn-danger');
+          case "running":
+            return this.setMergeButtonClass('btn-warning');
+          case "success":
+          case "success_with_warnings":
+            return this.setMergeButtonClass('btn-create');
+        }
+      } else {
+        $('.ci_widget.ci-error').show();
+        return this.setMergeButtonClass('btn-danger');
+      }
+    };
+
+    MergeRequestWidget.prototype.showCICoverage = function(coverage) {
+      var text;
+      text = 'Coverage ' + coverage + '%';
+      return $('.ci_widget:visible .ci-coverage').text(text);
+    };
+
+    MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) {
+      return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class);
+    };
+
+    return MergeRequestWidget;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
deleted file mode 100644
index 779f536d9f07455c104212b1eafa0bd8e44aedd0..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ /dev/null
@@ -1,140 +0,0 @@
-class @MergeRequestWidget
-  # Initialize MergeRequestWidget behavior
-  #
-  #   check_enable           - Boolean, whether to check automerge status
-  #   merge_check_url - String, URL to use to check automerge status
-  #   ci_status_url        - String, URL to use to check CI status
-  #
-
-  constructor: (@opts) ->
-    $('#modal_merge_info').modal(show: false)
-    @firstCICheck = true
-    @readyForCICheck = false
-    @cancel = false
-    clearInterval @fetchBuildStatusInterval
-
-    @clearEventListeners()
-    @addEventListeners()
-    @getCIStatus(false)
-    @pollCIStatus()
-    notifyPermissions()
-
-  clearEventListeners: ->
-    $(document).off 'page:change.merge_request'
-
-  cancelPolling: ->
-    @cancel = true
-
-  addEventListeners: ->
-    allowedPages = ['show', 'commits', 'builds', 'changes']
-    $(document).on 'page:change.merge_request', =>
-      page = $('body').data('page').split(':').last()
-      if allowedPages.indexOf(page) < 0
-        clearInterval @fetchBuildStatusInterval
-        @cancelPolling()
-        @clearEventListeners()
-
-  mergeInProgress: (deleteSourceBranch = false)->
-    $.ajax
-      type: 'GET'
-      url: $('.merge-request').data('url')
-      success: (data) =>
-        if data.state == "merged"
-          urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
-
-          window.location.href = window.location.pathname + urlSuffix
-        else if data.merge_error
-          $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
-        else
-          callback = -> merge_request_widget.mergeInProgress(deleteSourceBranch)
-          setTimeout(callback, 2000)
-      dataType: 'json'
-
-  getMergeStatus: ->
-    $.get @opts.merge_check_url, (data) ->
-      $('.mr-state-widget').replaceWith(data)
-
-  ciLabelForStatus: (status) ->
-    if status is 'success'
-      'passed'
-    else
-      status
-
-  pollCIStatus: ->
-    @fetchBuildStatusInterval = setInterval ( =>
-      return if not @readyForCICheck
-
-      @getCIStatus(true)
-
-      @readyForCICheck = false
-    ), 10000
-
-  getCIStatus: (showNotification) ->
-    _this = @
-    $('.ci-widget-fetching').show()
-
-    $.getJSON @opts.ci_status_url, (data) =>
-      return if @cancel
-      @readyForCICheck = true
-
-      if data.status is ''
-        return
-
-      if @firstCICheck || data.status isnt @opts.ci_status and data.status?
-        @opts.ci_status = data.status
-        @showCIStatus data.status
-        if data.coverage
-          @showCICoverage data.coverage
-
-        # The first check should only update the UI, a notification
-        # should only be displayed on status changes
-        if showNotification and not @firstCICheck
-          status = @ciLabelForStatus(data.status)
-
-          if status is "preparing"
-            title = @opts.ci_title.preparing
-            status = status.charAt(0).toUpperCase() + status.slice(1);
-            message = @opts.ci_message.preparing.replace('{{status}}', status)
-          else
-            title = @opts.ci_title.normal
-            message = @opts.ci_message.normal.replace('{{status}}', status)
-
-          title = title.replace('{{status}}', status)
-          message = message.replace('{{sha}}', data.sha)
-          message = message.replace('{{title}}', data.title)
-
-          notify(
-            title,
-            message,
-            @opts.gitlab_icon,
-            ->
-              @close()
-              Turbolinks.visit _this.opts.builds_path
-          )
-        @firstCICheck = false
-
-  showCIStatus: (state) ->
-    return if not state?
-    $('.ci_widget').hide()
-    allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
-    if state in allowed_states
-      $('.ci_widget.ci-' + state).show()
-      switch state
-        when "failed", "canceled", "not_found"
-          @setMergeButtonClass('btn-danger')
-        when "running"
-          @setMergeButtonClass('btn-warning')
-        when "success"
-          @setMergeButtonClass('btn-create')
-    else
-      $('.ci_widget.ci-error').show()
-      @setMergeButtonClass('btn-danger')
-
-  showCICoverage: (coverage) ->
-    text = 'Coverage ' + coverage + '%'
-    $('.ci_widget:visible .ci-coverage').text(text)
-
-  setMergeButtonClass: (css_class) ->
-    $('.js-merge-button,.accept-action .dropdown-toggle')
-      .removeClass('btn-danger btn-warning btn-create')
-      .addClass(css_class)
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fed38661a2d055c6bd0194643826ee9b78f0616
--- /dev/null
+++ b/app/assets/javascripts/merged_buttons.js
@@ -0,0 +1,45 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.MergedButtons = (function() {
+    function MergedButtons() {
+      this.removeSourceBranch = bind(this.removeSourceBranch, this);
+      this.$removeBranchWidget = $('.remove_source_branch_widget');
+      this.$removeBranchProgress = $('.remove_source_branch_in_progress');
+      this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
+      this.cleanEventListeners();
+      this.initEventListeners();
+    }
+
+    MergedButtons.prototype.cleanEventListeners = function() {
+      $(document).off('click', '.remove_source_branch');
+      $(document).off('ajax:success', '.remove_source_branch');
+      return $(document).off('ajax:error', '.remove_source_branch');
+    };
+
+    MergedButtons.prototype.initEventListeners = function() {
+      $(document).on('click', '.remove_source_branch', this.removeSourceBranch);
+      $(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess);
+      return $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
+    };
+
+    MergedButtons.prototype.removeSourceBranch = function() {
+      this.$removeBranchWidget.hide();
+      return this.$removeBranchProgress.show();
+    };
+
+    MergedButtons.prototype.removeBranchSuccess = function() {
+      return location.reload();
+    };
+
+    MergedButtons.prototype.removeBranchError = function() {
+      this.$removeBranchWidget.hide();
+      this.$removeBranchProgress.hide();
+      return this.$removeBranchFailed.show();
+    };
+
+    return MergedButtons;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/merged_buttons.js.coffee b/app/assets/javascripts/merged_buttons.js.coffee
deleted file mode 100644
index 4929295c10b5baca7f81e8d872d4ec35a04c6b6d..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/merged_buttons.js.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-class @MergedButtons
-  constructor: ->
-    @$removeBranchWidget = $('.remove_source_branch_widget')
-    @$removeBranchProgress = $('.remove_source_branch_in_progress')
-    @$removeBranchFailed = $('.remove_source_branch_widget.failed')
-
-    @cleanEventListeners()
-    @initEventListeners()
-
-  cleanEventListeners: ->
-    $(document).off 'click', '.remove_source_branch'
-    $(document).off 'ajax:success', '.remove_source_branch'
-    $(document).off 'ajax:error', '.remove_source_branch'
-
-  initEventListeners: ->
-    $(document).on 'click', '.remove_source_branch', @removeSourceBranch
-    $(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess
-    $(document).on 'ajax:error', '.remove_source_branch', @removeBranchError
-
-  removeSourceBranch: =>
-    @$removeBranchWidget.hide()
-    @$removeBranchProgress.show()
-
-  removeBranchSuccess: ->
-    location.reload()
-
-  removeBranchError: ->
-    @$removeBranchWidget.hide()
-    @$removeBranchProgress.hide()
-    @$removeBranchFailed.show()
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
new file mode 100644
index 0000000000000000000000000000000000000000..e8d51da7d5829ae07bf60c1a0f824df5d0e50b3b
--- /dev/null
+++ b/app/assets/javascripts/milestone.js
@@ -0,0 +1,195 @@
+(function() {
+  this.Milestone = (function() {
+    Milestone.updateIssue = function(li, issue_url, data) {
+      return $.ajax({
+        type: "PUT",
+        url: issue_url,
+        data: data,
+        success: (function(_this) {
+          return function(_data) {
+            return _this.successCallback(_data, li);
+          };
+        })(this),
+        error: function(data) {
+          return new Flash("Issue update failed", 'alert');
+        },
+        dataType: "json"
+      });
+    };
+
+    Milestone.sortIssues = function(data) {
+      var sort_issues_url;
+      sort_issues_url = location.href + "/sort_issues";
+      return $.ajax({
+        type: "PUT",
+        url: sort_issues_url,
+        data: data,
+        success: (function(_this) {
+          return function(_data) {
+            return _this.successCallback(_data);
+          };
+        })(this),
+        error: function() {
+          return new Flash("Issues update failed", 'alert');
+        },
+        dataType: "json"
+      });
+    };
+
+    Milestone.sortMergeRequests = function(data) {
+      var sort_mr_url;
+      sort_mr_url = location.href + "/sort_merge_requests";
+      return $.ajax({
+        type: "PUT",
+        url: sort_mr_url,
+        data: data,
+        success: (function(_this) {
+          return function(_data) {
+            return _this.successCallback(_data);
+          };
+        })(this),
+        error: function(data) {
+          return new Flash("Issue update failed", 'alert');
+        },
+        dataType: "json"
+      });
+    };
+
+    Milestone.updateMergeRequest = function(li, merge_request_url, data) {
+      return $.ajax({
+        type: "PUT",
+        url: merge_request_url,
+        data: data,
+        success: (function(_this) {
+          return function(_data) {
+            return _this.successCallback(_data, li);
+          };
+        })(this),
+        error: function(data) {
+          return new Flash("Issue update failed", 'alert');
+        },
+        dataType: "json"
+      });
+    };
+
+    Milestone.successCallback = function(data, element) {
+      var img_tag;
+      if (data.assignee) {
+        img_tag = $('<img/>');
+        img_tag.attr('src', data.assignee.avatar_url);
+        img_tag.addClass('avatar s16');
+        $(element).find('.assignee-icon').html(img_tag);
+      } else {
+        $(element).find('.assignee-icon').html('');
+      }
+      return $(element).effect('highlight');
+    };
+
+    function Milestone() {
+      var oldMouseStart;
+      oldMouseStart = $.ui.sortable.prototype._mouseStart;
+      $.ui.sortable.prototype._mouseStart = function(event, overrideHandle, noActivation) {
+        this._trigger("beforeStart", event, this._uiHash());
+        return oldMouseStart.apply(this, [event, overrideHandle, noActivation]);
+      };
+      this.bindIssuesSorting();
+      this.bindMergeRequestSorting();
+      this.bindTabsSwitching();
+    }
+
+    Milestone.prototype.bindIssuesSorting = function() {
+      return $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable({
+        connectWith: ".issues-sortable-list",
+        dropOnEmpty: true,
+        items: "li:not(.ui-sort-disabled)",
+        beforeStart: function(event, ui) {
+          return $(".issues-sortable-list").css("min-height", ui.item.outerHeight());
+        },
+        stop: function(event, ui) {
+          return $(".issues-sortable-list").css("min-height", "0px");
+        },
+        update: function(event, ui) {
+          var data;
+          if ($(this).find(ui.item).length > 0) {
+            data = $(this).sortable("serialize");
+            return Milestone.sortIssues(data);
+          }
+        },
+        receive: function(event, ui) {
+          var data, issue_id, issue_url, new_state;
+          new_state = $(this).data('state');
+          issue_id = ui.item.data('iid');
+          issue_url = ui.item.data('url');
+          data = (function() {
+            switch (new_state) {
+              case 'ongoing':
+                return "issue[assignee_id]=" + gon.current_user_id;
+              case 'unassigned':
+                return "issue[assignee_id]=";
+              case 'closed':
+                return "issue[state_event]=close";
+            }
+          })();
+          if ($(ui.sender).data('state') === "closed") {
+            data += "&issue[state_event]=reopen";
+          }
+          return Milestone.updateIssue(ui.item, issue_url, data);
+        }
+      }).disableSelection();
+    };
+
+    Milestone.prototype.bindTabsSwitching = function() {
+      return $('a[data-toggle="tab"]').on('show.bs.tab', function(e) {
+        var currentTabClass, previousTabClass;
+        currentTabClass = $(e.target).data('show');
+        previousTabClass = $(e.relatedTarget).data('show');
+        $(previousTabClass).hide();
+        $(currentTabClass).removeClass('hidden');
+        return $(currentTabClass).show();
+      });
+    };
+
+    Milestone.prototype.bindMergeRequestSorting = function() {
+      return $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable({
+        connectWith: ".merge_requests-sortable-list",
+        dropOnEmpty: true,
+        items: "li:not(.ui-sort-disabled)",
+        beforeStart: function(event, ui) {
+          return $(".merge_requests-sortable-list").css("min-height", ui.item.outerHeight());
+        },
+        stop: function(event, ui) {
+          return $(".merge_requests-sortable-list").css("min-height", "0px");
+        },
+        update: function(event, ui) {
+          var data;
+          data = $(this).sortable("serialize");
+          return Milestone.sortMergeRequests(data);
+        },
+        receive: function(event, ui) {
+          var data, merge_request_id, merge_request_url, new_state;
+          new_state = $(this).data('state');
+          merge_request_id = ui.item.data('iid');
+          merge_request_url = ui.item.data('url');
+          data = (function() {
+            switch (new_state) {
+              case 'ongoing':
+                return "merge_request[assignee_id]=" + gon.current_user_id;
+              case 'unassigned':
+                return "merge_request[assignee_id]=";
+              case 'closed':
+                return "merge_request[state_event]=close";
+            }
+          })();
+          if ($(ui.sender).data('state') === "closed") {
+            data += "&merge_request[state_event]=reopen";
+          }
+          return Milestone.updateMergeRequest(ui.item, merge_request_url, data);
+        }
+      }).disableSelection();
+    };
+
+    return Milestone;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
deleted file mode 100644
index a19e68b39e291516d4e07fe64e46039ba38faa13..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/milestone.js.coffee
+++ /dev/null
@@ -1,146 +0,0 @@
-class @Milestone
-  @updateIssue: (li, issue_url, data) ->
-    $.ajax
-      type: "PUT"
-      url: issue_url
-      data: data
-      success: (_data) =>
-        @successCallback(_data, li)
-      error: (data) ->
-        new Flash("Issue update failed", 'alert')
-      dataType: "json"
-
-  @sortIssues: (data) ->
-    sort_issues_url = location.href + "/sort_issues"
-
-    $.ajax
-      type: "PUT"
-      url: sort_issues_url
-      data: data
-      success: (_data) =>
-        @successCallback(_data)
-      error: ->
-        new Flash("Issues update failed", 'alert')
-      dataType: "json"
-
-  @sortMergeRequests: (data) ->
-    sort_mr_url = location.href + "/sort_merge_requests"
-
-    $.ajax
-      type: "PUT"
-      url: sort_mr_url
-      data: data
-      success: (_data) =>
-        @successCallback(_data)
-      error: (data) ->
-        new Flash("Issue update failed", 'alert')
-      dataType: "json"
-
-  @updateMergeRequest: (li, merge_request_url, data) ->
-    $.ajax
-      type: "PUT"
-      url: merge_request_url
-      data: data
-      success: (_data) =>
-        @successCallback(_data, li)
-      error: (data) ->
-        new Flash("Issue update failed", 'alert')
-      dataType: "json"
-
-  @successCallback: (data, element) =>
-    if data.assignee
-      img_tag = $('<img/>')
-      img_tag.attr('src', data.assignee.avatar_url)
-      img_tag.addClass('avatar s16')
-      $(element).find('.assignee-icon').html(img_tag)
-    else
-      $(element).find('.assignee-icon').html('')
-
-    $(element).effect 'highlight'
-
-  constructor: ->
-    oldMouseStart = $.ui.sortable.prototype._mouseStart
-    $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
-      this._trigger "beforeStart", event, this._uiHash()
-      oldMouseStart.apply this, [event, overrideHandle, noActivation]
-
-    @bindIssuesSorting()
-    @bindMergeRequestSorting()
-    @bindTabsSwitching()
-
-  bindIssuesSorting: ->
-    $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
-      connectWith: ".issues-sortable-list",
-      dropOnEmpty: true,
-      items: "li:not(.ui-sort-disabled)",
-      beforeStart: (event, ui) ->
-        $(".issues-sortable-list").css "min-height", ui.item.outerHeight()
-      stop: (event, ui) ->
-        $(".issues-sortable-list").css "min-height", "0px"
-      update: (event, ui) ->
-        # Prevents sorting from container which element has been removed.
-        if $(this).find(ui.item).length > 0
-          data = $(this).sortable("serialize")
-          Milestone.sortIssues(data)
-
-      receive: (event, ui) ->
-        new_state = $(this).data('state')
-        issue_id = ui.item.data('iid')
-        issue_url = ui.item.data('url')
-
-        data = switch new_state
-          when 'ongoing'
-            "issue[assignee_id]=" + gon.current_user_id
-          when 'unassigned'
-            "issue[assignee_id]="
-          when 'closed'
-            "issue[state_event]=close"
-
-        if $(ui.sender).data('state') == "closed"
-          data += "&issue[state_event]=reopen"
-
-        Milestone.updateIssue(ui.item, issue_url, data)
-
-    ).disableSelection()
-
-  bindTabsSwitching: ->
-    $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
-      currentTabClass  = $(e.target).data('show')
-      previousTabClass =  $(e.relatedTarget).data('show')
-
-      $(previousTabClass).hide()
-      $(currentTabClass).removeClass('hidden')
-      $(currentTabClass).show()
-
-  bindMergeRequestSorting: ->
-    $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
-      connectWith: ".merge_requests-sortable-list",
-      dropOnEmpty: true,
-      items: "li:not(.ui-sort-disabled)",
-      beforeStart: (event, ui) ->
-        $(".merge_requests-sortable-list").css "min-height", ui.item.outerHeight()
-      stop: (event, ui) ->
-        $(".merge_requests-sortable-list").css "min-height", "0px"
-      update: (event, ui) ->
-        data = $(this).sortable("serialize")
-        Milestone.sortMergeRequests(data)
-
-      receive: (event, ui) ->
-        new_state = $(this).data('state')
-        merge_request_id = ui.item.data('iid')
-        merge_request_url = ui.item.data('url')
-
-        data = switch new_state
-          when 'ongoing'
-            "merge_request[assignee_id]=" + gon.current_user_id
-          when 'unassigned'
-            "merge_request[assignee_id]="
-          when 'closed'
-            "merge_request[state_event]=close"
-
-        if $(ui.sender).data('state') == "closed"
-          data += "&merge_request[state_event]=reopen"
-
-        Milestone.updateMergeRequest(ui.item, merge_request_url, data)
-
-    ).disableSelection()
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0b65d20c03425c71954e1b6d3e968f485dd740c
--- /dev/null
+++ b/app/assets/javascripts/milestone_select.js
@@ -0,0 +1,151 @@
+(function() {
+  this.MilestoneSelect = (function() {
+    function MilestoneSelect(currentProject) {
+      var _this;
+      if (currentProject != null) {
+        _this = this;
+        this.currentProject = JSON.parse(currentProject);
+      }
+      $('.js-milestone-select').each(function(i, dropdown) {
+        var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId;
+        $dropdown = $(dropdown);
+        projectId = $dropdown.data('project-id');
+        milestonesUrl = $dropdown.data('milestones');
+        issueUpdateURL = $dropdown.data('issueUpdate');
+        selectedMilestone = $dropdown.data('selected');
+        showNo = $dropdown.data('show-no');
+        showAny = $dropdown.data('show-any');
+        showUpcoming = $dropdown.data('show-upcoming');
+        useId = $dropdown.data('use-id');
+        defaultLabel = $dropdown.data('default-label');
+        issuableId = $dropdown.data('issuable-id');
+        abilityName = $dropdown.data('ability-name');
+        $selectbox = $dropdown.closest('.selectbox');
+        $block = $selectbox.closest('.block');
+        $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
+        $value = $block.find('.value');
+        $loading = $block.find('.block-loading').fadeOut();
+        if (issueUpdateURL) {
+          milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+          milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
+          collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
+        }
+        return $dropdown.glDropdown({
+          data: function(term, callback) {
+            return $.ajax({
+              url: milestonesUrl
+            }).done(function(data) {
+              var extraOptions;
+              extraOptions = [];
+              if (showAny) {
+                extraOptions.push({
+                  id: 0,
+                  name: '',
+                  title: 'Any Milestone'
+                });
+              }
+              if (showNo) {
+                extraOptions.push({
+                  id: -1,
+                  name: 'No Milestone',
+                  title: 'No Milestone'
+                });
+              }
+              if (showUpcoming) {
+                extraOptions.push({
+                  id: -2,
+                  name: '#upcoming',
+                  title: 'Upcoming'
+                });
+              }
+              if (extraOptions.length > 2) {
+                extraOptions.push('divider');
+              }
+              return callback(extraOptions.concat(data));
+            });
+          },
+          filterable: true,
+          search: {
+            fields: ['title']
+          },
+          selectable: true,
+          toggleLabel: function(selected) {
+            if (selected && 'id' in selected) {
+              return selected.title;
+            } else {
+              return defaultLabel;
+            }
+          },
+          fieldName: $dropdown.data('field-name'),
+          text: function(milestone) {
+            return _.escape(milestone.title);
+          },
+          id: function(milestone) {
+            if (!useId) {
+              return milestone.name;
+            } else {
+              return milestone.id;
+            }
+          },
+          isSelected: function(milestone) {
+            return milestone.name === selectedMilestone;
+          },
+          hidden: function() {
+            $selectbox.hide();
+            return $value.css('display', '');
+          },
+          clicked: function(selected) {
+            var data, isIssueIndex, isMRIndex, page;
+            page = $('body').data('page');
+            isIssueIndex = page === 'projects:issues:index';
+            isMRIndex = (page === page && page === 'projects:merge_requests:index');
+            if ($dropdown.hasClass('js-filter-bulk-update')) {
+              return;
+            }
+            if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+              if (selected.name != null) {
+                selectedMilestone = selected.name;
+              } else {
+                selectedMilestone = '';
+              }
+              return Issuable.filterResults($dropdown.closest('form'));
+            } else if ($dropdown.hasClass('js-filter-submit')) {
+              return $dropdown.closest('form').submit();
+            } else {
+              selected = $selectbox.find('input[type="hidden"]').val();
+              data = {};
+              data[abilityName] = {};
+              data[abilityName].milestone_id = selected != null ? selected : null;
+              $loading.fadeIn();
+              $dropdown.trigger('loading.gl.dropdown');
+              return $.ajax({
+                type: 'PUT',
+                url: issueUpdateURL,
+                data: data
+              }).done(function(data) {
+                $dropdown.trigger('loaded.gl.dropdown');
+                $loading.fadeOut();
+                $selectbox.hide();
+                $value.css('display', '');
+                if (data.milestone != null) {
+                  data.milestone.namespace = _this.currentProject.namespace;
+                  data.milestone.path = _this.currentProject.path;
+                  data.milestone.remaining = $.timefor(data.milestone.due_date);
+                  $value.html(milestoneLinkTemplate(data.milestone));
+                  return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
+                } else {
+                  $value.html(milestoneLinkNoneTemplate);
+                  return $sidebarCollapsedValue.find('span').text('No');
+                }
+              });
+            }
+          }
+        });
+      });
+    }
+
+    return MilestoneSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee
deleted file mode 100644
index 8ab03ed93eec3f53623158ff9454b17a9624628f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/milestone_select.js.coffee
+++ /dev/null
@@ -1,137 +0,0 @@
-class @MilestoneSelect
-  constructor: (currentProject) ->
-    if currentProject?
-      _this = @
-      @currentProject = JSON.parse(currentProject)
-    $('.js-milestone-select').each (i, dropdown) ->
-      $dropdown = $(dropdown)
-      projectId = $dropdown.data('project-id')
-      milestonesUrl = $dropdown.data('milestones')
-      issueUpdateURL = $dropdown.data('issueUpdate')
-      selectedMilestone = $dropdown.data('selected')
-      showNo = $dropdown.data('show-no')
-      showAny = $dropdown.data('show-any')
-      showUpcoming = $dropdown.data('show-upcoming')
-      useId = $dropdown.data('use-id')
-      defaultLabel = $dropdown.data('default-label')
-      issuableId = $dropdown.data('issuable-id')
-      abilityName = $dropdown.data('ability-name')
-      $selectbox = $dropdown.closest('.selectbox')
-      $block = $selectbox.closest('.block')
-      $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
-      $value = $block.find('.value')
-      $loading = $block.find('.block-loading').fadeOut()
-
-      if issueUpdateURL
-        milestoneLinkTemplate = _.template(
-          '<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'
-        )
-
-        milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
-
-        collapsedSidebarLabelTemplate = _.template(
-          '<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left">
-            <%- title %>
-          </span>'
-        )
-
-      $dropdown.glDropdown(
-        data: (term, callback) ->
-          $.ajax(
-            url: milestonesUrl
-          ).done (data) ->
-            extraOptions = []
-            if showAny
-              extraOptions.push(
-                id: 0
-                name: ''
-                title: 'Any Milestone'
-              )
-
-            if showNo
-              extraOptions.push(
-                id: -1
-                name: 'No Milestone'
-                title: 'No Milestone'
-              )
-
-            if showUpcoming
-              extraOptions.push(
-                id: -2
-                name: '#upcoming'
-                title: 'Upcoming'
-              )
-
-            if extraOptions.length > 2
-              extraOptions.push 'divider'
-
-            callback(extraOptions.concat(data))
-        filterable: true
-        search:
-          fields: ['title']
-        selectable: true
-        toggleLabel: (selected) ->
-          if selected && 'id' of selected
-            selected.title
-          else
-            defaultLabel
-        fieldName: $dropdown.data('field-name')
-        text: (milestone) ->
-          _.escape(milestone.title)
-        id: (milestone) ->
-          if !useId
-            milestone.name
-          else
-            milestone.id
-        isSelected: (milestone) ->
-          milestone.name is selectedMilestone
-        hidden: ->
-          $selectbox.hide()
-
-          # display:block overrides the hide-collapse rule
-          $value.css('display', '')
-        clicked: (selected) ->
-          page = $('body').data 'page'
-          isIssueIndex = page is 'projects:issues:index'
-          isMRIndex = page is page is 'projects:merge_requests:index'
-
-          if $dropdown.hasClass 'js-filter-bulk-update'
-            return
-
-          if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
-            if selected.name?
-              selectedMilestone = selected.name
-            else
-              selectedMilestone = ''
-            Issuable.filterResults $dropdown.closest('form')
-          else if $dropdown.hasClass('js-filter-submit')
-            $dropdown.closest('form').submit()
-          else
-            selected = $selectbox
-              .find('input[type="hidden"]')
-              .val()
-            data = {}
-            data[abilityName] = {}
-            data[abilityName].milestone_id = if selected? then selected else null
-            $loading
-              .fadeIn()
-            $dropdown.trigger('loading.gl.dropdown')
-            $.ajax(
-              type: 'PUT'
-              url: issueUpdateURL
-              data: data
-            ).done (data) ->
-              $dropdown.trigger('loaded.gl.dropdown')
-              $loading.fadeOut()
-              $selectbox.hide()
-              $value.css('display', '')
-              if data.milestone?
-                data.milestone.namespace = _this.currentProject.namespace
-                data.milestone.path = _this.currentProject.path
-                data.milestone.remaining = $.timefor data.milestone.due_date
-                $value.html(milestoneLinkTemplate(data.milestone))
-                $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone))
-              else
-                $value.html(milestoneLinkNoneTemplate)
-                $sidebarCollapsedValue.find('span').text('No')
-      )
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..10f4fd106d855b20bf81ab72ffe289cb2cd43067
--- /dev/null
+++ b/app/assets/javascripts/namespace_select.js
@@ -0,0 +1,86 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.NamespaceSelect = (function() {
+    function NamespaceSelect(opts) {
+      this.onSelectItem = bind(this.onSelectItem, this);
+      var fieldName, showAny;
+      this.dropdown = opts.dropdown;
+      showAny = true;
+      fieldName = 'namespace_id';
+      if (this.dropdown.attr('data-field-name')) {
+        fieldName = this.dropdown.data('fieldName');
+      }
+      if (this.dropdown.attr('data-show-any')) {
+        showAny = this.dropdown.data('showAny');
+      }
+      this.dropdown.glDropdown({
+        filterable: true,
+        selectable: true,
+        filterRemote: true,
+        search: {
+          fields: ['path']
+        },
+        fieldName: fieldName,
+        toggleLabel: function(selected) {
+          if (selected.id == null) {
+            return selected.text;
+          } else {
+            return selected.kind + ": " + selected.path;
+          }
+        },
+        data: function(term, dataCallback) {
+          return Api.namespaces(term, function(namespaces) {
+            var anyNamespace;
+            if (showAny) {
+              anyNamespace = {
+                text: 'Any namespace',
+                id: null
+              };
+              namespaces.unshift(anyNamespace);
+              namespaces.splice(1, 0, 'divider');
+            }
+            return dataCallback(namespaces);
+          });
+        },
+        text: function(namespace) {
+          if (namespace.id == null) {
+            return namespace.text;
+          } else {
+            return namespace.kind + ": " + namespace.path;
+          }
+        },
+        renderRow: this.renderRow,
+        clicked: this.onSelectItem
+      });
+    }
+
+    NamespaceSelect.prototype.onSelectItem = function(item, el, e) {
+      return e.preventDefault();
+    };
+
+    return NamespaceSelect;
+
+  })();
+
+  this.NamespaceSelects = (function() {
+    function NamespaceSelects(opts) {
+      var ref;
+      if (opts == null) {
+        opts = {};
+      }
+      this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select');
+      this.$dropdowns.each(function(i, dropdown) {
+        var $dropdown;
+        $dropdown = $(dropdown);
+        return new NamespaceSelect({
+          dropdown: $dropdown
+        });
+      });
+    }
+
+    return NamespaceSelects;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee
deleted file mode 100644
index 3b419dff105f23fc794910e2511f0e659ee6f2d2..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/namespace_select.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-class @NamespaceSelect
-  constructor: (opts) ->
-    {
-      @dropdown
-    } = opts
-
-    showAny = true
-    fieldName = 'namespace_id'
-
-    if @dropdown.attr 'data-field-name'
-     fieldName = @dropdown.data 'fieldName'
-
-    if @dropdown.attr 'data-show-any'
-      showAny = @dropdown.data 'showAny'
-
-    @dropdown.glDropdown(
-      filterable: true
-      selectable: true
-      filterRemote: true
-      search:
-        fields: ['path']
-      fieldName: fieldName
-      toggleLabel: (selected) ->
-        return if not selected.id? then selected.text else "#{selected.kind}: #{selected.path}"
-      data: (term, dataCallback) ->
-        Api.namespaces term, (namespaces) ->
-          if showAny
-            anyNamespace =
-              text: 'Any namespace'
-              id: null
-
-            namespaces.unshift(anyNamespace)
-            namespaces.splice 1, 0, 'divider'
-
-          dataCallback(namespaces)
-      text: (namespace) ->
-        return if not namespace.id? then namespace.text else "#{namespace.kind}: #{namespace.path}"
-      renderRow: @renderRow
-      clicked: @onSelectItem
-    )
-
-  onSelectItem: (item, el, e) =>
-    e.preventDefault()
-
-class @NamespaceSelects
-  constructor: (opts = {}) ->
-    {
-      @$dropdowns = $('.js-namespace-select')
-    } = opts
-
-    @$dropdowns.each (i, dropdown) ->
-      $dropdown = $(dropdown)
-
-      new NamespaceSelect(
-        dropdown: $dropdown
-      )
diff --git a/app/assets/javascripts/network/application.js.coffee b/app/assets/javascripts/network/application.js.coffee
deleted file mode 100644
index f75f63869c58bba87ad7935f971d8575ca2e246c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/network/application.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
-#
-#= require_tree .
-
-$ ->
-  network_graph = new Network({
-    url: $(".network-graph").attr('data-url'),
-    commit_url: $(".network-graph").attr('data-commit-url'),
-    ref: $(".network-graph").attr('data-ref'),
-    commit_id: $(".network-graph").attr('data-commit-id')
-  })
-
-  new ShortcutsNetwork(network_graph.branch_graph)
diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js
new file mode 100644
index 0000000000000000000000000000000000000000..c0fec1f860773b4cb42a9019307b31a6cbfe4963
--- /dev/null
+++ b/app/assets/javascripts/network/branch-graph.js
@@ -0,0 +1,404 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.BranchGraph = (function() {
+    function BranchGraph(element1, options1) {
+      this.element = element1;
+      this.options = options1;
+      this.scrollTop = bind(this.scrollTop, this);
+      this.scrollBottom = bind(this.scrollBottom, this);
+      this.scrollRight = bind(this.scrollRight, this);
+      this.scrollLeft = bind(this.scrollLeft, this);
+      this.scrollUp = bind(this.scrollUp, this);
+      this.scrollDown = bind(this.scrollDown, this);
+      this.preparedCommits = {};
+      this.mtime = 0;
+      this.mspace = 0;
+      this.parents = {};
+      this.colors = ["#000"];
+      this.offsetX = 150;
+      this.offsetY = 20;
+      this.unitTime = 30;
+      this.unitSpace = 10;
+      this.prev_start = -1;
+      this.load();
+    }
+
+    BranchGraph.prototype.load = function() {
+      return $.ajax({
+        url: this.options.url,
+        method: "get",
+        dataType: "json",
+        success: $.proxy(function(data) {
+          $(".loading", this.element).hide();
+          this.prepareData(data.days, data.commits);
+          return this.buildGraph();
+        }, this)
+      });
+    };
+
+    BranchGraph.prototype.prepareData = function(days, commits) {
+      var c, ch, cw, j, len, ref;
+      this.days = days;
+      this.commits = commits;
+      this.collectParents();
+      this.graphHeight = $(this.element).height();
+      this.graphWidth = $(this.element).width();
+      ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
+      cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
+      this.r = Raphael(this.element.get(0), cw, ch);
+      this.top = this.r.set();
+      this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
+      ref = this.commits;
+      for (j = 0, len = ref.length; j < len; j++) {
+        c = ref[j];
+        if (c.id in this.parents) {
+          c.isParent = true;
+        }
+        this.preparedCommits[c.id] = c;
+        this.markCommit(c);
+      }
+      return this.collectColors();
+    };
+
+    BranchGraph.prototype.collectParents = function() {
+      var c, j, len, p, ref, results;
+      ref = this.commits;
+      results = [];
+      for (j = 0, len = ref.length; j < len; j++) {
+        c = ref[j];
+        this.mtime = Math.max(this.mtime, c.time);
+        this.mspace = Math.max(this.mspace, c.space);
+        results.push((function() {
+          var l, len1, ref1, results1;
+          ref1 = c.parents;
+          results1 = [];
+          for (l = 0, len1 = ref1.length; l < len1; l++) {
+            p = ref1[l];
+            this.parents[p[0]] = true;
+            results1.push(this.mspace = Math.max(this.mspace, p[1]));
+          }
+          return results1;
+        }).call(this));
+      }
+      return results;
+    };
+
+    BranchGraph.prototype.collectColors = function() {
+      var k, results;
+      k = 0;
+      results = [];
+      while (k < this.mspace) {
+        this.colors.push(Raphael.getColor(.8));
+        Raphael.getColor();
+        Raphael.getColor();
+        results.push(k++);
+      }
+      return results;
+    };
+
+    BranchGraph.prototype.buildGraph = function() {
+      var cuday, cumonth, day, j, len, mm, r, ref;
+      r = this.r;
+      cuday = 0;
+      cumonth = "";
+      r.rect(0, 0, 40, this.barHeight).attr({
+        fill: "#222"
+      });
+      r.rect(40, 0, 30, this.barHeight).attr({
+        fill: "#444"
+      });
+      ref = this.days;
+      for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
+        day = ref[mm];
+        if (cuday !== day[0] || cumonth !== day[1]) {
+          r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
+            font: "12px Monaco, monospace",
+            fill: "#BBB"
+          });
+          cuday = day[0];
+        }
+        if (cumonth !== day[1]) {
+          r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
+            font: "12px Monaco, monospace",
+            fill: "#EEE"
+          });
+          cumonth = day[1];
+        }
+      }
+      this.renderPartialGraph();
+      return this.bindEvents();
+    };
+
+    BranchGraph.prototype.renderPartialGraph = function() {
+      var commit, end, i, isGraphEdge, start, x, y;
+      start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
+      if (start < 0) {
+        isGraphEdge = true;
+        start = 0;
+      }
+      end = start + 40;
+      if (this.commits.length < end) {
+        isGraphEdge = true;
+        end = this.commits.length;
+      }
+      if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
+        i = start;
+        this.prev_start = start;
+        while (i < end) {
+          commit = this.commits[i];
+          i += 1;
+          if (commit.hasDrawn !== true) {
+            x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+            y = this.offsetY + this.unitTime * commit.time;
+            this.drawDot(x, y, commit);
+            this.drawLines(x, y, commit);
+            this.appendLabel(x, y, commit);
+            this.appendAnchor(x, y, commit);
+            commit.hasDrawn = true;
+          }
+        }
+        return this.top.toFront();
+      }
+    };
+
+    BranchGraph.prototype.bindEvents = function() {
+      var element;
+      element = this.element;
+      return $(element).scroll((function(_this) {
+        return function(event) {
+          return _this.renderPartialGraph();
+        };
+      })(this));
+    };
+
+    BranchGraph.prototype.scrollDown = function() {
+      this.element.scrollTop(this.element.scrollTop() + 50);
+      return this.renderPartialGraph();
+    };
+
+    BranchGraph.prototype.scrollUp = function() {
+      this.element.scrollTop(this.element.scrollTop() - 50);
+      return this.renderPartialGraph();
+    };
+
+    BranchGraph.prototype.scrollLeft = function() {
+      this.element.scrollLeft(this.element.scrollLeft() - 50);
+      return this.renderPartialGraph();
+    };
+
+    BranchGraph.prototype.scrollRight = function() {
+      this.element.scrollLeft(this.element.scrollLeft() + 50);
+      return this.renderPartialGraph();
+    };
+
+    BranchGraph.prototype.scrollBottom = function() {
+      return this.element.scrollTop(this.element.find('svg').height());
+    };
+
+    BranchGraph.prototype.scrollTop = function() {
+      return this.element.scrollTop(0);
+    };
+
+    BranchGraph.prototype.appendLabel = function(x, y, commit) {
+      var label, r, rect, shortrefs, text, textbox, triangle;
+      if (!commit.refs) {
+        return;
+      }
+      r = this.r;
+      shortrefs = commit.refs;
+      if (shortrefs.length > 17) {
+        shortrefs = shortrefs.substr(0, 15) + "…";
+      }
+      text = r.text(x + 4, y, shortrefs).attr({
+        "text-anchor": "start",
+        font: "10px Monaco, monospace",
+        fill: "#FFF",
+        title: commit.refs
+      });
+      textbox = text.getBBox();
+      rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
+        fill: "#000",
+        "fill-opacity": .5,
+        stroke: "none"
+      });
+      triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
+        fill: "#000",
+        "fill-opacity": .5,
+        stroke: "none"
+      });
+      label = r.set(rect, text);
+      label.transform(["t", -rect.getBBox().width - 15, 0]);
+      return text.toFront();
+    };
+
+    BranchGraph.prototype.appendAnchor = function(x, y, commit) {
+      var anchor, options, r, top;
+      r = this.r;
+      top = this.top;
+      options = this.options;
+      anchor = r.circle(x, y, 10).attr({
+        fill: "#000",
+        opacity: 0,
+        cursor: "pointer"
+      }).click(function() {
+        return window.open(options.commit_url.replace("%s", commit.id), "_blank");
+      }).hover(function() {
+        this.tooltip = r.commitTooltip(x + 5, y, commit);
+        return top.push(this.tooltip.insertBefore(this));
+      }, function() {
+        return this.tooltip && this.tooltip.remove() && delete this.tooltip;
+      });
+      return top.push(anchor);
+    };
+
+    BranchGraph.prototype.drawDot = function(x, y, commit) {
+      var avatar_box_x, avatar_box_y, r;
+      r = this.r;
+      r.circle(x, y, 3).attr({
+        fill: this.colors[commit.space],
+        stroke: "none"
+      });
+      avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
+      avatar_box_y = y - 10;
+      r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
+        stroke: this.colors[commit.space],
+        "stroke-width": 2
+      });
+      r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
+      return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
+        "text-anchor": "start",
+        font: "14px Monaco, monospace"
+      });
+    };
+
+    BranchGraph.prototype.drawLines = function(x, y, commit) {
+      var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route;
+      r = this.r;
+      ref = commit.parents;
+      results = [];
+      for (i = j = 0, len = ref.length; j < len; i = ++j) {
+        parent = ref[i];
+        parentCommit = this.preparedCommits[parent[0]];
+        parentY = this.offsetY + this.unitTime * parentCommit.time;
+        parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
+        parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+        if (parentCommit.space <= commit.space) {
+          color = this.colors[commit.space];
+        } else {
+          color = this.colors[parentCommit.space];
+        }
+        if (parent[1] === commit.space) {
+          offset = [0, 5];
+          arrow = "l-2,5,4,0,-2,-5,0,5";
+        } else if (parent[1] < commit.space) {
+          offset = [3, 3];
+          arrow = "l5,0,-2,4,-3,-4,4,2";
+        } else {
+          offset = [-3, 3];
+          arrow = "l-5,0,2,4,3,-4,-4,2";
+        }
+        route = ["M", x + offset[0], y + offset[1]];
+        if (i > 0) {
+          route.push(arrow);
+        }
+        if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
+          route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
+        }
+        route.push("L", parentX1, parentY);
+        results.push(r.path(route).attr({
+          stroke: color,
+          "stroke-width": 2
+        }));
+      }
+      return results;
+    };
+
+    BranchGraph.prototype.markCommit = function(commit) {
+      var r, x, y;
+      if (commit.id === this.options.commit_id) {
+        r = this.r;
+        x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+        y = this.offsetY + this.unitTime * commit.time;
+        r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
+          fill: "#000",
+          "fill-opacity": .5,
+          stroke: "none"
+        });
+        return this.element.scrollTop(y - this.graphHeight / 2);
+      }
+    };
+
+    return BranchGraph;
+
+  })();
+
+  Raphael.prototype.commitTooltip = function(x, y, commit) {
+    var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip;
+    boxWidth = 300;
+    boxHeight = 200;
+    icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20);
+    nameText = this.text(x + 25, y + 10, commit.author.name);
+    idText = this.text(x, y + 35, commit.id);
+    messageText = this.text(x, y + 50, commit.message);
+    textSet = this.set(icon, nameText, idText, messageText).attr({
+      "text-anchor": "start",
+      font: "12px Monaco, monospace"
+    });
+    nameText.attr({
+      font: "14px Arial",
+      "font-weight": "bold"
+    });
+    idText.attr({
+      fill: "#AAA"
+    });
+    this.textWrap(messageText, boxWidth - 50);
+    rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
+      fill: "#FFF",
+      stroke: "#000",
+      "stroke-linecap": "round",
+      "stroke-width": 2
+    });
+    tooltip = this.set(rect, textSet);
+    rect.attr({
+      height: tooltip.getBBox().height + 10,
+      width: tooltip.getBBox().width + 10
+    });
+    tooltip.transform(["t", 20, 20]);
+    return tooltip;
+  };
+
+  Raphael.prototype.textWrap = function(t, width) {
+    var abc, b, content, h, j, len, letterWidth, s, word, words, x;
+    content = t.attr("text");
+    abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    t.attr({
+      text: abc
+    });
+    letterWidth = t.getBBox().width / abc.length;
+    t.attr({
+      text: content
+    });
+    words = content.split(" ");
+    x = 0;
+    s = [];
+    for (j = 0, len = words.length; j < len; j++) {
+      word = words[j];
+      if (x + (word.length * letterWidth) > width) {
+        s.push("\n");
+        x = 0;
+      }
+      x += word.length * letterWidth;
+      s.push(word + " ");
+    }
+    t.attr({
+      text: s.join("")
+    });
+    b = t.getBBox();
+    h = Math.abs(b.y2) - Math.abs(b.y) + 1;
+    return t.attr({
+      y: b.y + h
+    });
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/network/branch-graph.js.coffee b/app/assets/javascripts/network/branch-graph.js.coffee
deleted file mode 100644
index f2fd2a775a4883fe55cbb6f247e1b95eb16ddf8a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/network/branch-graph.js.coffee
+++ /dev/null
@@ -1,340 +0,0 @@
-class @BranchGraph
-  constructor: (@element, @options) ->
-    @preparedCommits = {}
-    @mtime = 0
-    @mspace = 0
-    @parents = {}
-    @colors = ["#000"]
-    @offsetX = 150
-    @offsetY = 20
-    @unitTime = 30
-    @unitSpace = 10
-    @prev_start = -1
-    @load()
-
-  load: ->
-    $.ajax
-      url: @options.url
-      method: "get"
-      dataType: "json"
-      success: $.proxy((data) ->
-        $(".loading", @element).hide()
-        @prepareData data.days, data.commits
-        @buildGraph()
-      , this)
-
-  prepareData: (@days, @commits) ->
-    @collectParents()
-    @graphHeight = $(@element).height()
-    @graphWidth = $(@element).width()
-    ch = Math.max(@graphHeight, @offsetY + @unitTime * @mtime + 150)
-    cw = Math.max(@graphWidth, @offsetX + @unitSpace * @mspace + 300)
-    @r = Raphael(@element.get(0), cw, ch)
-    @top = @r.set()
-    @barHeight = Math.max(@graphHeight, @unitTime * @days.length + 320)
-
-    for c in @commits
-      c.isParent = true  if c.id of @parents
-      @preparedCommits[c.id] = c
-      @markCommit(c)
-
-    @collectColors()
-
-  collectParents: ->
-    for c in @commits
-      @mtime = Math.max(@mtime, c.time)
-      @mspace = Math.max(@mspace, c.space)
-      for p in c.parents
-        @parents[p[0]] = true
-        @mspace = Math.max(@mspace, p[1])
-
-  collectColors: ->
-    k = 0
-    while k < @mspace
-      @colors.push Raphael.getColor(.8)
-      # Skipping a few colors in the spectrum to get more contrast between colors
-      Raphael.getColor()
-      Raphael.getColor()
-      k++
-
-  buildGraph: ->
-    r = @r
-    cuday = 0
-    cumonth = ""
-
-    r.rect(0, 0, 40, @barHeight).attr fill: "#222"
-    r.rect(40, 0, 30, @barHeight).attr fill: "#444"
-
-    for day, mm in @days
-      if cuday isnt day[0] || cumonth isnt day[1]
-        # Dates
-        r.text(55, @offsetY + @unitTime * mm, day[0])
-          .attr(
-            font: "12px Monaco, monospace"
-            fill: "#BBB"
-          )
-        cuday = day[0]
-
-      if cumonth isnt day[1]
-        # Months
-        r.text(20, @offsetY + @unitTime * mm, day[1])
-          .attr(
-            font: "12px Monaco, monospace"
-            fill: "#EEE"
-          )
-        cumonth = day[1]
-
-    @renderPartialGraph()
-
-    @bindEvents()
-
-  renderPartialGraph: ->
-    start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
-    if start < 0
-      isGraphEdge = true
-      start = 0
-    end = start + 40
-    if @commits.length < end
-      isGraphEdge = true
-      end = @commits.length
-
-    if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
-      i = start
-
-      @prev_start = start
-
-      while i < end
-        commit = @commits[i]
-        i += 1
-
-        if commit.hasDrawn isnt true
-          x = @offsetX + @unitSpace * (@mspace - commit.space)
-          y = @offsetY + @unitTime * commit.time
-
-          @drawDot(x, y, commit)
-
-          @drawLines(x, y, commit)
-
-          @appendLabel(x, y, commit)
-
-          @appendAnchor(x, y, commit)
-
-          commit.hasDrawn = true
-
-      @top.toFront()
-
-  bindEvents: ->
-    element = @element
-
-    $(element).scroll (event) =>
-      @renderPartialGraph()
-
-  scrollDown: =>
-    @element.scrollTop @element.scrollTop() + 50
-    @renderPartialGraph()
-
-  scrollUp: =>
-    @element.scrollTop @element.scrollTop() - 50
-    @renderPartialGraph()
-
-  scrollLeft: =>
-    @element.scrollLeft @element.scrollLeft() - 50
-    @renderPartialGraph()
-
-  scrollRight: =>
-    @element.scrollLeft @element.scrollLeft() + 50
-    @renderPartialGraph()
-
-  scrollBottom: =>
-    @element.scrollTop @element.find('svg').height()
-
-  scrollTop: =>
-    @element.scrollTop 0
-
-  appendLabel: (x, y, commit) ->
-    return unless commit.refs
-
-    r = @r
-    shortrefs = commit.refs
-    # Truncate if longer than 15 chars
-    shortrefs = shortrefs.substr(0, 15) + "…"  if shortrefs.length > 17
-    text = r.text(x + 4, y, shortrefs).attr(
-      "text-anchor": "start"
-      font: "10px Monaco, monospace"
-      fill: "#FFF"
-      title: commit.refs
-    )
-    textbox = text.getBBox()
-    # Create rectangle based on the size of the textbox
-    rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
-      fill: "#000"
-      "fill-opacity": .5
-      stroke: "none"
-    )
-    triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
-      fill: "#000"
-      "fill-opacity": .5
-      stroke: "none"
-    )
-
-    label = r.set(rect, text)
-    label.transform(["t", -rect.getBBox().width - 15, 0])
-
-    # Set text to front
-    text.toFront()
-
-  appendAnchor: (x, y, commit) ->
-    r = @r
-    top = @top
-    options = @options
-    anchor = r.circle(x, y, 10).attr(
-      fill: "#000"
-      opacity: 0
-      cursor: "pointer"
-    ).click(->
-      window.open options.commit_url.replace("%s", commit.id), "_blank"
-    ).hover(->
-      @tooltip = r.commitTooltip(x + 5, y, commit)
-      top.push @tooltip.insertBefore(this)
-    , ->
-      @tooltip and @tooltip.remove() and delete @tooltip
-    )
-    top.push anchor
-
-  drawDot: (x, y, commit) ->
-    r = @r
-    r.circle(x, y, 3).attr(
-      fill: @colors[commit.space]
-      stroke: "none"
-    )
-
-    avatar_box_x = @offsetX + @unitSpace * @mspace + 10
-    avatar_box_y = y - 10
-    r.rect(avatar_box_x, avatar_box_y, 20, 20).attr(
-      stroke: @colors[commit.space]
-      "stroke-width": 2
-    )
-    r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
-    r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
-      "text-anchor": "start"
-      font: "14px Monaco, monospace"
-    )
-
-  drawLines: (x, y, commit) ->
-    r = @r
-    for parent, i in commit.parents
-      parentCommit = @preparedCommits[parent[0]]
-      parentY = @offsetY + @unitTime * parentCommit.time
-      parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
-      parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
-
-      # Set line color
-      if parentCommit.space <= commit.space
-        color = @colors[commit.space]
-
-      else
-        color = @colors[parentCommit.space]
-
-      # Build line shape
-      if parent[1] is commit.space
-        offset = [0, 5]
-        arrow = "l-2,5,4,0,-2,-5,0,5"
-
-      else if parent[1] < commit.space
-        offset = [3, 3]
-        arrow = "l5,0,-2,4,-3,-4,4,2"
-
-      else
-        offset = [-3, 3]
-        arrow = "l-5,0,2,4,3,-4,-4,2"
-
-      # Start point
-      route = ["M", x + offset[0], y + offset[1]]
-
-      # Add arrow if not first parent
-      if i > 0
-        route.push(arrow)
-
-      # Circumvent if overlap
-      if commit.space isnt parentCommit.space or commit.space isnt parent[1]
-        route.push(
-          "L", parentX2, y + 10,
-          "L", parentX2, parentY - 5,
-        )
-
-      # End point
-      route.push("L", parentX1, parentY)
-
-      r
-        .path(route)
-        .attr(
-          stroke: color
-          "stroke-width": 2)
-
-  markCommit: (commit) ->
-    if commit.id is @options.commit_id
-      r = @r
-      x = @offsetX + @unitSpace * (@mspace - commit.space)
-      y = @offsetY + @unitTime * commit.time
-      r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
-        fill: "#000"
-        "fill-opacity": .5
-        stroke: "none"
-      )
-      # Displayed in the center
-      @element.scrollTop(y - @graphHeight / 2)
-
-Raphael::commitTooltip = (x, y, commit) ->
-  boxWidth = 300
-  boxHeight = 200
-  icon = @image(gon.relative_url_root + commit.author.icon, x, y, 20, 20)
-  nameText = @text(x + 25, y + 10, commit.author.name)
-  idText = @text(x, y + 35, commit.id)
-  messageText = @text(x, y + 50, commit.message)
-  textSet = @set(icon, nameText, idText, messageText).attr(
-    "text-anchor": "start"
-    font: "12px Monaco, monospace"
-  )
-  nameText.attr(
-    font: "14px Arial"
-    "font-weight": "bold"
-  )
-
-  idText.attr fill: "#AAA"
-  @textWrap messageText, boxWidth - 50
-  rect = @rect(x - 10, y - 10, boxWidth, 100, 4).attr(
-    fill: "#FFF"
-    stroke: "#000"
-    "stroke-linecap": "round"
-    "stroke-width": 2
-  )
-  tooltip = @set(rect, textSet)
-  rect.attr(
-    height: tooltip.getBBox().height + 10
-    width: tooltip.getBBox().width + 10
-  )
-
-  tooltip.transform ["t", 20, 20]
-  tooltip
-
-Raphael::textWrap = (t, width) ->
-  content = t.attr("text")
-  abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-  t.attr text: abc
-  letterWidth = t.getBBox().width / abc.length
-  t.attr text: content
-  words = content.split(" ")
-  x = 0
-  s = []
-
-  for word in words
-    if x + (word.length * letterWidth) > width
-      s.push "\n"
-      x = 0
-    x += word.length * letterWidth
-    s.push word + " "
-
-  t.attr text: s.join("")
-  b = t.getBBox()
-  h = Math.abs(b.y2) - Math.abs(b.y) + 1
-  t.attr y: b.y + h
diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js
new file mode 100644
index 0000000000000000000000000000000000000000..7baebcd100a9203cd7ccddb3187617c7115c8445
--- /dev/null
+++ b/app/assets/javascripts/network/network.js
@@ -0,0 +1,19 @@
+(function() {
+  this.Network = (function() {
+    function Network(opts) {
+      var vph;
+      $("#filter_ref").click(function() {
+        return $(this).closest('form').submit();
+      });
+      this.branch_graph = new BranchGraph($(".network-graph"), opts);
+      vph = $(window).height() - 250;
+      $('.network-graph').css({
+        'height': vph + 'px'
+      });
+    }
+
+    return Network;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/network/network.js.coffee b/app/assets/javascripts/network/network.js.coffee
deleted file mode 100644
index f4ef07a50a7e10efbab544384eec31020d5f6922..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/network/network.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @Network
-  constructor: (opts) ->
-    $("#filter_ref").click ->
-      $(this).closest('form').submit()
-
-    @branch_graph = new BranchGraph($(".network-graph"), opts)
-
-    vph = $(window).height() - 250
-    $('.network-graph').css 'height': (vph + 'px')
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a7422a77553bf88e625cc29249aa4b9d2cf0afc
--- /dev/null
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -0,0 +1,16 @@
+
+/*= require_tree . */
+
+(function() {
+  $(function() {
+    var network_graph;
+    network_graph = new Network({
+      url: $(".network-graph").attr('data-url'),
+      commit_url: $(".network-graph").attr('data-commit-url'),
+      ref: $(".network-graph").attr('data-ref'),
+      commit_id: $(".network-graph").attr('data-commit-id')
+    });
+    return new ShortcutsNetwork(network_graph.branch_graph);
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..20aa2fced27e0e8cbff5d853a132d21f7df8286d
--- /dev/null
+++ b/app/assets/javascripts/new_branch_form.js
@@ -0,0 +1,104 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+  this.NewBranchForm = (function() {
+    function NewBranchForm(form, availableRefs) {
+      this.validate = bind(this.validate, this);
+      this.branchNameError = form.find('.js-branch-name-error');
+      this.name = form.find('.js-branch-name');
+      this.ref = form.find('#ref');
+      this.setupAvailableRefs(availableRefs);
+      this.setupRestrictions();
+      this.addBinding();
+      this.init();
+    }
+
+    NewBranchForm.prototype.addBinding = function() {
+      return this.name.on('blur', this.validate);
+    };
+
+    NewBranchForm.prototype.init = function() {
+      if (this.name.val().length > 0) {
+        return this.name.trigger('blur');
+      }
+    };
+
+    NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
+      return this.ref.autocomplete({
+        source: availableRefs,
+        minLength: 1
+      });
+    };
+
+    NewBranchForm.prototype.setupRestrictions = function() {
+      var endsWith, invalid, single, startsWith;
+      startsWith = {
+        pattern: /^(\/|\.)/g,
+        prefix: "can't start with",
+        conjunction: "or"
+      };
+      endsWith = {
+        pattern: /(\/|\.|\.lock)$/g,
+        prefix: "can't end in",
+        conjunction: "or"
+      };
+      invalid = {
+        pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
+        prefix: "can't contain",
+        conjunction: ", "
+      };
+      single = {
+        pattern: /^@+$/g,
+        prefix: "can't be",
+        conjunction: "or"
+      };
+      return this.restrictions = [startsWith, invalid, endsWith, single];
+    };
+
+    NewBranchForm.prototype.validate = function() {
+      var errorMessage, errors, formatter, unique, validator;
+      this.branchNameError.empty();
+      unique = function(values, value) {
+        if (indexOf.call(values, value) < 0) {
+          values.push(value);
+        }
+        return values;
+      };
+      formatter = function(values, restriction) {
+        var formatted;
+        formatted = values.map(function(value) {
+          switch (false) {
+            case !/\s/.test(value):
+              return 'spaces';
+            case !/\/{2,}/g.test(value):
+              return 'consecutive slashes';
+            default:
+              return "'" + value + "'";
+          }
+        });
+        return restriction.prefix + " " + (formatted.join(restriction.conjunction));
+      };
+      validator = (function(_this) {
+        return function(errors, restriction) {
+          var matched;
+          matched = _this.name.val().match(restriction.pattern);
+          if (matched) {
+            return errors.concat(formatter(matched.reduce(unique, []), restriction));
+          } else {
+            return errors;
+          }
+        };
+      })(this);
+      errors = this.restrictions.reduce(validator, []);
+      if (errors.length > 0) {
+        errorMessage = $("<span/>").text(errors.join(', '));
+        return this.branchNameError.append(errorMessage);
+      }
+    };
+
+    return NewBranchForm;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/new_branch_form.js.coffee b/app/assets/javascripts/new_branch_form.js.coffee
deleted file mode 100644
index 4b350854f7855d438dc3a06d6c5ce202c9ed91af..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/new_branch_form.js.coffee
+++ /dev/null
@@ -1,78 +0,0 @@
-class @NewBranchForm
-  constructor: (form, availableRefs) ->
-    @branchNameError = form.find('.js-branch-name-error')
-    @name = form.find('.js-branch-name')
-    @ref  = form.find('#ref')
-
-    @setupAvailableRefs(availableRefs)
-    @setupRestrictions()
-    @addBinding()
-    @init()
-
-  addBinding: ->
-    @name.on 'blur', @validate
-
-  init: ->
-    @name.trigger 'blur' if @name.val().length > 0
-
-  setupAvailableRefs: (availableRefs) ->
-    @ref.autocomplete
-      source: availableRefs,
-      minLength: 1
-
-  setupRestrictions: ->
-    startsWith = {
-      pattern: /^(\/|\.)/g,
-      prefix: "can't start with",
-      conjunction: "or"
-    }
-
-    endsWith = {
-      pattern: /(\/|\.|\.lock)$/g,
-      prefix: "can't end in",
-      conjunction: "or"
-    }
-
-    invalid = {
-      pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g
-      prefix: "can't contain",
-      conjunction: ", "
-    }
-
-    single = {
-      pattern: /^@+$/g
-      prefix: "can't be",
-      conjunction: "or"
-    }
-
-    @restrictions = [startsWith, invalid, endsWith, single]
-
-  validate: =>
-    @branchNameError.empty()
-
-    unique = (values, value) ->
-      values.push(value) unless value in values
-      values
-
-    formatter = (values, restriction) ->
-      formatted = values.map (value) ->
-        switch
-          when /\s/.test value then 'spaces'
-          when /\/{2,}/g.test value then 'consecutive slashes'
-          else "'#{value}'"
-
-      "#{restriction.prefix} #{formatted.join(restriction.conjunction)}"
-
-    validator = (errors, restriction) =>
-      matched = @name.val().match(restriction.pattern)
-
-      if matched
-        errors.concat formatter(matched.reduce(unique, []), restriction)
-      else
-        errors
-
-    errors = @restrictions.reduce validator, []
-
-    if errors.length > 0
-      errorMessage = $("<span/>").text(errors.join(', '))
-      @branchNameError.append(errorMessage)
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..21bf8867f7b566be7680dc92982fb7d2901b78c1
--- /dev/null
+++ b/app/assets/javascripts/new_commit_form.js
@@ -0,0 +1,34 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.NewCommitForm = (function() {
+    function NewCommitForm(form) {
+      this.renderDestination = bind(this.renderDestination, this);
+      this.newBranch = form.find('.js-target-branch');
+      this.originalBranch = form.find('.js-original-branch');
+      this.createMergeRequest = form.find('.js-create-merge-request');
+      this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
+      this.renderDestination();
+      this.newBranch.keyup(this.renderDestination);
+    }
+
+    NewCommitForm.prototype.renderDestination = function() {
+      var different;
+      different = this.newBranch.val() !== this.originalBranch.val();
+      if (different) {
+        this.createMergeRequestContainer.show();
+        if (!this.wasDifferent) {
+          this.createMergeRequest.prop('checked', true);
+        }
+      } else {
+        this.createMergeRequestContainer.hide();
+        this.createMergeRequest.prop('checked', false);
+      }
+      return this.wasDifferent = different;
+    };
+
+    return NewCommitForm;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
deleted file mode 100644
index 03f0f51acfad536ba0927ceaf5541f774796e0dd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ /dev/null
@@ -1,21 +0,0 @@
-class @NewCommitForm
-  constructor: (form) ->
-    @newBranch = form.find('.js-target-branch')
-    @originalBranch = form.find('.js-original-branch')
-    @createMergeRequest = form.find('.js-create-merge-request')
-    @createMergeRequestContainer = form.find('.js-create-merge-request-container')
-
-    @renderDestination()
-    @newBranch.keyup @renderDestination
-
-  renderDestination: =>
-    different = @newBranch.val() != @originalBranch.val()
-
-    if different
-      @createMergeRequestContainer.show()
-      @createMergeRequest.prop('checked', true) unless @wasDifferent
-    else
-      @createMergeRequestContainer.hide()
-      @createMergeRequest.prop('checked', false)
-
-    @wasDifferent = different
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ece474d9941ea4c8f64bcfe67d98c2da3993022
--- /dev/null
+++ b/app/assets/javascripts/notes.js
@@ -0,0 +1,732 @@
+
+/*= require autosave */
+
+
+/*= require autosize */
+
+
+/*= require dropzone */
+
+
+/*= require dropzone_input */
+
+
+/*= require gfm_auto_complete */
+
+
+/*= require jquery.atwho */
+
+
+/*= require task_list */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Notes = (function() {
+    var isMetaKey;
+
+    Notes.interval = null;
+
+    function Notes(notes_url, note_ids, last_fetched_at, view) {
+      this.updateTargetButtons = bind(this.updateTargetButtons, this);
+      this.updateCloseButton = bind(this.updateCloseButton, this);
+      this.visibilityChange = bind(this.visibilityChange, this);
+      this.cancelDiscussionForm = bind(this.cancelDiscussionForm, this);
+      this.addDiffNote = bind(this.addDiffNote, this);
+      this.setupDiscussionNoteForm = bind(this.setupDiscussionNoteForm, this);
+      this.replyToDiscussionNote = bind(this.replyToDiscussionNote, this);
+      this.removeNote = bind(this.removeNote, this);
+      this.cancelEdit = bind(this.cancelEdit, this);
+      this.updateNote = bind(this.updateNote, this);
+      this.addDiscussionNote = bind(this.addDiscussionNote, this);
+      this.addNoteError = bind(this.addNoteError, this);
+      this.addNote = bind(this.addNote, this);
+      this.resetMainTargetForm = bind(this.resetMainTargetForm, this);
+      this.refresh = bind(this.refresh, this);
+      this.keydownNoteText = bind(this.keydownNoteText, this);
+      this.notes_url = notes_url;
+      this.note_ids = note_ids;
+      this.last_fetched_at = last_fetched_at;
+      this.view = view;
+      this.noteable_url = document.URL;
+      this.notesCountBadge || (this.notesCountBadge = $(".issuable-details").find(".notes-tab .badge"));
+      this.basePollingInterval = 15000;
+      this.maxPollingSteps = 4;
+      this.cleanBinding();
+      this.addBinding();
+      this.setPollingInterval();
+      this.setupMainTargetNoteForm();
+      this.initTaskList();
+    }
+
+    Notes.prototype.addBinding = function() {
+      $(document).on("ajax:success", ".js-main-target-form", this.addNote);
+      $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
+      $(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
+      $(document).on("ajax:success", "form.edit-note", this.updateNote);
+      $(document).on("click", ".js-note-edit", this.showEditForm);
+      $(document).on("click", ".note-edit-cancel", this.cancelEdit);
+      $(document).on("click", ".js-comment-button", this.updateCloseButton);
+      $(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
+      $(document).on("click", ".js-note-delete", this.removeNote);
+      $(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
+      $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
+      $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
+      $(document).on("click", ".js-note-discard", this.resetMainTargetForm);
+      $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
+      $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
+      $(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
+      $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
+      $(document).on("visibilitychange", this.visibilityChange);
+      $(document).on("issuable:change", this.refresh);
+      return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
+    };
+
+    Notes.prototype.cleanBinding = function() {
+      $(document).off("ajax:success", ".js-main-target-form");
+      $(document).off("ajax:success", ".js-discussion-note-form");
+      $(document).off("ajax:success", "form.edit-note");
+      $(document).off("click", ".js-note-edit");
+      $(document).off("click", ".note-edit-cancel");
+      $(document).off("click", ".js-note-delete");
+      $(document).off("click", ".js-note-attachment-delete");
+      $(document).off("ajax:complete", ".js-main-target-form");
+      $(document).off("ajax:success", ".js-main-target-form");
+      $(document).off("click", ".js-discussion-reply-button");
+      $(document).off("click", ".js-add-diff-note-button");
+      $(document).off("visibilitychange");
+      $(document).off("keyup", ".js-note-text");
+      $(document).off("click", ".js-note-target-reopen");
+      $(document).off("click", ".js-note-target-close");
+      $(document).off("click", ".js-note-discard");
+      $(document).off("keydown", ".js-note-text");
+      $('.note .js-task-list-container').taskList('disable');
+      return $(document).off('tasklist:changed', '.note .js-task-list-container');
+    };
+
+    Notes.prototype.keydownNoteText = function(e) {
+      var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
+      if (isMetaKey(e)) {
+        return;
+      }
+      $textarea = $(e.target);
+      switch (e.which) {
+        case 38:
+          if ($textarea.val() !== '') {
+            return;
+          }
+          myLastNote = $("li.note[data-author-id='" + gon.current_user_id + "'][data-editable]:last");
+          if (myLastNote.length) {
+            myLastNoteEditBtn = myLastNote.find('.js-note-edit');
+            return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
+          }
+          break;
+        case 27:
+          discussionNoteForm = $textarea.closest('.js-discussion-note-form');
+          if (discussionNoteForm.length) {
+            if ($textarea.val() !== '') {
+              if (!confirm('Are you sure you want to cancel creating this comment?')) {
+                return;
+              }
+            }
+            this.removeDiscussionNoteForm(discussionNoteForm);
+            return;
+          }
+          editNote = $textarea.closest('.note');
+          if (editNote.length) {
+            originalText = $textarea.closest('form').data('original-note');
+            newText = $textarea.val();
+            if (originalText !== newText) {
+              if (!confirm('Are you sure you want to cancel editing this comment?')) {
+                return;
+              }
+            }
+            return this.removeNoteEditForm(editNote);
+          }
+      }
+    };
+
+    isMetaKey = function(e) {
+      return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
+    };
+
+    Notes.prototype.initRefresh = function() {
+      clearInterval(Notes.interval);
+      return Notes.interval = setInterval((function(_this) {
+        return function() {
+          return _this.refresh();
+        };
+      })(this), this.pollingInterval);
+    };
+
+    Notes.prototype.refresh = function() {
+      if (!document.hidden && document.URL.indexOf(this.noteable_url) === 0) {
+        return this.getContent();
+      }
+    };
+
+    Notes.prototype.getContent = function() {
+      if (this.refreshing) {
+        return;
+      }
+      this.refreshing = true;
+      return $.ajax({
+        url: this.notes_url,
+        data: "last_fetched_at=" + this.last_fetched_at,
+        dataType: "json",
+        success: (function(_this) {
+          return function(data) {
+            var notes;
+            notes = data.notes;
+            _this.last_fetched_at = data.last_fetched_at;
+            _this.setPollingInterval(data.notes.length);
+            return $.each(notes, function(i, note) {
+              if (note.discussion_html != null) {
+                return _this.renderDiscussionNote(note);
+              } else {
+                return _this.renderNote(note);
+              }
+            });
+          };
+        })(this)
+      }).always((function(_this) {
+        return function() {
+          return _this.refreshing = false;
+        };
+      })(this));
+    };
+
+
+    /*
+    Increase @pollingInterval up to 120 seconds on every function call,
+    if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
+    will reset to @basePollingInterval.
+    
+    Note: this function is used to gradually increase the polling interval
+    if there aren't new notes coming from the server
+     */
+
+    Notes.prototype.setPollingInterval = function(shouldReset) {
+      var nthInterval;
+      if (shouldReset == null) {
+        shouldReset = true;
+      }
+      nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
+      if (shouldReset) {
+        this.pollingInterval = this.basePollingInterval;
+      } else if (this.pollingInterval < nthInterval) {
+        this.pollingInterval *= 2;
+      }
+      return this.initRefresh();
+    };
+
+
+    /*
+    Render note in main comments area.
+    
+    Note: for rendering inline notes use renderDiscussionNote
+     */
+
+    Notes.prototype.renderNote = function(note) {
+      var $notesList, votesBlock;
+      if (!note.valid) {
+        if (note.award) {
+          new Flash('You have already awarded this emoji!', 'alert');
+        }
+        return;
+      }
+      if (note.award) {
+        votesBlock = $('.js-awards-block').eq(0);
+        gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
+        return gl.awardsHandler.scrollToAwards();
+      } else if (this.isNewNote(note)) {
+        this.note_ids.push(note.id);
+        $notesList = $('ul.main-notes-list');
+        $notesList.append(note.html).syntaxHighlight();
+        gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
+        this.initTaskList();
+        return this.updateNotesCount(1);
+      }
+    };
+
+
+    /*
+    Check if note does not exists on page
+     */
+
+    Notes.prototype.isNewNote = function(note) {
+      return $.inArray(note.id, this.note_ids) === -1;
+    };
+
+    Notes.prototype.isParallelView = function() {
+      return this.view === 'parallel';
+    };
+
+
+    /*
+    Render note in discussion area.
+    
+    Note: for rendering inline notes use renderDiscussionNote
+     */
+
+    Notes.prototype.renderDiscussionNote = function(note) {
+      var discussionContainer, form, note_html, row;
+      if (!this.isNewNote(note)) {
+        return;
+      }
+      this.note_ids.push(note.id);
+      form = $("#new-discussion-note-form-" + note.discussion_id);
+      if ((note.original_discussion_id != null) && form.length === 0) {
+        form = $("#new-discussion-note-form-" + note.original_discussion_id);
+      }
+      row = form.closest("tr");
+      note_html = $(note.html);
+      note_html.syntaxHighlight();
+      discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+      if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
+        discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
+      }
+      if (discussionContainer.length === 0) {
+        row.after(note.diff_discussion_html);
+        row.next().find(".note").remove();
+        discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
+        discussionContainer.append(note_html);
+        if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
+          $('ul.main-notes-list').append(note.discussion_html).syntaxHighlight();
+        }
+      } else {
+        discussionContainer.append(note_html);
+      }
+      gl.utils.localTimeAgo($('.js-timeago', note_html), false);
+      return this.updateNotesCount(1);
+    };
+
+
+    /*
+    Called in response the main target form has been successfully submitted.
+    
+    Removes any errors.
+    Resets text and preview.
+    Resets buttons.
+     */
+
+    Notes.prototype.resetMainTargetForm = function(e) {
+      var form;
+      form = $(".js-main-target-form");
+      form.find(".js-errors").remove();
+      form.find(".js-md-write-button").click();
+      form.find(".js-note-text").val("").trigger("input");
+      form.find(".js-note-text").data("autosave").reset();
+      return this.updateTargetButtons(e);
+    };
+
+    Notes.prototype.reenableTargetFormSubmitButton = function() {
+      var form;
+      form = $(".js-main-target-form");
+      return form.find(".js-note-text").trigger("input");
+    };
+
+
+    /*
+    Shows the main form and does some setup on it.
+    
+    Sets some hidden fields in the form.
+     */
+
+    Notes.prototype.setupMainTargetNoteForm = function() {
+      var form;
+      form = $(".js-new-note-form");
+      this.formClone = form.clone();
+      this.setupNoteForm(form);
+      form.removeClass("js-new-note-form");
+      form.addClass("js-main-target-form");
+      form.find("#note_line_code").remove();
+      form.find("#note_position").remove();
+      form.find("#note_type").remove();
+      return this.parentTimeline = form.parents('.timeline');
+    };
+
+
+    /*
+    General note form setup.
+    
+    deactivates the submit button when text is empty
+    hides the preview button when text is empty
+    setup GFM auto complete
+    show the form
+     */
+
+    Notes.prototype.setupNoteForm = function(form) {
+      var textarea;
+      new GLForm(form);
+      textarea = form.find(".js-note-text");
+      return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
+    };
+
+
+    /*
+    Called in response to the new note form being submitted
+    
+    Adds new note to list.
+     */
+
+    Notes.prototype.addNote = function(xhr, note, status) {
+      return this.renderNote(note);
+    };
+
+    Notes.prototype.addNoteError = function(xhr, note, status) {
+      return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
+    };
+
+
+    /*
+    Called in response to the new note form being submitted
+    
+    Adds new note to list.
+     */
+
+    Notes.prototype.addDiscussionNote = function(xhr, note, status) {
+      this.renderDiscussionNote(note);
+      return this.removeDiscussionNoteForm($(xhr.target));
+    };
+
+
+    /*
+    Called in response to the edit note form being submitted
+    
+    Updates the current note field.
+     */
+
+    Notes.prototype.updateNote = function(_xhr, note, _status) {
+      var $html, $note_li;
+      $html = $(note.html);
+      gl.utils.localTimeAgo($('.js-timeago', $html));
+      $html.syntaxHighlight();
+      $html.find('.js-task-list-container').taskList('enable');
+      $note_li = $('.note-row-' + note.id);
+      return $note_li.replaceWith($html);
+    };
+
+
+    /*
+    Called in response to clicking the edit note link
+    
+    Replaces the note text with the note edit form
+    Adds a data attribute to the form with the original content of the note for cancellations
+     */
+
+    Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
+      var $noteText, done, form, note;
+      e.preventDefault();
+      note = $(this).closest(".note");
+      note.addClass("is-editting");
+      form = note.find(".note-edit-form");
+      form.addClass('current-note-edit-form');
+      note.find(".js-note-attachment-delete").show();
+      done = function($noteText) {
+        var noteTextVal;
+        noteTextVal = $noteText.val();
+        form.find('form.edit-note').data('original-note', noteTextVal);
+        return $noteText.val('').val(noteTextVal);
+      };
+      new GLForm(form);
+      if ((scrollTo != null) && (myLastNote != null)) {
+        $('html, body').scrollTop($(document).height());
+        return $('html, body').animate({
+          scrollTop: myLastNote.offset().top - 150
+        }, 500, function() {
+          var $noteText;
+          $noteText = form.find(".js-note-text");
+          $noteText.focus();
+          return done($noteText);
+        });
+      } else {
+        $noteText = form.find('.js-note-text');
+        $noteText.focus();
+        return done($noteText);
+      }
+    };
+
+
+    /*
+    Called in response to clicking the edit note link
+    
+    Hides edit form and restores the original note text to the editor textarea.
+     */
+
+    Notes.prototype.cancelEdit = function(e) {
+      var note;
+      e.preventDefault();
+      note = $(e.target).closest('.note');
+      return this.removeNoteEditForm(note);
+    };
+
+    Notes.prototype.removeNoteEditForm = function(note) {
+      var form;
+      form = note.find(".current-note-edit-form");
+      note.removeClass("is-editting");
+      form.removeClass("current-note-edit-form");
+      return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
+    };
+
+
+    /*
+    Called in response to deleting a note of any kind.
+    
+    Removes the actual note from view.
+    Removes the whole discussion if the last note is being removed.
+     */
+
+    Notes.prototype.removeNote = function(e) {
+      var noteId;
+      noteId = $(e.currentTarget).closest(".note").attr("id");
+      $(".note[id='" + noteId + "']").each((function(_this) {
+        return function(i, el) {
+          var note, notes;
+          note = $(el);
+          notes = note.closest(".notes");
+          if (notes.find(".note").length === 1) {
+            notes.closest(".timeline-entry").remove();
+            notes.closest("tr").remove();
+          }
+          return note.remove();
+        };
+      })(this));
+      return this.updateNotesCount(-1);
+    };
+
+
+    /*
+    Called in response to clicking the delete attachment link
+    
+    Removes the attachment wrapper view, including image tag if it exists
+    Resets the note editing form
+     */
+
+    Notes.prototype.removeAttachment = function() {
+      var note;
+      note = $(this).closest(".note");
+      note.find(".note-attachment").remove();
+      note.find(".note-body > .note-text").show();
+      note.find(".note-header").show();
+      return note.find(".current-note-edit-form").remove();
+    };
+
+
+    /*
+    Called when clicking on the "reply" button for a diff line.
+    
+    Shows the note form below the notes.
+     */
+
+    Notes.prototype.replyToDiscussionNote = function(e) {
+      var form, replyLink;
+      form = this.formClone.clone();
+      replyLink = $(e.target).closest(".js-discussion-reply-button");
+      replyLink.hide();
+      replyLink.after(form);
+      return this.setupDiscussionNoteForm(replyLink, form);
+    };
+
+
+    /*
+    Shows the diff or discussion form and does some setup on it.
+    
+    Sets some hidden fields in the form.
+    
+    Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
+    and "noteableId" data attributes set.
+     */
+
+    Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
+      form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
+      form.attr("data-line-code", dataHolder.data("lineCode"));
+      form.find("#note_type").val(dataHolder.data("noteType"));
+      form.find("#line_type").val(dataHolder.data("lineType"));
+      form.find("#note_commit_id").val(dataHolder.data("commitId"));
+      form.find("#note_line_code").val(dataHolder.data("lineCode"));
+      form.find("#note_position").val(dataHolder.attr("data-position"));
+      form.find("#note_noteable_type").val(dataHolder.data("noteableType"));
+      form.find("#note_noteable_id").val(dataHolder.data("noteableId"));
+      form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancel-text'));
+      this.setupNoteForm(form);
+      form.find(".js-note-text").focus();
+      return form.removeClass('js-main-target-form').addClass("discussion-form js-discussion-note-form");
+    };
+
+
+    /*
+    Called when clicking on the "add a comment" button on the side of a diff line.
+    
+    Inserts a temporary row for the form below the line.
+    Sets up the form and shows it.
+     */
+
+    Notes.prototype.addDiffNote = function(e) {
+      var $link, addForm, hasNotes, lineType, newForm, nextRow, noteForm, notesContent, replyButton, row, rowCssToAdd, targetContent;
+      e.preventDefault();
+      $link = $(e.currentTarget);
+      row = $link.closest("tr");
+      nextRow = row.next();
+      hasNotes = nextRow.is(".notes_holder");
+      addForm = false;
+      targetContent = ".notes_content";
+      rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>";
+      if (this.isParallelView()) {
+        lineType = $link.data("lineType");
+        targetContent += "." + lineType;
+        rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>";
+      }
+      if (hasNotes) {
+        notesContent = nextRow.find(targetContent);
+        if (notesContent.length) {
+          replyButton = notesContent.find(".js-discussion-reply-button:visible");
+          if (replyButton.length) {
+            e.target = replyButton[0];
+            $.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
+          } else {
+            noteForm = notesContent.find(".js-discussion-note-form");
+            if (noteForm.length === 0) {
+              addForm = true;
+            }
+          }
+        }
+      } else {
+        row.after(rowCssToAdd);
+        addForm = true;
+      }
+      if (addForm) {
+        newForm = this.formClone.clone();
+        newForm.appendTo(row.next().find(targetContent));
+        return this.setupDiscussionNoteForm($link, newForm);
+      }
+    };
+
+
+    /*
+    Called in response to "cancel" on a diff note form.
+    
+    Shows the reply button again.
+    Removes the form and if necessary it's temporary row.
+     */
+
+    Notes.prototype.removeDiscussionNoteForm = function(form) {
+      var glForm, row;
+      row = form.closest("tr");
+      glForm = form.data('gl-form');
+      glForm.destroy();
+      form.find(".js-note-text").data("autosave").reset();
+      form.prev(".js-discussion-reply-button").show();
+      if (row.is(".js-temp-notes-holder")) {
+        return row.remove();
+      } else {
+        return form.remove();
+      }
+    };
+
+    Notes.prototype.cancelDiscussionForm = function(e) {
+      var form;
+      e.preventDefault();
+      form = $(e.target).closest(".js-discussion-note-form");
+      return this.removeDiscussionNoteForm(form);
+    };
+
+
+    /*
+    Called after an attachment file has been selected.
+    
+    Updates the file name for the selected attachment.
+     */
+
+    Notes.prototype.updateFormAttachment = function() {
+      var filename, form;
+      form = $(this).closest("form");
+      filename = $(this).val().replace(/^.*[\\\/]/, "");
+      return form.find(".js-attachment-filename").text(filename);
+    };
+
+
+    /*
+    Called when the tab visibility changes
+     */
+
+    Notes.prototype.visibilityChange = function() {
+      return this.refresh();
+    };
+
+    Notes.prototype.updateCloseButton = function(e) {
+      var closebtn, form, textarea;
+      textarea = $(e.target);
+      form = textarea.parents('form');
+      closebtn = form.find('.js-note-target-close');
+      return closebtn.text(closebtn.data('original-text'));
+    };
+
+    Notes.prototype.updateTargetButtons = function(e) {
+      var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
+      textarea = $(e.target);
+      form = textarea.parents('form');
+      reopenbtn = form.find('.js-note-target-reopen');
+      closebtn = form.find('.js-note-target-close');
+      discardbtn = form.find('.js-note-discard');
+      if (textarea.val().trim().length > 0) {
+        reopentext = reopenbtn.data('alternative-text');
+        closetext = closebtn.data('alternative-text');
+        if (reopenbtn.text() !== reopentext) {
+          reopenbtn.text(reopentext);
+        }
+        if (closebtn.text() !== closetext) {
+          closebtn.text(closetext);
+        }
+        if (reopenbtn.is(':not(.btn-comment-and-reopen)')) {
+          reopenbtn.addClass('btn-comment-and-reopen');
+        }
+        if (closebtn.is(':not(.btn-comment-and-close)')) {
+          closebtn.addClass('btn-comment-and-close');
+        }
+        if (discardbtn.is(':hidden')) {
+          return discardbtn.show();
+        }
+      } else {
+        reopentext = reopenbtn.data('original-text');
+        closetext = closebtn.data('original-text');
+        if (reopenbtn.text() !== reopentext) {
+          reopenbtn.text(reopentext);
+        }
+        if (closebtn.text() !== closetext) {
+          closebtn.text(closetext);
+        }
+        if (reopenbtn.is('.btn-comment-and-reopen')) {
+          reopenbtn.removeClass('btn-comment-and-reopen');
+        }
+        if (closebtn.is('.btn-comment-and-close')) {
+          closebtn.removeClass('btn-comment-and-close');
+        }
+        if (discardbtn.is(':visible')) {
+          return discardbtn.hide();
+        }
+      }
+    };
+
+    Notes.prototype.initTaskList = function() {
+      this.enableTaskList();
+      return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList);
+    };
+
+    Notes.prototype.enableTaskList = function() {
+      return $('.note .js-task-list-container').taskList('enable');
+    };
+
+    Notes.prototype.updateTaskList = function() {
+      return $('form', this).submit();
+    };
+
+    Notes.prototype.updateNotesCount = function(updateCount) {
+      return this.notesCountBadge.text(parseInt(this.notesCountBadge.text()) + updateCount);
+    };
+
+    return Notes;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
deleted file mode 100644
index 0ea54faae1a078b3c22c38fc757e3cc7f0196f4e..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/notes.js.coffee
+++ /dev/null
@@ -1,694 +0,0 @@
-#= require autosave
-#= require autosize
-#= require dropzone
-#= require dropzone_input
-#= require gfm_auto_complete
-#= require jquery.atwho
-#= require task_list
-
-class @Notes
-  @interval: null
-
-  constructor: (notes_url, note_ids, last_fetched_at, view) ->
-    @notes_url = notes_url
-    @note_ids = note_ids
-    @last_fetched_at = last_fetched_at
-    @view = view
-    @noteable_url = document.URL
-    @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
-    @basePollingInterval = 15000
-    @maxPollingSteps = 4
-
-    @cleanBinding()
-    @addBinding()
-    @setPollingInterval()
-    @setupMainTargetNoteForm()
-    @initTaskList()
-
-  addBinding: ->
-    # add note to UI after creation
-    $(document).on "ajax:success", ".js-main-target-form", @addNote
-    $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
-
-    # catch note ajax errors
-    $(document).on "ajax:error", ".js-main-target-form", @addNoteError
-
-    # change note in UI after update
-    $(document).on "ajax:success", "form.edit-note", @updateNote
-
-    # Edit note link
-    $(document).on "click", ".js-note-edit", @showEditForm
-    $(document).on "click", ".note-edit-cancel", @cancelEdit
-
-    # Reopen and close actions for Issue/MR combined with note form submit
-    $(document).on "click", ".js-comment-button", @updateCloseButton
-    $(document).on "keyup input", ".js-note-text", @updateTargetButtons
-
-    # remove a note (in general)
-    $(document).on "click", ".js-note-delete", @removeNote
-
-    # delete note attachment
-    $(document).on "click", ".js-note-attachment-delete", @removeAttachment
-
-    # reset main target form after submit
-    $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
-    $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
-
-    # reset main target form when clicking discard
-    $(document).on "click", ".js-note-discard", @resetMainTargetForm
-
-    # update the file name when an attachment is selected
-    $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
-
-    # reply to diff/discussion notes
-    $(document).on "click", ".js-discussion-reply-button", @replyToDiscussionNote
-
-    # add diff note
-    $(document).on "click", ".js-add-diff-note-button", @addDiffNote
-
-    # hide diff note form
-    $(document).on "click", ".js-close-discussion-note-form", @cancelDiscussionForm
-
-    # fetch notes when tab becomes visible
-    $(document).on "visibilitychange", @visibilityChange
-
-    # when issue status changes, we need to refresh data
-    $(document).on "issuable:change", @refresh
-
-    # when a key is clicked on the notes
-    $(document).on "keydown", ".js-note-text", @keydownNoteText
-
-  cleanBinding: ->
-    $(document).off "ajax:success", ".js-main-target-form"
-    $(document).off "ajax:success", ".js-discussion-note-form"
-    $(document).off "ajax:success", "form.edit-note"
-    $(document).off "click", ".js-note-edit"
-    $(document).off "click", ".note-edit-cancel"
-    $(document).off "click", ".js-note-delete"
-    $(document).off "click", ".js-note-attachment-delete"
-    $(document).off "ajax:complete", ".js-main-target-form"
-    $(document).off "ajax:success", ".js-main-target-form"
-    $(document).off "click", ".js-discussion-reply-button"
-    $(document).off "click", ".js-add-diff-note-button"
-    $(document).off "visibilitychange"
-    $(document).off "keyup", ".js-note-text"
-    $(document).off "click", ".js-note-target-reopen"
-    $(document).off "click", ".js-note-target-close"
-    $(document).off "click", ".js-note-discard"
-    $(document).off "keydown", ".js-note-text"
-
-    $('.note .js-task-list-container').taskList('disable')
-    $(document).off 'tasklist:changed', '.note .js-task-list-container'
-
-  keydownNoteText: (e) =>
-    return if isMetaKey e
-
-    $textarea = $(e.target)
-
-    # Edit previous note when UP arrow is hit
-    switch e.which
-      when 38
-        return unless $textarea.val() is ''
-
-        myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
-        if myLastNote.length
-          myLastNoteEditBtn = myLastNote.find('.js-note-edit')
-          myLastNoteEditBtn.trigger('click', [true, myLastNote])
-
-      # Cancel creating diff note or editing any note when ESCAPE is hit
-      when 27
-        discussionNoteForm = $textarea.closest('.js-discussion-note-form')
-        if discussionNoteForm.length
-          if $textarea.val() isnt ''
-            return unless confirm('Are you sure you want to cancel creating this comment?')
-
-          @removeDiscussionNoteForm(discussionNoteForm)
-          return
-
-        editNote = $textarea.closest('.note')
-        if editNote.length
-          originalText = $textarea.closest('form').data('original-note')
-          newText = $textarea.val()
-          if originalText isnt newText
-            return unless confirm('Are you sure you want to cancel editing this comment?')
-
-          @removeNoteEditForm(editNote)
-
-
-  isMetaKey = (e) ->
-    (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
-
-  initRefresh: ->
-    clearInterval(Notes.interval)
-    Notes.interval = setInterval =>
-      @refresh()
-    , @pollingInterval
-
-  refresh: =>
-    if not document.hidden and document.URL.indexOf(@noteable_url) is 0
-      @getContent()
-
-  getContent: ->
-    return if @refreshing
-
-    @refreshing = true
-
-    $.ajax
-      url: @notes_url
-      data: "last_fetched_at=" + @last_fetched_at
-      dataType: "json"
-      success: (data) =>
-        notes = data.notes
-        @last_fetched_at = data.last_fetched_at
-        @setPollingInterval(data.notes.length)
-        $.each notes, (i, note) =>
-          if note.discussion_with_diff_html?
-            @renderDiscussionNote(note)
-          else
-            @renderNote(note)
-    .always () =>
-      @refreshing = false
-
-  ###
-  Increase @pollingInterval up to 120 seconds on every function call,
-  if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
-  will reset to @basePollingInterval.
-
-  Note: this function is used to gradually increase the polling interval
-  if there aren't new notes coming from the server
-  ###
-  setPollingInterval: (shouldReset = true) ->
-    nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
-    if shouldReset
-      @pollingInterval = @basePollingInterval
-    else if @pollingInterval < nthInterval
-      @pollingInterval *= 2
-
-    @initRefresh()
-
-  ###
-  Render note in main comments area.
-
-  Note: for rendering inline notes use renderDiscussionNote
-  ###
-  renderNote: (note) ->
-    unless note.valid
-      if note.award
-        new Flash('You have already awarded this emoji!', 'alert')
-      return
-
-    if note.award
-      votesBlock = $('.js-awards-block').eq 0
-      gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
-      gl.awardsHandler.scrollToAwards()
-
-    # render note if it not present in loaded list
-    # or skip if rendered
-    else if @isNewNote(note)
-      @note_ids.push(note.id)
-
-      $notesList = $('ul.main-notes-list')
-
-      $notesList
-        .append(note.html)
-        .syntaxHighlight()
-
-      # Update datetime format on the recent note
-      gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
-
-      @initTaskList()
-      @updateNotesCount(1)
-
-
-  ###
-  Check if note does not exists on page
-  ###
-  isNewNote: (note) ->
-    $.inArray(note.id, @note_ids) == -1
-
-  isParallelView: ->
-    @view == 'parallel'
-
-  ###
-  Render note in discussion area.
-
-  Note: for rendering inline notes use renderDiscussionNote
-  ###
-  renderDiscussionNote: (note) ->
-    return unless @isNewNote(note)
-
-    @note_ids.push(note.id)
-    form = $("#new-discussion-note-form-#{note.discussion_id}")
-    if note.original_discussion_id? and form.length is 0
-      form = $("#new-discussion-note-form-#{note.original_discussion_id}")
-    row = form.closest("tr")
-    note_html = $(note.html)
-    note_html.syntaxHighlight()
-
-    # is this the first note of discussion?
-    discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
-    if note.original_discussion_id? and discussionContainer.length is 0
-      discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
-    if discussionContainer.length is 0
-      # insert the note and the reply button after the temp row
-      row.after note.discussion_html
-
-      # remove the note (will be added again below)
-      row.next().find(".note").remove()
-
-      # Before that, the container didn't exist
-      discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
-
-      # Add note to 'Changes' page discussions
-      discussionContainer.append note_html
-
-      # Init discussion on 'Discussion' page if it is merge request page
-      if $('body').attr('data-page').indexOf('projects:merge_request') is 0
-        $('ul.main-notes-list')
-          .append(note.discussion_with_diff_html)
-          .syntaxHighlight()
-    else
-      # append new note to all matching discussions
-      discussionContainer.append note_html
-
-    gl.utils.localTimeAgo($('.js-timeago', note_html), false)
-
-    @updateNotesCount(1)
-
-  ###
-  Called in response the main target form has been successfully submitted.
-
-  Removes any errors.
-  Resets text and preview.
-  Resets buttons.
-  ###
-  resetMainTargetForm: (e) =>
-    form = $(".js-main-target-form")
-
-    # remove validation errors
-    form.find(".js-errors").remove()
-
-    # reset text and preview
-    form.find(".js-md-write-button").click()
-    form.find(".js-note-text").val("").trigger "input"
-
-    form.find(".js-note-text").data("autosave").reset()
-
-    @updateTargetButtons(e)
-
-  reenableTargetFormSubmitButton: ->
-    form = $(".js-main-target-form")
-
-    form.find(".js-note-text").trigger "input"
-
-  ###
-  Shows the main form and does some setup on it.
-
-  Sets some hidden fields in the form.
-  ###
-  setupMainTargetNoteForm: ->
-    # find the form
-    form = $(".js-new-note-form")
-
-    # Set a global clone of the form for later cloning
-    @formClone = form.clone()
-
-    # show the form
-    @setupNoteForm(form)
-
-    # fix classes
-    form.removeClass "js-new-note-form"
-    form.addClass "js-main-target-form"
-
-    form.find("#note_line_code").remove()
-    form.find("#note_position").remove()
-    form.find("#note_type").remove()
-
-    @parentTimeline = form.parents('.timeline')
-
-  ###
-  General note form setup.
-
-  deactivates the submit button when text is empty
-  hides the preview button when text is empty
-  setup GFM auto complete
-  show the form
-  ###
-  setupNoteForm: (form) ->
-    new GLForm form
-
-    textarea = form.find(".js-note-text")
-
-    new Autosave textarea, [
-      "Note"
-      form.find("#note_noteable_type").val()
-      form.find("#note_noteable_id").val()
-      form.find("#note_commit_id").val()
-      form.find("#note_type").val()
-      form.find("#note_line_code").val()
-      form.find("#note_position").val()
-    ]
-
-  ###
-  Called in response to the new note form being submitted
-
-  Adds new note to list.
-  ###
-  addNote: (xhr, note, status) =>
-    @renderNote(note)
-
-  addNoteError: (xhr, note, status) =>
-    new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline)
-
-  ###
-  Called in response to the new note form being submitted
-
-  Adds new note to list.
-  ###
-  addDiscussionNote: (xhr, note, status) =>
-    @renderDiscussionNote(note)
-
-    # cleanup after successfully creating a diff/discussion note
-    @removeDiscussionNoteForm($(xhr.target))
-
-  ###
-  Called in response to the edit note form being submitted
-
-  Updates the current note field.
-  ###
-  updateNote: (_xhr, note, _status) =>
-    # Convert returned HTML to a jQuery object so we can modify it further
-    $html = $(note.html)
-
-    gl.utils.localTimeAgo($('.js-timeago', $html))
-
-    $html.syntaxHighlight()
-    $html.find('.js-task-list-container').taskList('enable')
-
-    # Find the note's `li` element by ID and replace it with the updated HTML
-    $note_li = $('.note-row-' + note.id)
-    $note_li.replaceWith($html)
-
-  ###
-  Called in response to clicking the edit note link
-
-  Replaces the note text with the note edit form
-  Adds a data attribute to the form with the original content of the note for cancellations
-  ###
-  showEditForm: (e, scrollTo, myLastNote) ->
-    e.preventDefault()
-    note = $(this).closest(".note")
-    note.addClass "is-editting"
-    form = note.find(".note-edit-form")
-
-    form.addClass('current-note-edit-form')
-
-    # Show the attachment delete link
-    note.find(".js-note-attachment-delete").show()
-
-    done = ($noteText) ->
-      # Neat little trick to put the cursor at the end
-      noteTextVal = $noteText.val()
-      # Store the original note text in a data attribute to retrieve if a user cancels edit.
-      form.find('form.edit-note').data 'original-note', noteTextVal
-      $noteText.val('').val(noteTextVal);
-
-    new GLForm form
-    if scrollTo? and myLastNote?
-      # scroll to the bottom
-      # so the open of the last element doesn't make a jump
-      $('html, body').scrollTop($(document).height());
-      $('html, body').animate({
-        scrollTop: myLastNote.offset().top - 150
-      }, 500, ->
-        $noteText = form.find(".js-note-text")
-        $noteText.focus()
-        done($noteText)
-      );
-    else
-      $noteText = form.find('.js-note-text')
-      $noteText.focus()
-      done($noteText)
-
-  ###
-  Called in response to clicking the edit note link
-
-  Hides edit form and restores the original note text to the editor textarea.
-  ###
-  cancelEdit: (e) =>
-    e.preventDefault()
-    note = $(e.target).closest('.note')
-    @removeNoteEditForm(note)
-
-  removeNoteEditForm: (note) ->
-    form = note.find(".current-note-edit-form")
-    note.removeClass "is-editting"
-    form.removeClass("current-note-edit-form")
-    # Replace markdown textarea text with original note text.
-    form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'))
-
-  ###
-  Called in response to deleting a note of any kind.
-
-  Removes the actual note from view.
-  Removes the whole discussion if the last note is being removed.
-  ###
-  removeNote: (e) =>
-    noteId = $(e.currentTarget)
-               .closest(".note")
-               .attr("id")
-
-    # A same note appears in the "Discussion" and in the "Changes" tab, we have
-    # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
-    # where $("#noteId") would return only one.
-    $(".note[id='#{noteId}']").each (i, el) =>
-      note  = $(el)
-      notes = note.closest(".notes")
-
-      # check if this is the last note for this line
-      if notes.find(".note").length is 1
-
-        # "Discussions" tab
-        notes.closest(".timeline-entry").remove()
-
-        # "Changes" tab / commit view
-        notes.closest("tr").remove()
-
-      note.remove()
-
-    # Decrement the "Discussions" counter only once
-    @updateNotesCount(-1)
-
-  ###
-  Called in response to clicking the delete attachment link
-
-  Removes the attachment wrapper view, including image tag if it exists
-  Resets the note editing form
-  ###
-  removeAttachment: ->
-    note = $(this).closest(".note")
-    note.find(".note-attachment").remove()
-    note.find(".note-body > .note-text").show()
-    note.find(".note-header").show()
-    note.find(".current-note-edit-form").remove()
-
-  ###
-  Called when clicking on the "reply" button for a diff line.
-
-  Shows the note form below the notes.
-  ###
-  replyToDiscussionNote: (e) =>
-    form = @formClone.clone()
-    replyLink = $(e.target).closest(".js-discussion-reply-button")
-    replyLink.hide()
-
-    # insert the form after the button
-    replyLink.after form
-
-    # show the form
-    @setupDiscussionNoteForm(replyLink, form)
-
-  ###
-  Shows the diff or discussion form and does some setup on it.
-
-  Sets some hidden fields in the form.
-
-  Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
-  and "noteableId" data attributes set.
-  ###
-  setupDiscussionNoteForm: (dataHolder, form) =>
-    # setup note target
-    form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
-    form.attr "data-line-code", dataHolder.data("lineCode")
-    form.find("#note_type").val dataHolder.data("noteType")
-    form.find("#line_type").val dataHolder.data("lineType")
-    form.find("#note_commit_id").val dataHolder.data("commitId")
-    form.find("#note_line_code").val dataHolder.data("lineCode")
-    form.find("#note_position").val dataHolder.attr("data-position")
-    form.find("#note_noteable_type").val dataHolder.data("noteableType")
-    form.find("#note_noteable_id").val dataHolder.data("noteableId")
-    form.find('.js-note-discard')
-        .show()
-        .removeClass('js-note-discard')
-        .addClass('js-close-discussion-note-form')
-        .text(form.find('.js-close-discussion-note-form').data('cancel-text'))
-    @setupNoteForm form
-    form.find(".js-note-text").focus()
-    form
-      .removeClass('js-main-target-form')
-      .addClass("discussion-form js-discussion-note-form")
-
-  ###
-  Called when clicking on the "add a comment" button on the side of a diff line.
-
-  Inserts a temporary row for the form below the line.
-  Sets up the form and shows it.
-  ###
-  addDiffNote: (e) =>
-    e.preventDefault()
-    $link = $(e.currentTarget)
-    row = $link.closest("tr")
-    nextRow = row.next()
-    hasNotes = nextRow.is(".notes_holder")
-    addForm = false
-    targetContent = ".notes_content"
-    rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"></td></tr>"
-
-    # In parallel view, look inside the correct left/right pane
-    if @isParallelView()
-      lineType = $link.data("lineType")
-      targetContent += "." + lineType
-      rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
-
-    if hasNotes
-      notesContent = nextRow.find(targetContent)
-      if notesContent.length
-        replyButton = notesContent.find(".js-discussion-reply-button:visible")
-        if replyButton.length
-          e.target = replyButton[0]
-          $.proxy(@replyToDiscussionNote, replyButton[0], e).call()
-        else
-          # In parallel view, the form may not be present in one of the panes
-          noteForm = notesContent.find(".js-discussion-note-form")
-          if noteForm.length == 0
-            addForm = true
-    else
-      # add a notes row and insert the form
-      row.after rowCssToAdd
-      addForm = true
-
-    if addForm
-      newForm = @formClone.clone()
-      newForm.appendTo row.next().find(targetContent)
-
-      # show the form
-      @setupDiscussionNoteForm $link, newForm
-
-  ###
-  Called in response to "cancel" on a diff note form.
-
-  Shows the reply button again.
-  Removes the form and if necessary it's temporary row.
-  ###
-  removeDiscussionNoteForm: (form)->
-    row = form.closest("tr")
-
-    glForm = form.data 'gl-form'
-    glForm.destroy()
-
-    form.find(".js-note-text").data("autosave").reset()
-
-    # show the reply button (will only work for replies)
-    form.prev(".js-discussion-reply-button").show()
-    if row.is(".js-temp-notes-holder")
-      # remove temporary row for diff lines
-      row.remove()
-    else
-      # only remove the form
-      form.remove()
-
-  cancelDiscussionForm: (e) =>
-    e.preventDefault()
-    form = $(e.target).closest(".js-discussion-note-form")
-    @removeDiscussionNoteForm(form)
-
-  ###
-  Called after an attachment file has been selected.
-
-  Updates the file name for the selected attachment.
-  ###
-  updateFormAttachment: ->
-    form = $(this).closest("form")
-
-    # get only the basename
-    filename = $(this).val().replace(/^.*[\\\/]/, "")
-    form.find(".js-attachment-filename").text filename
-
-  ###
-  Called when the tab visibility changes
-  ###
-  visibilityChange: =>
-    @refresh()
-
-  updateCloseButton: (e) =>
-    textarea = $(e.target)
-    form = textarea.parents('form')
-    closebtn = form.find('.js-note-target-close')
-    closebtn.text(closebtn.data('original-text'))
-
-  updateTargetButtons: (e) =>
-    textarea = $(e.target)
-    form = textarea.parents('form')
-    reopenbtn = form.find('.js-note-target-reopen')
-    closebtn = form.find('.js-note-target-close')
-    discardbtn = form.find('.js-note-discard')
-
-    if textarea.val().trim().length > 0
-      reopentext = reopenbtn.data('alternative-text')
-      closetext = closebtn.data('alternative-text')
-
-      if reopenbtn.text() isnt reopentext
-        reopenbtn.text(reopentext)
-
-      if closebtn.text() isnt closetext
-        closebtn.text(closetext)
-
-      if reopenbtn.is(':not(.btn-comment-and-reopen)')
-        reopenbtn.addClass('btn-comment-and-reopen')
-
-      if closebtn.is(':not(.btn-comment-and-close)')
-        closebtn.addClass('btn-comment-and-close')
-
-      if discardbtn.is(':hidden')
-        discardbtn.show()
-    else
-      reopentext = reopenbtn.data('original-text')
-      closetext = closebtn.data('original-text')
-
-      if reopenbtn.text() isnt reopentext
-        reopenbtn.text(reopentext)
-
-      if closebtn.text() isnt closetext
-        closebtn.text(closetext)
-
-      if reopenbtn.is('.btn-comment-and-reopen')
-        reopenbtn.removeClass('btn-comment-and-reopen')
-
-      if closebtn.is('.btn-comment-and-close')
-        closebtn.removeClass('btn-comment-and-close')
-
-      if discardbtn.is(':visible')
-        discardbtn.hide()
-
-  initTaskList: ->
-    @enableTaskList()
-    $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList
-
-  enableTaskList: ->
-    $('.note .js-task-list-container').taskList('enable')
-
-  updateTaskList: ->
-    $('form', this).submit()
-
-  updateNotesCount: (updateCount) ->
-    @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
new file mode 100644
index 0000000000000000000000000000000000000000..a41e9d3fabecd223247fb7b6b57d04d30642d797
--- /dev/null
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -0,0 +1,30 @@
+(function() {
+  this.NotificationsDropdown = (function() {
+    function NotificationsDropdown() {
+      $(document).off('click', '.update-notification').on('click', '.update-notification', function(e) {
+        var form, label, notificationLevel;
+        e.preventDefault();
+        if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
+          return;
+        }
+        notificationLevel = $(this).data('notification-level');
+        label = $(this).data('notification-title');
+        form = $(this).parents('.notification-form:first');
+        form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
+        form.find('#notification_setting_level').val(notificationLevel);
+        return form.submit();
+      });
+      $(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) {
+        if (data.saved) {
+          return $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html);
+        } else {
+          return new Flash('Failed to save new settings', 'alert');
+        }
+      });
+    }
+
+    return NotificationsDropdown;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee
deleted file mode 100644
index 0bbd082c156c3d5ac6a3ddb3b4ecc153d6c14cfa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/notifications_dropdown.js.coffee
+++ /dev/null
@@ -1,25 +0,0 @@
-class @NotificationsDropdown
-  constructor: ->
-    $(document)
-      .off 'click', '.update-notification'
-      .on 'click', '.update-notification', (e) ->
-        e.preventDefault()
-
-        return if $(this).is('.is-active') and $(this).data('notification-level') is 'custom'
-
-        notificationLevel = $(@).data 'notification-level'
-        label = $(@).data 'notification-title'
-        form = $(this).parents('.notification-form:first')
-        form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
-        form.find('#notification_setting_level').val(notificationLevel)
-        form.submit()
-
-    $(document)
-      .off 'ajax:success', '.notification-form'
-      .on 'ajax:success', '.notification-form', (e, data) ->
-        if data.saved
-          $(e.currentTarget)
-            .closest('.notification-dropdown')
-            .replaceWith(data.html)
-        else
-          new Flash('Failed to save new settings', 'alert')
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b2ef17ef6bcb0f781c293b8c0e545fa5918839b
--- /dev/null
+++ b/app/assets/javascripts/notifications_form.js
@@ -0,0 +1,58 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.NotificationsForm = (function() {
+    function NotificationsForm() {
+      this.toggleCheckbox = bind(this.toggleCheckbox, this);
+      this.removeEventListeners();
+      this.initEventListeners();
+    }
+
+    NotificationsForm.prototype.removeEventListeners = function() {
+      return $(document).off('change', '.js-custom-notification-event');
+    };
+
+    NotificationsForm.prototype.initEventListeners = function() {
+      return $(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
+    };
+
+    NotificationsForm.prototype.toggleCheckbox = function(e) {
+      var $checkbox, $parent;
+      $checkbox = $(e.currentTarget);
+      $parent = $checkbox.closest('.checkbox');
+      return this.saveEvent($checkbox, $parent);
+    };
+
+    NotificationsForm.prototype.showCheckboxLoadingSpinner = function($parent) {
+      return $parent.addClass('is-loading').find('.custom-notification-event-loading').removeClass('fa-check').addClass('fa-spin fa-spinner').removeClass('is-done');
+    };
+
+    NotificationsForm.prototype.saveEvent = function($checkbox, $parent) {
+      var form;
+      form = $parent.parents('form:first');
+      return $.ajax({
+        url: form.attr('action'),
+        method: form.attr('method'),
+        dataType: 'json',
+        data: form.serialize(),
+        beforeSend: (function(_this) {
+          return function() {
+            return _this.showCheckboxLoadingSpinner($parent);
+          };
+        })(this)
+      }).done(function(data) {
+        $checkbox.enable();
+        if (data.saved) {
+          $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+          return setTimeout(function() {
+            return $parent.removeClass('is-loading').find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+          }, 2000);
+        }
+      });
+    };
+
+    return NotificationsForm;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/notifications_form.js.coffee b/app/assets/javascripts/notifications_form.js.coffee
deleted file mode 100644
index 3432428702a868cc1f03aa4304cac98f01bf892b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/notifications_form.js.coffee
+++ /dev/null
@@ -1,49 +0,0 @@
-class @NotificationsForm
-  constructor: ->
-    @removeEventListeners()
-    @initEventListeners()
-
-  removeEventListeners: ->
-    $(document).off 'change', '.js-custom-notification-event'
-
-  initEventListeners: ->
-    $(document).on 'change', '.js-custom-notification-event', @toggleCheckbox
-
-  toggleCheckbox: (e) =>
-    $checkbox = $(e.currentTarget)
-    $parent = $checkbox.closest('.checkbox')
-    @saveEvent($checkbox, $parent)
-
-  showCheckboxLoadingSpinner: ($parent) ->
-    $parent
-      .addClass 'is-loading'
-      .find '.custom-notification-event-loading'
-      .removeClass 'fa-check'
-      .addClass 'fa-spin fa-spinner'
-      .removeClass 'is-done'
-
-  saveEvent: ($checkbox, $parent) ->
-    form = $parent.parents('form:first')
-
-    $.ajax(
-      url: form.attr('action')
-      method: form.attr('method')
-      dataType: 'json'
-      data: form.serialize()
-
-      beforeSend: =>
-        @showCheckboxLoadingSpinner($parent)
-    ).done (data) ->
-      $checkbox.enable()
-
-      if data.saved
-        $parent
-          .find '.custom-notification-event-loading'
-          .toggleClass 'fa-spin fa-spinner fa-check is-done'
-
-        setTimeout(->
-          $parent
-            .removeClass 'is-loading'
-            .find '.custom-notification-event-loading'
-            .toggleClass 'fa-spin fa-spinner fa-check is-done'
-        , 2000)
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
new file mode 100644
index 0000000000000000000000000000000000000000..b81ed50cb48042c2e86e40648758498c06e4d292
--- /dev/null
+++ b/app/assets/javascripts/pager.js
@@ -0,0 +1,63 @@
+(function() {
+  this.Pager = {
+    init: function(limit, preload, disable, callback) {
+      this.limit = limit != null ? limit : 0;
+      this.disable = disable != null ? disable : false;
+      this.callback = callback != null ? callback : $.noop;
+      this.loading = $('.loading').first();
+      if (preload) {
+        this.offset = 0;
+        this.getOld();
+      } else {
+        this.offset = this.limit;
+      }
+      return this.initLoadMore();
+    },
+    getOld: function() {
+      this.loading.show();
+      return $.ajax({
+        type: "GET",
+        url: $(".content_list").data('href') || location.href,
+        data: "limit=" + this.limit + "&offset=" + this.offset,
+        complete: (function(_this) {
+          return function() {
+            return _this.loading.hide();
+          };
+        })(this),
+        success: function(data) {
+          Pager.append(data.count, data.html);
+          return Pager.callback();
+        },
+        dataType: "json"
+      });
+    },
+    append: function(count, html) {
+      $(".content_list").append(html);
+      if (count > 0) {
+        return this.offset += count;
+      } else {
+        return this.disable = true;
+      }
+    },
+    initLoadMore: function() {
+      $(document).unbind('scroll');
+      return $(document).endlessScroll({
+        bottomPixels: 400,
+        fireDelay: 1000,
+        fireOnce: true,
+        ceaseFire: function() {
+          return Pager.disable;
+        },
+        callback: (function(_this) {
+          return function(i) {
+            if (!_this.loading.is(':visible')) {
+              _this.loading.show();
+              return Pager.getOld();
+            }
+          };
+        })(this)
+      });
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
deleted file mode 100644
index 8049c5c30e2a2dcf275519be70bdfc68c9f66ae8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/pager.js.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-@Pager =
-  init: (@limit = 0, preload, @disable = false, @callback = $.noop) ->
-    @loading = $('.loading').first()
-
-    if preload
-      @offset = 0
-      @getOld()
-    else
-      @offset = @limit
-    @initLoadMore()
-
-  getOld: ->
-    @loading.show()
-    $.ajax
-      type: "GET"
-      url: $(".content_list").data('href') || location.href
-      data: "limit=" + @limit + "&offset=" + @offset
-      complete: =>
-        @loading.hide()
-      success: (data) ->
-        Pager.append(data.count, data.html)
-        Pager.callback()
-      dataType: "json"
-
-  append: (count, html) ->
-    $(".content_list").append html
-    if count > 0
-      @offset += count
-    else
-      @disable = true
-
-  initLoadMore: ->
-    $(document).unbind('scroll')
-    $(document).endlessScroll
-      bottomPixels: 400
-      fireDelay: 1000
-      fireOnce: true
-      ceaseFire: ->
-        Pager.disable
-
-      callback: (i) =>
-        unless @loading.is(':visible')
-          @loading.show()
-          Pager.getOld()
diff --git a/app/assets/javascripts/profile/application.js.coffee b/app/assets/javascripts/profile/application.js.coffee
deleted file mode 100644
index 91cacfece463abccaffbc9dc2bc27074e6719436..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/profile/application.js.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-#= require_tree .
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3eea316f67c7aca652b9446ff597734c5243d35
--- /dev/null
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -0,0 +1,169 @@
+(function() {
+  var GitLabCrop,
+    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  GitLabCrop = (function() {
+    var FILENAMEREGEX;
+
+    FILENAMEREGEX = /^.*[\\\/]/;
+
+    function GitLabCrop(input, opts) {
+      var ref, ref1, ref2, ref3, ref4;
+      if (opts == null) {
+        opts = {};
+      }
+      this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this);
+      this.onModalHide = bind(this.onModalHide, this);
+      this.onModalShow = bind(this.onModalShow, this);
+      this.onPickImageClick = bind(this.onPickImageClick, this);
+      this.fileInput = $(input);
+      this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
+      this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
+      this.filename = this.getElement(this.filename);
+      this.previewImage = this.getElement(this.previewImage);
+      this.pickImageEl = this.getElement(this.pickImageEl);
+      this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
+      this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
+      this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
+      this.cropActionsBtn = this.modalCrop.find('[data-method]');
+      this.bindEvents();
+    }
+
+    GitLabCrop.prototype.getElement = function(selector) {
+      return $(selector, this.form);
+    };
+
+    GitLabCrop.prototype.bindEvents = function() {
+      var _this;
+      _this = this;
+      this.fileInput.on('change', function(e) {
+        return _this.onFileInputChange(e, this);
+      });
+      this.pickImageEl.on('click', this.onPickImageClick);
+      this.modalCrop.on('shown.bs.modal', this.onModalShow);
+      this.modalCrop.on('hidden.bs.modal', this.onModalHide);
+      this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
+      this.cropActionsBtn.on('click', function(e) {
+        var btn;
+        btn = this;
+        return _this.onActionBtnClick(btn);
+      });
+      return this.croppedImageBlob = null;
+    };
+
+    GitLabCrop.prototype.onPickImageClick = function() {
+      return this.fileInput.trigger('click');
+    };
+
+    GitLabCrop.prototype.onModalShow = function() {
+      var _this;
+      _this = this;
+      return this.modalCropImg.cropper({
+        viewMode: 1,
+        center: false,
+        aspectRatio: 1,
+        modal: true,
+        scalable: false,
+        rotatable: false,
+        zoomable: true,
+        dragMode: 'move',
+        guides: false,
+        zoomOnTouch: false,
+        zoomOnWheel: false,
+        cropBoxMovable: false,
+        cropBoxResizable: false,
+        toggleDragModeOnDblclick: false,
+        built: function() {
+          var $image, container, cropBoxHeight, cropBoxWidth;
+          $image = $(this);
+          container = $image.cropper('getContainerData');
+          cropBoxWidth = _this.cropBoxWidth;
+          cropBoxHeight = _this.cropBoxHeight;
+          return $image.cropper('setCropBoxData', {
+            width: cropBoxWidth,
+            height: cropBoxHeight,
+            left: (container.width - cropBoxWidth) / 2,
+            top: (container.height - cropBoxHeight) / 2
+          });
+        }
+      });
+    };
+
+    GitLabCrop.prototype.onModalHide = function() {
+      return this.modalCropImg.attr('src', '').cropper('destroy');
+    };
+
+    GitLabCrop.prototype.onUploadImageBtnClick = function(e) {
+      e.preventDefault();
+      this.setBlob();
+      this.setPreview();
+      this.modalCrop.modal('hide');
+      return this.fileInput.val('');
+    };
+
+    GitLabCrop.prototype.onActionBtnClick = function(btn) {
+      var data, result;
+      data = $(btn).data();
+      if (this.modalCropImg.data('cropper') && data.method) {
+        return result = this.modalCropImg.cropper(data.method, data.option);
+      }
+    };
+
+    GitLabCrop.prototype.onFileInputChange = function(e, input) {
+      return this.readFile(input);
+    };
+
+    GitLabCrop.prototype.readFile = function(input) {
+      var _this, reader;
+      _this = this;
+      reader = new FileReader;
+      reader.onload = function() {
+        _this.modalCropImg.attr('src', reader.result);
+        return _this.modalCrop.modal('show');
+      };
+      return reader.readAsDataURL(input.files[0]);
+    };
+
+    GitLabCrop.prototype.dataURLtoBlob = function(dataURL) {
+      var array, binary, i, k, len, v;
+      binary = atob(dataURL.split(',')[1]);
+      array = [];
+      for (k = i = 0, len = binary.length; i < len; k = ++i) {
+        v = binary[k];
+        array.push(binary.charCodeAt(k));
+      }
+      return new Blob([new Uint8Array(array)], {
+        type: 'image/png'
+      });
+    };
+
+    GitLabCrop.prototype.setPreview = function() {
+      var filename;
+      this.previewImage.attr('src', this.dataURL);
+      filename = this.fileInput.val().replace(FILENAMEREGEX, '');
+      return this.filename.text(filename);
+    };
+
+    GitLabCrop.prototype.setBlob = function() {
+      this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', {
+        width: 200,
+        height: 200
+      }).toDataURL('image/png');
+      return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL);
+    };
+
+    GitLabCrop.prototype.getBlob = function() {
+      return this.croppedImageBlob;
+    };
+
+    return GitLabCrop;
+
+  })();
+
+  $.fn.glCrop = function(opts) {
+    return this.each(function() {
+      return $(this).data('glcrop', new GitLabCrop(this, opts));
+    });
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/profile/gl_crop.js.coffee b/app/assets/javascripts/profile/gl_crop.js.coffee
deleted file mode 100644
index df9bfdfa6cc7c5988195d87af5d7b93ddc9e8220..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/profile/gl_crop.js.coffee
+++ /dev/null
@@ -1,152 +0,0 @@
-class GitLabCrop
-  # Matches everything but the file name
-  FILENAMEREGEX = /^.*[\\\/]/
-
-  constructor: (input, opts = {}) ->
-    @fileInput = $(input)
-
-    # We should rename to avoid spec to fail
-    # Form will submit the proper input filed with a file using FormData
-    @fileInput
-      .attr('name', "#{@fileInput.attr('name')}-trigger")
-      .attr('id', "#{@fileInput.attr('id')}-trigger")
-
-    # Set defaults
-    {
-      @exportWidth = 200
-      @exportHeight = 200
-      @cropBoxWidth = 200
-      @cropBoxHeight = 200
-      @form = @fileInput.parents('form')
-
-      # Required params
-      @filename
-      @previewImage
-      @modalCrop
-      @pickImageEl
-      @uploadImageBtn
-      @modalCropImg
-    } = opts
-
-    # Ensure needed elements are jquery objects
-    # If selector is provided we will convert them to a jQuery Object
-    @filename = @getElement(@filename)
-    @previewImage = @getElement(@previewImage)
-    @pickImageEl = @getElement(@pickImageEl)
-
-    # Modal elements usually are outside the @form element
-    @modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop
-    @uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn
-    @modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg
-
-    @cropActionsBtn = @modalCrop.find('[data-method]')
-
-    @bindEvents()
-
-  getElement: (selector) ->
-    $(selector, @form)
-
-  bindEvents: ->
-    _this = @
-    @fileInput.on 'change', (e) ->
-      _this.onFileInputChange(e, @)
-
-    @pickImageEl.on 'click', @onPickImageClick
-    @modalCrop.on 'shown.bs.modal', @onModalShow
-    @modalCrop.on 'hidden.bs.modal', @onModalHide
-    @uploadImageBtn.on 'click', @onUploadImageBtnClick
-    @cropActionsBtn.on 'click', (e) ->
-      btn = @
-      _this.onActionBtnClick(btn)
-    @croppedImageBlob = null
-
-  onPickImageClick: =>
-    @fileInput.trigger('click')
-
-  onModalShow: =>
-    _this = @
-    @modalCropImg.cropper(
-      viewMode: 1
-      center: false
-      aspectRatio: 1
-      modal: true
-      scalable: false
-      rotatable: false
-      zoomable: true
-      dragMode: 'move'
-      guides: false
-      zoomOnTouch: false
-      zoomOnWheel: false
-      cropBoxMovable: false
-      cropBoxResizable: false
-      toggleDragModeOnDblclick: false
-      built: ->
-        $image = $(@)
-        container = $image.cropper 'getContainerData'
-        cropBoxWidth = _this.cropBoxWidth;
-        cropBoxHeight = _this.cropBoxHeight;
-
-        $image.cropper('setCropBoxData',
-          width: cropBoxWidth,
-          height: cropBoxHeight,
-          left: (container.width - cropBoxWidth) / 2,
-          top: (container.height - cropBoxHeight) / 2
-        )
-    )
-
-
-  onModalHide: =>
-    @modalCropImg
-      .attr('src', '') # Remove attached image
-      .cropper('destroy') # Destroy cropper instance
-
-  onUploadImageBtnClick: (e) =>
-    e.preventDefault()
-    @setBlob()
-    @setPreview()
-    @modalCrop.modal('hide')
-    @fileInput.val('')
-
-  onActionBtnClick: (btn) ->
-    data = $(btn).data()
-
-    if @modalCropImg.data('cropper') && data.method
-      result = @modalCropImg.cropper data.method, data.option
-
-  onFileInputChange: (e, input) ->
-    @readFile(input)
-
-  readFile: (input) ->
-    _this = @
-    reader = new FileReader
-    reader.onload = ->
-      _this.modalCropImg.attr('src', reader.result)
-      _this.modalCrop.modal('show')
-
-    reader.readAsDataURL(input.files[0])
-
-  dataURLtoBlob: (dataURL) ->
-    binary = atob(dataURL.split(',')[1])
-    array = []
-    for v, k in  binary
-      array.push(binary.charCodeAt(k))
-    new Blob([new Uint8Array(array)], type: 'image/png')
-
-  setPreview: ->
-    @previewImage.attr('src', @dataURL)
-    filename = @fileInput.val().replace(FILENAMEREGEX, '')
-    @filename.text(filename)
-
-  setBlob: ->
-    @dataURL = @modalCropImg.cropper('getCroppedCanvas',
-        width: 200
-        height: 200
-      ).toDataURL('image/png')
-    @croppedImageBlob = @dataURLtoBlob(@dataURL)
-
-  getBlob: ->
-    @croppedImageBlob
-
-$.fn.glCrop = (opts) ->
-  return @.each ->
-    $(@).data('glcrop', new GitLabCrop(@, opts))
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
new file mode 100644
index 0000000000000000000000000000000000000000..ed1d87abafed50b7f42e28d07cbf227b456c8e39
--- /dev/null
+++ b/app/assets/javascripts/profile/profile.js
@@ -0,0 +1,102 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Profile = (function() {
+    function Profile(opts) {
+      var cropOpts, ref;
+      if (opts == null) {
+        opts = {};
+      }
+      this.onSubmitForm = bind(this.onSubmitForm, this);
+      this.form = (ref = opts.form) != null ? ref : $('.edit-user');
+      $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
+        return $(this).parents('form').submit();
+      });
+      $('#user_notification_email').on('change', function() {
+        return $(this).parents('form').submit();
+      });
+      $('.update-username').on('ajax:before', function() {
+        $('.loading-username').show();
+        $(this).find('.update-success').hide();
+        return $(this).find('.update-failed').hide();
+      });
+      $('.update-username').on('ajax:complete', function() {
+        $('.loading-username').hide();
+        $(this).find('.btn-save').enable();
+        return $(this).find('.loading-gif').hide();
+      });
+      $('.update-notifications').on('ajax:success', function(e, data) {
+        if (data.saved) {
+          return new Flash("Notification settings saved", "notice");
+        } else {
+          return new Flash("Failed to save new settings", "alert");
+        }
+      });
+      this.bindEvents();
+      cropOpts = {
+        filename: '.js-avatar-filename',
+        previewImage: '.avatar-image .avatar',
+        modalCrop: '.modal-profile-crop',
+        pickImageEl: '.js-choose-user-avatar-button',
+        uploadImageBtn: '.js-upload-user-avatar',
+        modalCropImg: '.modal-profile-crop-image'
+      };
+      this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
+    }
+
+    Profile.prototype.bindEvents = function() {
+      return this.form.on('submit', this.onSubmitForm);
+    };
+
+    Profile.prototype.onSubmitForm = function(e) {
+      e.preventDefault();
+      return this.saveForm();
+    };
+
+    Profile.prototype.saveForm = function() {
+      var avatarBlob, formData, self;
+      self = this;
+      formData = new FormData(this.form[0]);
+      avatarBlob = this.avatarGlCrop.getBlob();
+      if (avatarBlob != null) {
+        formData.append('user[avatar]', avatarBlob, 'avatar.png');
+      }
+      return $.ajax({
+        url: this.form.attr('action'),
+        type: this.form.attr('method'),
+        data: formData,
+        dataType: "json",
+        processData: false,
+        contentType: false,
+        success: function(response) {
+          return new Flash(response.message, 'notice');
+        },
+        error: function(jqXHR) {
+          return new Flash(jqXHR.responseJSON.message, 'alert');
+        },
+        complete: function() {
+          window.scrollTo(0, 0);
+          return self.form.find(':input[disabled]').enable();
+        }
+      });
+    };
+
+    return Profile;
+
+  })();
+
+  $(function() {
+    $(document).on('focusout.ssh_key', '#key_key', function() {
+      var $title, comment;
+      $title = $('#key_title');
+      comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
+      if (comment && comment.length > 1 && $title.val() === '') {
+        return $title.val(comment[1]).change();
+      }
+    });
+    if (gl.utils.getPagePath() === 'profiles') {
+      return new Profile();
+    }
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/profile/profile.js.coffee b/app/assets/javascripts/profile/profile.js.coffee
deleted file mode 100644
index f3b05f2c646f35c259b55696888ac233a066a86c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/profile/profile.js.coffee
+++ /dev/null
@@ -1,83 +0,0 @@
-class @Profile
-  constructor: (opts = {}) ->
-    {
-      @form = $('.edit-user')
-    } = opts
-
-    # Automatically submit the Preferences form when any of its radio buttons change
-    $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
-      $(this).parents('form').submit()
-
-    # Automatically submit email form when it changes
-    $('#user_notification_email').on 'change', ->
-      $(this).parents('form').submit()
-
-    $('.update-username').on 'ajax:before', ->
-      $('.loading-username').show()
-      $(this).find('.update-success').hide()
-      $(this).find('.update-failed').hide()
-
-    $('.update-username').on 'ajax:complete', ->
-      $('.loading-username').hide()
-      $(this).find('.btn-save').enable()
-      $(this).find('.loading-gif').hide()
-
-    $('.update-notifications').on 'ajax:success', (e, data) ->
-      if data.saved
-        new Flash("Notification settings saved", "notice")
-      else
-        new Flash("Failed to save new settings", "alert")
-
-    @bindEvents()
-
-    cropOpts =
-      filename: '.js-avatar-filename'
-      previewImage: '.avatar-image .avatar'
-      modalCrop: '.modal-profile-crop'
-      pickImageEl: '.js-choose-user-avatar-button'
-      uploadImageBtn: '.js-upload-user-avatar'
-      modalCropImg: '.modal-profile-crop-image'
-
-    @avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop'
-
-  bindEvents: ->
-    @form.on 'submit', @onSubmitForm
-
-  onSubmitForm: (e) =>
-    e.preventDefault()
-    @saveForm()
-
-  saveForm: ->
-    self = @
-    formData = new FormData(@form[0])
-
-    avatarBlob = @avatarGlCrop.getBlob()
-    formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
-
-    $.ajax
-      url: @form.attr('action')
-      type: @form.attr('method')
-      data: formData
-      dataType: "json"
-      processData: false
-      contentType: false
-      success: (response) ->
-        new Flash(response.message, 'notice')
-      error: (jqXHR) ->
-        new Flash(jqXHR.responseJSON.message, 'alert')
-      complete: ->
-        window.scrollTo 0, 0
-        # Enable submit button after requests ends
-        self.form.find(':input[disabled]').enable()
-
-$ ->
-  # Extract the SSH Key title from its comment
-  $(document).on 'focusout.ssh_key', '#key_key', ->
-    $title  = $('#key_title')
-    comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
-
-    if comment && comment.length > 1 && $title.val() == ''
-      $title.val(comment[1]).change()
-
-  if gl.utils.getPagePath() == 'profiles'
-    new Profile()
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..b95faadc8e72f17e7cdb90eab9203622916bee02
--- /dev/null
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -0,0 +1,7 @@
+
+/*= require_tree . */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
new file mode 100644
index 0000000000000000000000000000000000000000..b97f6d2271599bcf85fe9471c7d17bc0a95f934b
--- /dev/null
+++ b/app/assets/javascripts/project.js
@@ -0,0 +1,109 @@
+(function() {
+  this.Project = (function() {
+    function Project() {
+      $('ul.clone-options-dropdown a').click(function() {
+        var url;
+        if ($(this).hasClass('active')) {
+          return;
+        }
+        $('.active').not($(this)).removeClass('active');
+        $(this).toggleClass('active');
+        url = $("#project_clone").val();
+        $('#project_clone').val(url);
+        return $('.clone').text(url);
+      });
+      this.initRefSwitcher();
+      $('.project-refs-select').on('change', function() {
+        return $(this).parents('form').submit();
+      });
+      $('.hide-no-ssh-message').on('click', function(e) {
+        var path;
+        path = '/';
+        $.cookie('hide_no_ssh_message', 'false', {
+          path: path
+        });
+        $(this).parents('.no-ssh-key-message').remove();
+        return e.preventDefault();
+      });
+      $('.hide-no-password-message').on('click', function(e) {
+        var path;
+        path = '/';
+        $.cookie('hide_no_password_message', 'false', {
+          path: path
+        });
+        $(this).parents('.no-password-message').remove();
+        return e.preventDefault();
+      });
+      this.projectSelectDropdown();
+    }
+
+    Project.prototype.projectSelectDropdown = function() {
+      new ProjectSelect();
+      $('.project-item-select').on('click', (function(_this) {
+        return function(e) {
+          return _this.changeProject($(e.currentTarget).val());
+        };
+      })(this));
+      return $('.js-projects-dropdown-toggle').on('click', function(e) {
+        e.preventDefault();
+        return $('.js-projects-dropdown').select2('open');
+      });
+    };
+
+    Project.prototype.changeProject = function(url) {
+      return window.location = url;
+    };
+
+    Project.prototype.initRefSwitcher = function() {
+      return $('.js-project-refs-dropdown').each(function() {
+        var $dropdown, selected;
+        $dropdown = $(this);
+        selected = $dropdown.data('selected');
+        return $dropdown.glDropdown({
+          data: function(term, callback) {
+            return $.ajax({
+              url: $dropdown.data('refs-url'),
+              data: {
+                ref: $dropdown.data('ref')
+              }
+            }).done(function(refs) {
+              return callback(refs);
+            });
+          },
+          selectable: true,
+          filterable: true,
+          filterByText: true,
+          fieldName: 'ref',
+          renderRow: function(ref) {
+            var link;
+            if (ref.header != null) {
+              return $('<li />').addClass('dropdown-header').text(ref.header);
+            } else {
+              link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
+              return $('<li />').append(link);
+            }
+          },
+          id: function(obj, $el) {
+            return $el.attr('data-ref');
+          },
+          toggleLabel: function(obj, $el) {
+            return $el.text().trim();
+          },
+          clicked: function(selected, $el, e) {
+            e.preventDefault()
+            if ($('input[name="ref"]').length) {
+              var $form = $dropdown.closest('form'),
+                  action = $form.attr('action'),
+                  divider = action.indexOf('?') < 0 ? '?' : '&';
+              Turbolinks.visit(action + '' + divider + '' + $form.serialize());
+            }
+          }
+        });
+      });
+    };
+
+    return Project;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
deleted file mode 100644
index 2a8e8f2552b2c0653c088bfd4e006406b9565ba2..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project.js.coffee
+++ /dev/null
@@ -1,97 +0,0 @@
-class @Project
-  constructor: ->
-    # Git protocol switcher
-    $('ul.clone-options-dropdown a').click ->
-      return if $(@).hasClass('active')
-
-
-      # Remove the active class for all buttons (ssh, http, kerberos if shown)
-      $('.active').not($(@)).removeClass('active');
-      # Add the active class for the clicked button
-      $(@).toggleClass('active')
-
-      url = $("#project_clone").val()
-
-      # Update the input field
-      $('#project_clone').val(url)
-
-      # Update the command line instructions
-      $('.clone').text(url)
-
-    # Ref switcher
-    @initRefSwitcher()
-    $('.project-refs-select').on 'change', ->
-      $(@).parents('form').submit()
-
-    $('.hide-no-ssh-message').on 'click', (e) ->
-      path = '/'
-      $.cookie('hide_no_ssh_message', 'false', { path: path })
-      $(@).parents('.no-ssh-key-message').remove()
-      e.preventDefault()
-
-    $('.hide-no-password-message').on 'click', (e) ->
-      path = '/'
-      $.cookie('hide_no_password_message', 'false', { path: path })
-      $(@).parents('.no-password-message').remove()
-      e.preventDefault()
-
-    @projectSelectDropdown()
-
-  projectSelectDropdown: ->
-    new ProjectSelect()
-
-    $('.project-item-select').on 'click', (e) =>
-      @changeProject $(e.currentTarget).val()
-
-    $('.js-projects-dropdown-toggle').on 'click', (e) ->
-      e.preventDefault()
-
-      $('.js-projects-dropdown').select2('open')
-
-  changeProject: (url) ->
-    window.location = url
-
-  initRefSwitcher: ->
-    $('.js-project-refs-dropdown').each ->
-      $dropdown = $(@)
-      selected = $dropdown.data('selected')
-
-      $dropdown.glDropdown(
-        data: (term, callback) ->
-          $.ajax(
-            url: $dropdown.data('refs-url')
-            data:
-              ref: $dropdown.data('ref')
-          ).done (refs) ->
-            callback(refs)
-        selectable: true
-        filterable: true
-        filterByText: true
-        fieldName: 'ref'
-        renderRow: (ref) ->
-          if ref.header?
-            $('<li />')
-              .addClass('dropdown-header')
-              .text(ref.header)
-          else
-            link = $('<a />')
-              .attr('href', '#')
-              .addClass(if ref is selected then 'is-active' else '')
-              .text(ref)
-              .attr('data-ref', escape(ref))
-
-            $('<li />')
-              .append(link)
-        id: (obj, $el) ->
-          $el.attr('data-ref')
-        toggleLabel: (obj, $el) ->
-          $el.text().trim()
-        clicked: (selected, $el, e) ->
-          e.preventDefault()
-          if $('input[name="ref"]').length
-            $form = $dropdown.closest('form')
-            action = $form.attr('action')
-            divider = if action.indexOf('?') < 0 then '?' else '&'
-
-            Turbolinks.visit "#{action}#{divider}#{$form.serialize()}"
-      )
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js
new file mode 100644
index 0000000000000000000000000000000000000000..277e71523d5bae41bcab2ce42d5949dfe0023520
--- /dev/null
+++ b/app/assets/javascripts/project_avatar.js
@@ -0,0 +1,21 @@
+(function() {
+  this.ProjectAvatar = (function() {
+    function ProjectAvatar() {
+      $('.js-choose-project-avatar-button').bind('click', function() {
+        var form;
+        form = $(this).closest('form');
+        return form.find('.js-project-avatar-input').click();
+      });
+      $('.js-project-avatar-input').bind('change', function() {
+        var filename, form;
+        form = $(this).closest('form');
+        filename = $(this).val().replace(/^.*[\\\/]/, '');
+        return form.find('.js-avatar-filename').text(filename);
+      });
+    }
+
+    return ProjectAvatar;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_avatar.js.coffee b/app/assets/javascripts/project_avatar.js.coffee
deleted file mode 100644
index 8bec6e2ccca0046469046bd4b3f338cfec827a74..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_avatar.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-class @ProjectAvatar
-  constructor: ->
-    $('.js-choose-project-avatar-button').bind 'click', ->
-      form = $(this).closest('form')
-      form.find('.js-project-avatar-input').click()
-    $('.js-project-avatar-input').bind 'change', ->
-      form = $(this).closest('form')
-      filename = $(this).val().replace(/^.*[\\\/]/, '')
-      form.find('.js-avatar-filename').text(filename)
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
new file mode 100644
index 0000000000000000000000000000000000000000..4925f0519f069117b0bc44c28e042c003d926a26
--- /dev/null
+++ b/app/assets/javascripts/project_find_file.js
@@ -0,0 +1,170 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.ProjectFindFile = (function() {
+    var highlighter;
+
+    function ProjectFindFile(element1, options) {
+      this.element = element1;
+      this.options = options;
+      this.goToBlob = bind(this.goToBlob, this);
+      this.goToTree = bind(this.goToTree, this);
+      this.selectRowDown = bind(this.selectRowDown, this);
+      this.selectRowUp = bind(this.selectRowUp, this);
+      this.filePaths = {};
+      this.inputElement = this.element.find(".file-finder-input");
+      this.initEvent();
+      this.inputElement.focus();
+      this.load(this.options.url);
+    }
+
+    ProjectFindFile.prototype.initEvent = function() {
+      this.inputElement.off("keyup");
+      this.inputElement.on("keyup", (function(_this) {
+        return function(event) {
+          var oldValue, ref, target, value;
+          target = $(event.target);
+          value = target.val();
+          oldValue = (ref = target.data("oldValue")) != null ? ref : "";
+          if (value !== oldValue) {
+            target.data("oldValue", value);
+            _this.findFile();
+            return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
+          }
+        };
+      })(this));
+      return this.element.find(".tree-content-holder .tree-table").on("click", function(event) {
+        var path;
+        if (event.target.nodeName !== "A") {
+          path = this.element.find(".tree-item-file-name a", this).attr("href");
+          if (path) {
+            return location.href = path;
+          }
+        }
+      });
+    };
+
+    ProjectFindFile.prototype.findFile = function() {
+      var result, searchText;
+      searchText = this.inputElement.val();
+      result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
+      return this.renderList(result, searchText);
+    };
+
+    ProjectFindFile.prototype.load = function(url) {
+      return $.ajax({
+        url: url,
+        method: "get",
+        dataType: "json",
+        success: (function(_this) {
+          return function(data) {
+            _this.element.find(".loading").hide();
+            _this.filePaths = data;
+            _this.findFile();
+            return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
+          };
+        })(this)
+      });
+    };
+
+    ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
+      var blobItemUrl, filePath, html, i, j, len, matches, results;
+      this.element.find(".tree-table > tbody").empty();
+      results = [];
+      for (i = j = 0, len = filePaths.length; j < len; i = ++j) {
+        filePath = filePaths[i];
+        if (i === 20) {
+          break;
+        }
+        if (searchText) {
+          matches = fuzzaldrinPlus.match(filePath, searchText);
+        }
+        blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
+        html = this.makeHtml(filePath, matches, blobItemUrl);
+        results.push(this.element.find(".tree-table > tbody").append(html));
+      }
+      return results;
+    };
+
+    highlighter = function(element, text, matches) {
+      var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
+      lastIndex = 0;
+      highlightText = "";
+      matchedChars = [];
+      for (j = 0, len = matches.length; j < len; j++) {
+        matchIndex = matches[j];
+        unmatched = text.substring(lastIndex, matchIndex);
+        if (unmatched) {
+          if (matchedChars.length) {
+            element.append(matchedChars.join("").bold());
+          }
+          matchedChars = [];
+          element.append(document.createTextNode(unmatched));
+        }
+        matchedChars.push(text[matchIndex]);
+        lastIndex = matchIndex + 1;
+      }
+      if (matchedChars.length) {
+        element.append(matchedChars.join("").bold());
+      }
+      return element.append(document.createTextNode(text.substring(lastIndex)));
+    };
+
+    ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
+      var $tr;
+      $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
+      if (matches) {
+        $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
+      } else {
+        $tr.find("a").attr("href", blobItemUrl).text(filePath);
+      }
+      return $tr;
+    };
+
+    ProjectFindFile.prototype.selectRow = function(type) {
+      var next, rows, selectedRow;
+      rows = this.element.find(".files-slider tr.tree-item");
+      selectedRow = this.element.find(".files-slider tr.tree-item.selected");
+      if (rows && rows.length > 0) {
+        if (selectedRow && selectedRow.length > 0) {
+          if (type === "UP") {
+            next = selectedRow.prev();
+          } else if (type === "DOWN") {
+            next = selectedRow.next();
+          }
+          if (next.length > 0) {
+            selectedRow.removeClass("selected");
+            selectedRow = next;
+          }
+        } else {
+          selectedRow = rows.eq(0);
+        }
+        return selectedRow.addClass("selected").focus();
+      }
+    };
+
+    ProjectFindFile.prototype.selectRowUp = function() {
+      return this.selectRow("UP");
+    };
+
+    ProjectFindFile.prototype.selectRowDown = function() {
+      return this.selectRow("DOWN");
+    };
+
+    ProjectFindFile.prototype.goToTree = function() {
+      return location.href = this.options.treeUrl;
+    };
+
+    ProjectFindFile.prototype.goToBlob = function() {
+      var path;
+      path = this.element.find(".tree-item.selected .tree-item-file-name a").attr("href");
+      if (path) {
+        return location.href = path;
+      }
+    };
+
+    return ProjectFindFile;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_find_file.js.coffee b/app/assets/javascripts/project_find_file.js.coffee
deleted file mode 100644
index 0dd32352c343484558a36fc6815adb111ed458d3..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_find_file.js.coffee
+++ /dev/null
@@ -1,125 +0,0 @@
-class @ProjectFindFile
-  constructor: (@element, @options)->
-    @filePaths = {}
-    @inputElement = @element.find(".file-finder-input")
-
-    # init event
-    @initEvent()
-
-    # focus text input box
-    @inputElement.focus()
-
-    # load file list
-    @load(@options.url)
-
-  # init event
-  initEvent: ->
-    @inputElement.off "keyup"
-    @inputElement.on "keyup", (event) =>
-      target = $(event.target)
-      value = target.val()
-      oldValue = target.data("oldValue") ? ""
-
-      if value != oldValue
-        target.data("oldValue", value)
-        @findFile()
-        @element.find("tr.tree-item").eq(0).addClass("selected").focus()
-
-    @element.find(".tree-content-holder .tree-table").on "click", (event) ->
-      if (event.target.nodeName != "A")
-        path = @element.find(".tree-item-file-name a", this).attr("href")
-        location.href = path if path
-
-  # find file
-  findFile: ->
-    searchText = @inputElement.val()
-    result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths
-    @renderList result, searchText
-
-  # files pathes load
-  load: (url) ->
-    $.ajax
-      url: url
-      method: "get"
-      dataType: "json"
-      success: (data) =>
-        @element.find(".loading").hide()
-        @filePaths = data
-        @findFile()
-        @element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus()
-
-  # render result
-  renderList: (filePaths, searchText) ->
-    @element.find(".tree-table > tbody").empty()
-
-    for filePath, i in filePaths
-      break if i == 20
-
-      if searchText
-        matches = fuzzaldrinPlus.match(filePath, searchText)
-
-      blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}"
-
-      html = @makeHtml filePath, matches, blobItemUrl
-      @element.find(".tree-table > tbody").append(html)
-
-  # highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
-  highlighter = (element, text, matches) ->
-    lastIndex = 0
-    highlightText = ""
-    matchedChars = []
-
-    for matchIndex in matches
-      unmatched = text.substring(lastIndex, matchIndex)
-
-      if unmatched
-        element.append(matchedChars.join("").bold()) if matchedChars.length
-        matchedChars = []
-        element.append(document.createTextNode(unmatched))
-
-      matchedChars.push(text[matchIndex])
-      lastIndex = matchIndex + 1
-
-    element.append(matchedChars.join("").bold()) if matchedChars.length
-    element.append(document.createTextNode(text.substring(lastIndex)))
-
-  # make tbody row html
-  makeHtml: (filePath, matches, blobItemUrl) ->
-    $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
-    if matches
-      $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
-    else
-      $tr.find("a").attr("href", blobItemUrl).text(filePath)
-
-    return $tr
-
-  selectRow: (type) ->
-    rows = @element.find(".files-slider tr.tree-item")
-    selectedRow = @element.find(".files-slider tr.tree-item.selected")
-
-    if rows && rows.length > 0
-      if selectedRow && selectedRow.length > 0
-        if type == "UP"
-          next = selectedRow.prev()
-        else if type == "DOWN"
-          next = selectedRow.next()
-
-        if next.length > 0
-          selectedRow.removeClass "selected"
-          selectedRow = next
-      else
-        selectedRow = rows.eq(0)
-      selectedRow.addClass("selected").focus()
-
-  selectRowUp: =>
-    @selectRow "UP"
-
-  selectRowDown: =>
-    @selectRow "DOWN"
-
-  goToTree: =>
-    location.href = @options.treeUrl
-
-  goToBlob: =>
-    path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href")
-    location.href = path if path
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
new file mode 100644
index 0000000000000000000000000000000000000000..d2261c51f35701beb0f62cfda20f9bda2d965a98
--- /dev/null
+++ b/app/assets/javascripts/project_fork.js
@@ -0,0 +1,14 @@
+(function() {
+  this.ProjectFork = (function() {
+    function ProjectFork() {
+      $('.fork-thumbnail a').on('click', function() {
+        $('.fork-namespaces').hide();
+        return $('.save-project-loader').show();
+      });
+    }
+
+    return ProjectFork;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_fork.js.coffee b/app/assets/javascripts/project_fork.js.coffee
deleted file mode 100644
index e15a1c4ef76781725d3dac9c99471861dcfbe774..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_fork.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @ProjectFork
-  constructor: ->
-    $('.fork-thumbnail a').on 'click', ->
-      $('.fork-namespaces').hide()
-      $('.save-project-loader').show()
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js
new file mode 100644
index 0000000000000000000000000000000000000000..c61b0cf2fde1d838fe9c43f27461ea0dd6c0064d
--- /dev/null
+++ b/app/assets/javascripts/project_import.js
@@ -0,0 +1,13 @@
+(function() {
+  this.ProjectImport = (function() {
+    function ProjectImport() {
+      setTimeout(function() {
+        return Turbolinks.visit(location.href);
+      }, 5000);
+    }
+
+    return ProjectImport;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee
deleted file mode 100644
index 6633564a0792c6c877737cd72d0bd4d0daa2d268..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_import.js.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-class @ProjectImport
-  constructor: ->
-    setTimeout ->
-       Turbolinks.visit(location.href)
-    , 5000
diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6a796b325aba297ab0312047e7c6cfce5459c3d
--- /dev/null
+++ b/app/assets/javascripts/project_members.js
@@ -0,0 +1,13 @@
+(function() {
+  this.ProjectMembers = (function() {
+    function ProjectMembers() {
+      $('li.project_member').bind('ajax:success', function() {
+        return $(this).fadeOut();
+      });
+    }
+
+    return ProjectMembers;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_members.js.coffee b/app/assets/javascripts/project_members.js.coffee
deleted file mode 100644
index 896ba7e53eefeec6e78b1effb252255c7378c8cb..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_members.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-class @ProjectMembers
-  constructor: ->
-    $('li.project_member').bind 'ajax:success', ->
-      $(this).fadeOut()
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
new file mode 100644
index 0000000000000000000000000000000000000000..798f15e40a05b7cb2765d430bb6dfce43878e0a2
--- /dev/null
+++ b/app/assets/javascripts/project_new.js
@@ -0,0 +1,40 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.ProjectNew = (function() {
+    function ProjectNew() {
+      this.toggleSettings = bind(this.toggleSettings, this);
+      $('.project-edit-container').on('ajax:before', (function(_this) {
+        return function() {
+          $('.project-edit-container').hide();
+          return $('.save-project-loader').show();
+        };
+      })(this));
+      this.toggleSettings();
+      this.toggleSettingsOnclick();
+    }
+
+    ProjectNew.prototype.toggleSettings = function() {
+      this._showOrHide('#project_builds_enabled', '.builds-feature');
+      return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature');
+    };
+
+    ProjectNew.prototype.toggleSettingsOnclick = function() {
+      return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings);
+    };
+
+    ProjectNew.prototype._showOrHide = function(checkElement, container) {
+      var $container;
+      $container = $(container);
+      if ($(checkElement).prop('checked')) {
+        return $container.show();
+      } else {
+        return $container.hide();
+      }
+    };
+
+    return ProjectNew;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
deleted file mode 100644
index e48343a19b543e25df38e940c18c6797c1b64452..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_new.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-class @ProjectNew
-  constructor: ->
-    $('.project-edit-container').on 'ajax:before', =>
-      $('.project-edit-container').hide()
-      $('.save-project-loader').show()
-    @toggleSettings()
-    @toggleSettingsOnclick()
-
-
-  toggleSettings: =>
-    @_showOrHide('#project_builds_enabled', '.builds-feature')
-    @_showOrHide('#project_merge_requests_enabled', '.merge-requests-feature')
-
-  toggleSettingsOnclick: ->
-    $('#project_builds_enabled, #project_merge_requests_enabled').on 'click', @toggleSettings
-
-  _showOrHide: (checkElement, container) ->
-    $container = $(container)
-
-    if $(checkElement).prop('checked')
-      $container.show()
-    else
-      $container.hide()
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..20b147500cf43d284ee80cd7022762f537afffa4
--- /dev/null
+++ b/app/assets/javascripts/project_select.js
@@ -0,0 +1,102 @@
+(function() {
+  this.ProjectSelect = (function() {
+    function ProjectSelect() {
+      $('.js-projects-dropdown-toggle').each(function(i, dropdown) {
+        var $dropdown;
+        $dropdown = $(dropdown);
+        return $dropdown.glDropdown({
+          filterable: true,
+          filterRemote: true,
+          search: {
+            fields: ['name_with_namespace']
+          },
+          data: function(term, callback) {
+            var finalCallback, projectsCallback;
+            finalCallback = function(projects) {
+              return callback(projects);
+            };
+            if (this.includeGroups) {
+              projectsCallback = function(projects) {
+                var groupsCallback;
+                groupsCallback = function(groups) {
+                  var data;
+                  data = groups.concat(projects);
+                  return finalCallback(data);
+                };
+                return Api.groups(term, false, groupsCallback);
+              };
+            } else {
+              projectsCallback = finalCallback;
+            }
+            if (this.groupId) {
+              return Api.groupProjects(this.groupId, term, projectsCallback);
+            } else {
+              return Api.projects(term, this.orderBy, projectsCallback);
+            }
+          },
+          url: function(project) {
+            return project.web_url;
+          },
+          text: function(project) {
+            return project.name_with_namespace;
+          }
+        });
+      });
+      $('.ajax-project-select').each(function(i, select) {
+        var placeholder;
+        this.groupId = $(select).data('group-id');
+        this.includeGroups = $(select).data('include-groups');
+        this.orderBy = $(select).data('order-by') || 'id';
+        placeholder = "Search for project";
+        if (this.includeGroups) {
+          placeholder += " or group";
+        }
+        return $(select).select2({
+          placeholder: placeholder,
+          minimumInputLength: 0,
+          query: (function(_this) {
+            return function(query) {
+              var finalCallback, projectsCallback;
+              finalCallback = function(projects) {
+                var data;
+                data = {
+                  results: projects
+                };
+                return query.callback(data);
+              };
+              if (_this.includeGroups) {
+                projectsCallback = function(projects) {
+                  var groupsCallback;
+                  groupsCallback = function(groups) {
+                    var data;
+                    data = groups.concat(projects);
+                    return finalCallback(data);
+                  };
+                  return Api.groups(query.term, false, groupsCallback);
+                };
+              } else {
+                projectsCallback = finalCallback;
+              }
+              if (_this.groupId) {
+                return Api.groupProjects(_this.groupId, query.term, projectsCallback);
+              } else {
+                return Api.projects(query.term, _this.orderBy, projectsCallback);
+              }
+            };
+          })(this),
+          id: function(project) {
+            return project.web_url;
+          },
+          text: function(project) {
+            return project.name_with_namespace || project.name;
+          },
+          dropdownCssClass: "ajax-project-dropdown"
+        });
+      });
+    }
+
+    return ProjectSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
deleted file mode 100644
index 704bd8dee536a4ccacf927be4bb67c482ac35720..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_select.js.coffee
+++ /dev/null
@@ -1,72 +0,0 @@
-class @ProjectSelect
-  constructor: ->
-    $('.js-projects-dropdown-toggle').each (i, dropdown) ->
-      $dropdown = $(dropdown)
-
-      $dropdown.glDropdown(
-        filterable: true
-        filterRemote: true
-        search:
-          fields: ['name_with_namespace']
-        data: (term, callback) ->
-          finalCallback = (projects) ->
-            callback projects
-
-          if @includeGroups
-            projectsCallback = (projects) ->
-              groupsCallback = (groups) ->
-                data = groups.concat(projects)
-                finalCallback(data)
-
-              Api.groups term, false, groupsCallback
-          else
-            projectsCallback = finalCallback
-
-          if @groupId
-            Api.groupProjects @groupId, term, projectsCallback
-          else
-            Api.projects term, @orderBy, projectsCallback
-        url: (project) ->
-          project.web_url
-        text: (project) ->
-          project.name_with_namespace
-      )
-
-    $('.ajax-project-select').each (i, select) ->
-      @groupId = $(select).data('group-id')
-      @includeGroups = $(select).data('include-groups')
-      @orderBy = $(select).data('order-by') || 'id'
-
-      placeholder = "Search for project"
-      placeholder += " or group" if @includeGroups
-
-      $(select).select2
-        placeholder: placeholder
-        minimumInputLength: 0
-        query: (query) =>
-          finalCallback = (projects) ->
-            data = { results: projects }
-            query.callback(data)
-
-          if @includeGroups
-            projectsCallback = (projects) ->
-              groupsCallback = (groups) ->
-                data = groups.concat(projects)
-                finalCallback(data)
-
-              Api.groups query.term, false, groupsCallback
-          else
-            projectsCallback = finalCallback
-
-          if @groupId
-            Api.groupProjects @groupId, query.term, projectsCallback
-          else
-            Api.projects query.term, @orderBy, projectsCallback
-
-        id: (project) ->
-          project.web_url
-
-        text: (project) ->
-          project.name_with_namespace || project.name
-
-        dropdownCssClass: "ajax-project-dropdown"
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ca4c4279120525b19e0744df248551bee67629f
--- /dev/null
+++ b/app/assets/javascripts/project_show.js
@@ -0,0 +1,9 @@
+(function() {
+  this.ProjectShow = (function() {
+    function ProjectShow() {}
+
+    return ProjectShow;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee
deleted file mode 100644
index 1fdf28f25281a18a5acaf1b7779c50bc08bd75aa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/project_show.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-class @ProjectShow
-  constructor: ->
-    # I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..4f415b05dbc4793f29410c8b53a627c43a9cd122
--- /dev/null
+++ b/app/assets/javascripts/projects_list.js
@@ -0,0 +1,48 @@
+(function() {
+  this.ProjectsList = {
+    init: function() {
+      $(".projects-list-filter").off('keyup');
+      this.initSearch();
+      return this.initPagination();
+    },
+    initSearch: function() {
+      var debounceFilter, projectsListFilter;
+      projectsListFilter = $('.projects-list-filter');
+      debounceFilter = _.debounce(ProjectsList.filterResults, 500);
+      return projectsListFilter.on('keyup', function(e) {
+        if (projectsListFilter.val() !== '') {
+          return debounceFilter();
+        }
+      });
+    },
+    filterResults: function() {
+      var form, project_filter_url, search;
+      $('.projects-list-holder').fadeTo(250, 0.5);
+      form = null;
+      form = $("form#project-filter-form");
+      search = $(".projects-list-filter").val();
+      project_filter_url = form.attr('action') + '?' + form.serialize();
+      return $.ajax({
+        type: "GET",
+        url: form.attr('action'),
+        data: form.serialize(),
+        complete: function() {
+          return $('.projects-list-holder').fadeTo(250, 1);
+        },
+        success: function(data) {
+          $('.projects-list-holder').replaceWith(data.html);
+          return history.replaceState({
+            page: project_filter_url
+          }, document.title, project_filter_url);
+        },
+        dataType: "json"
+      });
+    },
+    initPagination: function() {
+      return $('.projects-list-holder .pagination').on('ajax:success', function(e, data) {
+        return $('.projects-list-holder').replaceWith(data.html);
+      });
+    }
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
deleted file mode 100644
index a7d78d9e4612f1dceac097ca1efde6057f7e65c1..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/projects_list.js.coffee
+++ /dev/null
@@ -1,36 +0,0 @@
-@ProjectsList =
-  init: ->
-    $(".projects-list-filter").off('keyup')
-    this.initSearch()
-    this.initPagination()
-
-  initSearch: ->
-    projectsListFilter = $('.projects-list-filter')
-    debounceFilter = _.debounce ProjectsList.filterResults, 500
-    projectsListFilter.on 'keyup', (e) ->
-      debounceFilter() if projectsListFilter.val() isnt ''
-
-  filterResults: ->
-    $('.projects-list-holder').fadeTo(250, 0.5)
-
-    form = null
-    form = $("form#project-filter-form")
-    search = $(".projects-list-filter").val()
-    project_filter_url = form.attr('action') + '?' + form.serialize()
-
-    $.ajax
-      type: "GET"
-      url: form.attr('action')
-      data: form.serialize()
-      complete: ->
-        $('.projects-list-holder').fadeTo(250, 1)
-      success: (data) ->
-        $('.projects-list-holder').replaceWith(data.html)
-        # Change url so if user reload a page - search results are saved
-        history.replaceState {page: project_filter_url}, document.title, project_filter_url
-      dataType: "json"
-
-  initPagination: ->
-    $('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
-      $('.projects-list-holder').replaceWith(data.html)
-    )
diff --git a/app/assets/javascripts/protected_branch_select.js b/app/assets/javascripts/protected_branch_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a47fc972dc94d9c10a0f4b805373b0e492091cf
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_select.js
@@ -0,0 +1,72 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.ProtectedBranchSelect = (function() {
+    function ProtectedBranchSelect(currentProject) {
+      this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
+      this.getProtectedBranches = bind(this.getProtectedBranches, this);
+      $('.dropdown-footer').hide();
+      this.dropdown = $('.js-protected-branch-select').glDropdown({
+        data: this.getProtectedBranches,
+        filterable: true,
+        remote: false,
+        search: {
+          fields: ['title']
+        },
+        selectable: true,
+        toggleLabel: function(selected) {
+          if (selected && 'id' in selected) {
+            return selected.title;
+          } else {
+            return 'Protected Branch';
+          }
+        },
+        fieldName: 'protected_branch[name]',
+        text: function(protected_branch) {
+          return _.escape(protected_branch.title);
+        },
+        id: function(protected_branch) {
+          return _.escape(protected_branch.id);
+        },
+        onFilter: this.toggleCreateNewButton,
+        clicked: function() {
+          return $('.protect-branch-btn').attr('disabled', false);
+        }
+      });
+      $('.create-new-protected-branch').on('click', (function(_this) {
+        return function(event) {
+          _this.dropdown.data('glDropdown').remote.execute();
+          return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
+        };
+      })(this));
+    }
+
+    ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
+      if (this.selectedBranch) {
+        return callback(gon.open_branches.concat(this.selectedBranch));
+      } else {
+        return callback(gon.open_branches);
+      }
+    };
+
+    ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
+      this.selectedBranch = {
+        title: branchName,
+        id: branchName,
+        text: branchName
+      };
+      if (branchName === '') {
+        $('.protected-branch-select-footer-list').addClass('hidden');
+        return $('.dropdown-footer').hide();
+      } else {
+        $('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
+        $('.protected-branch-select-footer-list').removeClass('hidden');
+        return $('.dropdown-footer').show();
+      }
+    };
+
+    return ProtectedBranchSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee
deleted file mode 100644
index 6d45770ace9c6e4692f9965c29f2b20572976dea..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/protected_branch_select.js.coffee
+++ /dev/null
@@ -1,40 +0,0 @@
-class @ProtectedBranchSelect
-  constructor: (currentProject) ->
-    $('.dropdown-footer').hide();
-    @dropdown = $('.js-protected-branch-select').glDropdown(
-      data: @getProtectedBranches
-      filterable: true
-      remote: false
-      search:
-        fields: ['title']
-      selectable: true
-      toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch'
-      fieldName: 'protected_branch[name]'
-      text: (protected_branch) -> _.escape(protected_branch.title)
-      id: (protected_branch) -> _.escape(protected_branch.id)
-      onFilter: @toggleCreateNewButton
-      clicked: () -> $('.protect-branch-btn').attr('disabled', false)
-    )
-
-    $('.create-new-protected-branch').on 'click', (event) =>
-      # Refresh the dropdown's data, which ends up calling `getProtectedBranches`
-      @dropdown.data('glDropdown').remote.execute()
-      @dropdown.data('glDropdown').selectRowAtIndex(event, 0)
-
-  getProtectedBranches: (term, callback) =>
-    if @selectedBranch
-      callback(gon.open_branches.concat(@selectedBranch))
-    else
-      callback(gon.open_branches)
-
-  toggleCreateNewButton: (branchName) =>
-    @selectedBranch = { title: branchName, id: branchName, text: branchName }
-
-    if branchName is ''
-      $('.protected-branch-select-footer-list').addClass('hidden')
-      $('.dropdown-footer').hide();
-    else
-      $('.create-new-protected-branch').text("Create Protected Branch: #{branchName}")
-      $('.protected-branch-select-footer-list').removeClass('hidden')
-      $('.dropdown-footer').show();
-
diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee
deleted file mode 100644
index 14afef2e2ee39034d07d56139bb088a2f3114114..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/protected_branches.js.coffee
+++ /dev/null
@@ -1,22 +0,0 @@
-$ ->
-  $(".protected-branches-list :checkbox").change (e) ->
-    name = $(this).attr("name")
-    if name == "developers_can_push" || name == "developers_can_merge"
-      id = $(this).val()
-      can_push = $(this).is(":checked")
-      url = $(this).data("url")
-      $.ajax
-        type: "PATCH"
-        url: url
-        dataType: "json"
-        data:
-          id: id
-          protected_branch:
-            "#{name}": can_push
-
-        success: ->
-          row = $(e.target)
-          row.closest('tr').effect('highlight')
-
-        error: ->
-          new Flash("Failed to update branch!", "alert")
diff --git a/app/assets/javascripts/protected_branches_access_select.js.es6 b/app/assets/javascripts/protected_branches_access_select.js.es6
new file mode 100644
index 0000000000000000000000000000000000000000..e98312bbf37d2241917c28228aa3b944075bafdd
--- /dev/null
+++ b/app/assets/javascripts/protected_branches_access_select.js.es6
@@ -0,0 +1,63 @@
+class ProtectedBranchesAccessSelect {
+  constructor(container, saveOnSelect, selectDefault) {
+    this.container = container;
+    this.saveOnSelect = saveOnSelect;
+
+    this.container.find(".allowed-to-merge").each((i, element) => {
+      var fieldName = $(element).data('field-name');
+      var dropdown = $(element).glDropdown({
+        data: gon.merge_access_levels,
+        selectable: true,
+        fieldName: fieldName,
+        clicked: _.chain(this.onSelect).partial(element).bind(this).value()
+      });
+
+      if (selectDefault) {
+        dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
+      }
+    });
+
+
+    this.container.find(".allowed-to-push").each((i, element) => {
+      var fieldName = $(element).data('field-name');
+      var dropdown = $(element).glDropdown({
+        data: gon.push_access_levels,
+        selectable: true,
+        fieldName: fieldName,
+        clicked: _.chain(this.onSelect).partial(element).bind(this).value()
+      });
+
+      if (selectDefault) {
+        dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
+      }
+    });
+  }
+
+  onSelect(dropdown, selected, element, e) {
+    $(dropdown).find('.dropdown-toggle-text').text(selected.text);
+    if (this.saveOnSelect) {
+      return $.ajax({
+        type: "POST",
+        url: $(dropdown).data('url'),
+        dataType: "json",
+        data: {
+          _method: 'PATCH',
+          id: $(dropdown).data('id'),
+          protected_branch: {
+            ["" + ($(dropdown).data('type')) + "_attributes"]: {
+              "access_level": selected.id
+            }
+          }
+        },
+        success: function() {
+          var row;
+          row = $(e.target);
+          return row.closest('tr').effect('highlight');
+        },
+        error: function() {
+          return new Flash("Failed to update branch!", "alert");
+        }
+      });
+    }
+  }
+}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc4d51138261f1f71a90684367ac5b289a97c5e1
--- /dev/null
+++ b/app/assets/javascripts/right_sidebar.js
@@ -0,0 +1,201 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Sidebar = (function() {
+    function Sidebar(currentUser) {
+      this.toggleTodo = bind(this.toggleTodo, this);
+      this.sidebar = $('aside');
+      this.addEventListeners();
+    }
+
+    Sidebar.prototype.addEventListeners = function() {
+      this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
+      $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
+      $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
+      $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
+      $(document).off('click', '.js-sidebar-toggle').on('click', '.js-sidebar-toggle', function(e, triggered) {
+        var $allGutterToggleIcons, $this, $thisIcon;
+        e.preventDefault();
+        $this = $(this);
+        $thisIcon = $this.find('i');
+        $allGutterToggleIcons = $('.js-sidebar-toggle i');
+        if ($thisIcon.hasClass('fa-angle-double-right')) {
+          $allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
+          $('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+          $('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+        } else {
+          $allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
+          $('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+          $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+        }
+        if (!triggered) {
+          return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
+            path: '/'
+          });
+        }
+      });
+      return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
+    };
+
+    Sidebar.prototype.toggleTodo = function(e) {
+      var $btnText, $this, $todoLoading, ajaxType, url;
+      $this = $(e.currentTarget);
+      $todoLoading = $('.js-issuable-todo-loading');
+      $btnText = $('.js-issuable-todo-text', $this);
+      ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST';
+      if ($this.attr('data-delete-path')) {
+        url = "" + ($this.attr('data-delete-path'));
+      } else {
+        url = "" + ($this.data('url'));
+      }
+      return $.ajax({
+        url: url,
+        type: ajaxType,
+        dataType: 'json',
+        data: {
+          issuable_id: $this.data('issuable-id'),
+          issuable_type: $this.data('issuable-type')
+        },
+        beforeSend: (function(_this) {
+          return function() {
+            return _this.beforeTodoSend($this, $todoLoading);
+          };
+        })(this)
+      }).done((function(_this) {
+        return function(data) {
+          return _this.todoUpdateDone(data, $this, $btnText, $todoLoading);
+        };
+      })(this));
+    };
+
+    Sidebar.prototype.beforeTodoSend = function($btn, $todoLoading) {
+      $btn.disable();
+      return $todoLoading.removeClass('hidden');
+    };
+
+    Sidebar.prototype.todoUpdateDone = function(data, $btn, $btnText, $todoLoading) {
+      var $todoPendingCount;
+      $todoPendingCount = $('.todos-pending-count');
+      $todoPendingCount.text(data.count);
+      $btn.enable();
+      $todoLoading.addClass('hidden');
+      if (data.count === 0) {
+        $todoPendingCount.addClass('hidden');
+      } else {
+        $todoPendingCount.removeClass('hidden');
+      }
+      if (data.delete_path != null) {
+        $btn.attr('aria-label', $btn.data('mark-text')).attr('data-delete-path', data.delete_path);
+        return $btnText.text($btn.data('mark-text'));
+      } else {
+        $btn.attr('aria-label', $btn.data('todo-text')).removeAttr('data-delete-path');
+        return $btnText.text($btn.data('todo-text'));
+      }
+    };
+
+    Sidebar.prototype.sidebarDropdownLoading = function(e) {
+      var $loading, $sidebarCollapsedIcon, i, img;
+      $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+      img = $sidebarCollapsedIcon.find('img');
+      i = $sidebarCollapsedIcon.find('i');
+      $loading = $('<i class="fa fa-spinner fa-spin"></i>');
+      if (img.length) {
+        img.before($loading);
+        return img.hide();
+      } else if (i.length) {
+        i.before($loading);
+        return i.hide();
+      }
+    };
+
+    Sidebar.prototype.sidebarDropdownLoaded = function(e) {
+      var $sidebarCollapsedIcon, i, img;
+      $sidebarCollapsedIcon = $(this).closest('.block').find('.sidebar-collapsed-icon');
+      img = $sidebarCollapsedIcon.find('img');
+      $sidebarCollapsedIcon.find('i.fa-spin').remove();
+      i = $sidebarCollapsedIcon.find('i');
+      if (img.length) {
+        return img.show();
+      } else {
+        return i.show();
+      }
+    };
+
+    Sidebar.prototype.sidebarCollapseClicked = function(e) {
+      var $block, sidebar;
+      if ($(e.currentTarget).hasClass('dont-change-state')) {
+        return;
+      }
+      sidebar = e.data;
+      e.preventDefault();
+      $block = $(this).closest('.block');
+      return sidebar.openDropdown($block);
+    };
+
+    Sidebar.prototype.openDropdown = function(blockOrName) {
+      var $block;
+      $block = _.isString(blockOrName) ? this.getBlock(blockOrName) : blockOrName;
+      $block.find('.edit-link').trigger('click');
+      if (!this.isOpen()) {
+        this.setCollapseAfterUpdate($block);
+        return this.toggleSidebar('open');
+      }
+    };
+
+    Sidebar.prototype.setCollapseAfterUpdate = function($block) {
+      $block.addClass('collapse-after-update');
+      return $('.page-with-sidebar').addClass('with-overlay');
+    };
+
+    Sidebar.prototype.onSidebarDropdownHidden = function(e) {
+      var $block, sidebar;
+      sidebar = e.data;
+      e.preventDefault();
+      $block = $(this).closest('.block');
+      return sidebar.sidebarDropdownHidden($block);
+    };
+
+    Sidebar.prototype.sidebarDropdownHidden = function($block) {
+      if ($block.hasClass('collapse-after-update')) {
+        $block.removeClass('collapse-after-update');
+        $('.page-with-sidebar').removeClass('with-overlay');
+        return this.toggleSidebar('hide');
+      }
+    };
+
+    Sidebar.prototype.triggerOpenSidebar = function() {
+      return this.sidebar.find('.js-sidebar-toggle').trigger('click');
+    };
+
+    Sidebar.prototype.toggleSidebar = function(action) {
+      if (action == null) {
+        action = 'toggle';
+      }
+      if (action === 'toggle') {
+        this.triggerOpenSidebar();
+      }
+      if (action === 'open') {
+        if (!this.isOpen()) {
+          this.triggerOpenSidebar();
+        }
+      }
+      if (action === 'hide') {
+        if (this.isOpen()) {
+          return this.triggerOpenSidebar();
+        }
+      }
+    };
+
+    Sidebar.prototype.isOpen = function() {
+      return this.sidebar.is('.right-sidebar-expanded');
+    };
+
+    Sidebar.prototype.getBlock = function(name) {
+      return this.sidebar.find(".block." + name);
+    };
+
+    return Sidebar;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/right_sidebar.js.coffee b/app/assets/javascripts/right_sidebar.js.coffee
deleted file mode 100644
index 12340bbce54ba71f97d1b4b8246478807185954b..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/right_sidebar.js.coffee
+++ /dev/null
@@ -1,172 +0,0 @@
-class @Sidebar
-  constructor: (currentUser) ->
-    @sidebar = $('aside')
-
-    @addEventListeners()
-
-  addEventListeners: ->
-    @sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
-    $('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
-    $('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
-    $('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
-
-
-    $(document)
-      .off 'click', '.js-sidebar-toggle'
-      .on 'click', '.js-sidebar-toggle', (e, triggered) ->
-        e.preventDefault()
-        $this = $(this)
-        $thisIcon = $this.find 'i'
-        $allGutterToggleIcons = $('.js-sidebar-toggle i')
-        if $thisIcon.hasClass('fa-angle-double-right')
-          $allGutterToggleIcons
-            .removeClass('fa-angle-double-right')
-            .addClass('fa-angle-double-left')
-          $('aside.right-sidebar')
-            .removeClass('right-sidebar-expanded')
-            .addClass('right-sidebar-collapsed')
-          $('.page-with-sidebar')
-            .removeClass('right-sidebar-expanded')
-            .addClass('right-sidebar-collapsed')
-        else
-          $allGutterToggleIcons
-            .removeClass('fa-angle-double-left')
-            .addClass('fa-angle-double-right')
-          $('aside.right-sidebar')
-            .removeClass('right-sidebar-collapsed')
-            .addClass('right-sidebar-expanded')
-          $('.page-with-sidebar')
-            .removeClass('right-sidebar-collapsed')
-            .addClass('right-sidebar-expanded')
-        if not triggered
-          $.cookie("collapsed_gutter",
-            $('.right-sidebar')
-              .hasClass('right-sidebar-collapsed'), { path: '/' })
-
-    $(document)
-      .off 'click', '.js-issuable-todo'
-      .on 'click', '.js-issuable-todo', @toggleTodo
-
-  toggleTodo: (e) =>
-    $this = $(e.currentTarget)
-    $todoLoading = $('.js-issuable-todo-loading')
-    $btnText = $('.js-issuable-todo-text', $this)
-    ajaxType = if $this.attr('data-delete-path') then 'DELETE' else 'POST'
-
-    if $this.attr('data-delete-path')
-      url = "#{$this.attr('data-delete-path')}"
-    else
-      url = "#{$this.data('url')}"
-
-    $.ajax(
-      url: url
-      type: ajaxType
-      dataType: 'json'
-      data:
-        issuable_id: $this.data('issuable-id')
-        issuable_type: $this.data('issuable-type')
-      beforeSend: =>
-        @beforeTodoSend($this, $todoLoading)
-    ).done (data) =>
-      @todoUpdateDone(data, $this, $btnText, $todoLoading)
-
-  beforeTodoSend: ($btn, $todoLoading) ->
-    $btn.disable()
-    $todoLoading.removeClass 'hidden'
-
-  todoUpdateDone: (data, $btn, $btnText, $todoLoading) ->
-    $todoPendingCount = $('.todos-pending-count')
-    $todoPendingCount.text data.count
-
-    $btn.enable()
-    $todoLoading.addClass 'hidden'
-
-    if data.count is 0
-      $todoPendingCount.addClass 'hidden'
-    else
-      $todoPendingCount.removeClass 'hidden'
-
-    if data.delete_path?
-      $btn
-        .attr 'aria-label', $btn.data('mark-text')
-        .attr 'data-delete-path', data.delete_path
-      $btnText.text $btn.data('mark-text')
-    else
-      $btn
-        .attr 'aria-label', $btn.data('todo-text')
-        .removeAttr 'data-delete-path'
-      $btnText.text $btn.data('todo-text')
-
-  sidebarDropdownLoading: (e) ->
-    $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
-    img = $sidebarCollapsedIcon.find('img')
-    i = $sidebarCollapsedIcon.find('i')
-    $loading = $('<i class="fa fa-spinner fa-spin"></i>')
-    if img.length
-      img.before($loading)
-      img.hide()
-    else if i.length
-      i.before($loading)
-      i.hide()
-
-  sidebarDropdownLoaded: (e) ->
-    $sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
-    img = $sidebarCollapsedIcon.find('img')
-    $sidebarCollapsedIcon.find('i.fa-spin').remove()
-    i = $sidebarCollapsedIcon.find('i')
-    if img.length
-      img.show()
-    else
-      i.show()
-
-  sidebarCollapseClicked: (e) ->
-    sidebar = e.data
-    e.preventDefault()
-    $block = $(@).closest('.block')
-    sidebar.openDropdown($block);
-
-  openDropdown: (blockOrName) ->
-    $block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
-
-    $block.find('.edit-link').trigger('click')
-
-    if not @isOpen()
-      @setCollapseAfterUpdate($block)
-      @toggleSidebar('open')
-
-  setCollapseAfterUpdate: ($block) ->
-    $block.addClass('collapse-after-update')
-    $('.page-with-sidebar').addClass('with-overlay')
-
-  onSidebarDropdownHidden: (e) ->
-    sidebar = e.data
-    e.preventDefault()
-    $block = $(@).closest('.block')
-    sidebar.sidebarDropdownHidden($block)
-
-  sidebarDropdownHidden: ($block) ->
-    if $block.hasClass('collapse-after-update')
-      $block.removeClass('collapse-after-update')
-      $('.page-with-sidebar').removeClass('with-overlay')
-      @toggleSidebar('hide')
-
-  triggerOpenSidebar: ->
-    @sidebar
-      .find('.js-sidebar-toggle')
-      .trigger('click')
-
-  toggleSidebar: (action = 'toggle') ->
-    if action is 'toggle'
-      @triggerOpenSidebar()
-
-    if action is 'open'
-      @triggerOpenSidebar() if not @isOpen()
-
-    if action is 'hide'
-      @triggerOpenSidebar() if @isOpen()
-
-  isOpen: ->
-    @sidebar.is('.right-sidebar-expanded')
-
-  getBlock: (name) ->
-    @sidebar.find(".block.#{name}")
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
new file mode 100644
index 0000000000000000000000000000000000000000..d34346f862bcddba426526426a88968eebee9a6e
--- /dev/null
+++ b/app/assets/javascripts/search.js
@@ -0,0 +1,93 @@
+(function() {
+  this.Search = (function() {
+    function Search() {
+      var $groupDropdown, $projectDropdown;
+      $groupDropdown = $('.js-search-group-dropdown');
+      $projectDropdown = $('.js-search-project-dropdown');
+      this.eventListeners();
+      $groupDropdown.glDropdown({
+        selectable: true,
+        filterable: true,
+        fieldName: 'group_id',
+        data: function(term, callback) {
+          return Api.groups(term, null, function(data) {
+            data.unshift({
+              name: 'Any'
+            });
+            data.splice(1, 0, 'divider');
+            return callback(data);
+          });
+        },
+        id: function(obj) {
+          return obj.id;
+        },
+        text: function(obj) {
+          return obj.name;
+        },
+        toggleLabel: function(obj) {
+          return ($groupDropdown.data('default-label')) + " " + obj.name;
+        },
+        clicked: (function(_this) {
+          return function() {
+            return _this.submitSearch();
+          };
+        })(this)
+      });
+      $projectDropdown.glDropdown({
+        selectable: true,
+        filterable: true,
+        fieldName: 'project_id',
+        data: function(term, callback) {
+          return Api.projects(term, 'id', function(data) {
+            data.unshift({
+              name_with_namespace: 'Any'
+            });
+            data.splice(1, 0, 'divider');
+            return callback(data);
+          });
+        },
+        id: function(obj) {
+          return obj.id;
+        },
+        text: function(obj) {
+          return obj.name_with_namespace;
+        },
+        toggleLabel: function(obj) {
+          return ($projectDropdown.data('default-label')) + " " + obj.name_with_namespace;
+        },
+        clicked: (function(_this) {
+          return function() {
+            return _this.submitSearch();
+          };
+        })(this)
+      });
+    }
+
+    Search.prototype.eventListeners = function() {
+      $(document).off('keyup', '.js-search-input').on('keyup', '.js-search-input', this.searchKeyUp);
+      return $(document).off('click', '.js-search-clear').on('click', '.js-search-clear', this.clearSearchField);
+    };
+
+    Search.prototype.submitSearch = function() {
+      return $('.js-search-form').submit();
+    };
+
+    Search.prototype.searchKeyUp = function() {
+      var $input;
+      $input = $(this);
+      if ($input.val() === '') {
+        return $('.js-search-clear').addClass('hidden');
+      } else {
+        return $('.js-search-clear').removeClass('hidden');
+      }
+    };
+
+    Search.prototype.clearSearchField = function() {
+      return $('.js-search-input').val('').trigger('keyup').focus();
+    };
+
+    return Search;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/search.js.coffee b/app/assets/javascripts/search.js.coffee
deleted file mode 100644
index 661e1195f60e4407cb6425b864b5da393e13d83c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/search.js.coffee
+++ /dev/null
@@ -1,75 +0,0 @@
-class @Search
-  constructor: ->
-    $groupDropdown = $('.js-search-group-dropdown')
-    $projectDropdown = $('.js-search-project-dropdown')
-    @eventListeners()
-
-    $groupDropdown.glDropdown(
-      selectable: true
-      filterable: true
-      fieldName: 'group_id'
-      data: (term, callback) ->
-        Api.groups term, null, (data) ->
-          data.unshift(
-            name: 'Any'
-          )
-          data.splice 1, 0, 'divider'
-
-          callback(data)
-      id: (obj) ->
-        obj.id
-      text: (obj) ->
-        obj.name
-      toggleLabel: (obj) ->
-        "#{$groupDropdown.data('default-label')} #{obj.name}"
-      clicked: =>
-        @submitSearch()
-    )
-
-    $projectDropdown.glDropdown(
-      selectable: true
-      filterable: true
-      fieldName: 'project_id'
-      data: (term, callback) ->
-        Api.projects term, 'id', (data) ->
-          data.unshift(
-            name_with_namespace: 'Any'
-          )
-          data.splice 1, 0, 'divider'
-
-          callback(data)
-      id: (obj) ->
-        obj.id
-      text: (obj) ->
-        obj.name_with_namespace
-      toggleLabel: (obj) ->
-        "#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
-      clicked: =>
-        @submitSearch()
-    )
-
-  eventListeners: ->
-    $(document)
-      .off 'keyup', '.js-search-input'
-      .on 'keyup', '.js-search-input', @searchKeyUp
-
-    $(document)
-      .off 'click', '.js-search-clear'
-      .on 'click', '.js-search-clear', @clearSearchField
-
-  submitSearch: ->
-    $('.js-search-form').submit()
-
-  searchKeyUp: ->
-    $input = $(@)
-
-    if $input.val() is ''
-      $('.js-search-clear').addClass 'hidden'
-    else
-      $('.js-search-clear').removeClass 'hidden'
-
-  clearSearchField: ->
-    $('.js-search-input')
-      .val ''
-      .trigger 'keyup'
-      .focus()
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
new file mode 100644
index 0000000000000000000000000000000000000000..990f6536eb2a69a4ca8b2d9425e4a8cb2a0d0066
--- /dev/null
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -0,0 +1,360 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.SearchAutocomplete = (function() {
+    var KEYCODE;
+
+    KEYCODE = {
+      ESCAPE: 27,
+      BACKSPACE: 8,
+      ENTER: 13
+    };
+
+    function SearchAutocomplete(opts) {
+      var ref, ref1, ref2, ref3, ref4;
+      if (opts == null) {
+        opts = {};
+      }
+      this.onSearchInputBlur = bind(this.onSearchInputBlur, this);
+      this.onClearInputClick = bind(this.onClearInputClick, this);
+      this.onSearchInputFocus = bind(this.onSearchInputFocus, this);
+      this.onSearchInputClick = bind(this.onSearchInputClick, this);
+      this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
+      this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
+      this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
+      this.dropdown = this.wrap.find('.dropdown');
+      this.dropdownContent = this.dropdown.find('.dropdown-content');
+      this.locationBadgeEl = this.getElement('.location-badge');
+      this.scopeInputEl = this.getElement('#scope');
+      this.searchInput = this.getElement('.search-input');
+      this.projectInputEl = this.getElement('#search_project_id');
+      this.groupInputEl = this.getElement('#group_id');
+      this.searchCodeInputEl = this.getElement('#search_code');
+      this.repositoryInputEl = this.getElement('#repository_ref');
+      this.clearInput = this.getElement('.js-clear-input');
+      this.saveOriginalState();
+      if (gon.current_user_id) {
+        this.createAutocomplete();
+      }
+      this.searchInput.addClass('disabled');
+      this.saveTextLength();
+      this.bindEvents();
+    }
+
+    SearchAutocomplete.prototype.getElement = function(selector) {
+      return this.wrap.find(selector);
+    };
+
+    SearchAutocomplete.prototype.saveOriginalState = function() {
+      return this.originalState = this.serializeState();
+    };
+
+    SearchAutocomplete.prototype.saveTextLength = function() {
+      return this.lastTextLength = this.searchInput.val().length;
+    };
+
+    SearchAutocomplete.prototype.createAutocomplete = function() {
+      return this.searchInput.glDropdown({
+        filterInputBlur: false,
+        filterable: true,
+        filterRemote: true,
+        highlight: true,
+        enterCallback: false,
+        filterInput: 'input#search',
+        search: {
+          fields: ['text']
+        },
+        data: this.getData.bind(this),
+        selectable: true,
+        clicked: this.onClick.bind(this)
+      });
+    };
+
+    SearchAutocomplete.prototype.getData = function(term, callback) {
+      var _this, contents, jqXHR;
+      _this = this;
+      if (!term) {
+        if (contents = this.getCategoryContents()) {
+          this.searchInput.data('glDropdown').filter.options.callback(contents);
+          this.enableAutocomplete();
+        }
+        return;
+      }
+      if (this.loadingSuggestions) {
+        return;
+      }
+      this.loadingSuggestions = true;
+      return jqXHR = $.get(this.autocompletePath, {
+        project_id: this.projectId,
+        project_ref: this.projectRef,
+        term: term
+      }, function(response) {
+        var data, firstCategory, i, lastCategory, len, suggestion;
+        if (!response.length) {
+          _this.disableAutocomplete();
+          return;
+        }
+        data = [];
+        firstCategory = true;
+        for (i = 0, len = response.length; i < len; i++) {
+          suggestion = response[i];
+          if (lastCategory !== suggestion.category) {
+            if (!firstCategory) {
+              data.push('separator');
+            }
+            if (firstCategory) {
+              firstCategory = false;
+            }
+            data.push({
+              header: suggestion.category
+            });
+            lastCategory = suggestion.category;
+          }
+          data.push({
+            id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
+            category: suggestion.category,
+            text: suggestion.label,
+            url: suggestion.url
+          });
+        }
+        if (data.length) {
+          data.push('separator');
+          data.push({
+            text: "Result name contains \"" + term + "\"",
+            url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val())
+          });
+        }
+        return callback(data);
+      }).always(function() {
+        return _this.loadingSuggestions = false;
+      });
+    };
+
+    SearchAutocomplete.prototype.getCategoryContents = function() {
+      var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
+      userId = gon.current_user_id;
+      utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
+      if (utils.isInGroupsPage() && groupOptions) {
+        options = groupOptions[utils.getGroupSlug()];
+      } else if (utils.isInProjectPage() && projectOptions) {
+        options = projectOptions[utils.getProjectSlug()];
+      } else if (dashboardOptions) {
+        options = dashboardOptions;
+      }
+      issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name;
+      items = [
+        {
+          header: "" + name
+        }, {
+          text: 'Issues assigned to me',
+          url: issuesPath + "/?assignee_id=" + userId
+        }, {
+          text: "Issues I've created",
+          url: issuesPath + "/?author_id=" + userId
+        }, 'separator', {
+          text: 'Merge requests assigned to me',
+          url: mrPath + "/?assignee_id=" + userId
+        }, {
+          text: "Merge requests I've created",
+          url: mrPath + "/?author_id=" + userId
+        }
+      ];
+      if (!name) {
+        items.splice(0, 1);
+      }
+      return items;
+    };
+
+    SearchAutocomplete.prototype.serializeState = function() {
+      return {
+        search_project_id: this.projectInputEl.val(),
+        group_id: this.groupInputEl.val(),
+        search_code: this.searchCodeInputEl.val(),
+        repository_ref: this.repositoryInputEl.val(),
+        scope: this.scopeInputEl.val(),
+        _location: this.locationBadgeEl.text()
+      };
+    };
+
+    SearchAutocomplete.prototype.bindEvents = function() {
+      this.searchInput.on('keydown', this.onSearchInputKeyDown);
+      this.searchInput.on('keyup', this.onSearchInputKeyUp);
+      this.searchInput.on('click', this.onSearchInputClick);
+      this.searchInput.on('focus', this.onSearchInputFocus);
+      this.searchInput.on('blur', this.onSearchInputBlur);
+      this.clearInput.on('click', this.onClearInputClick);
+      return this.locationBadgeEl.on('click', (function(_this) {
+        return function() {
+          return _this.searchInput.focus();
+        };
+      })(this));
+    };
+
+    SearchAutocomplete.prototype.enableAutocomplete = function() {
+      var _this;
+      if (!gon.current_user_id) {
+        return;
+      }
+      if (!this.dropdown.hasClass('open')) {
+        _this = this;
+        this.loadingSuggestions = false;
+        this.dropdown.addClass('open').trigger('shown.bs.dropdown');
+        return this.searchInput.removeClass('disabled');
+      }
+    };
+
+    SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
+      return this.saveTextLength();
+    };
+
+    SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
+      switch (e.keyCode) {
+        case KEYCODE.BACKSPACE:
+          if (this.lastTextLength === 0 && this.badgePresent()) {
+            this.removeLocationBadge();
+          }
+          if (this.lastTextLength === 1) {
+            this.disableAutocomplete();
+          }
+          if (this.lastTextLength > 1) {
+            this.enableAutocomplete();
+          }
+          break;
+        case KEYCODE.ESCAPE:
+          this.restoreOriginalState();
+          break;
+        default:
+          if (this.searchInput.val() === '') {
+            this.disableAutocomplete();
+          } else {
+            if (e.keyCode !== KEYCODE.ENTER) {
+              this.enableAutocomplete();
+            }
+          }
+      }
+      this.wrap.toggleClass('has-value', !!e.target.value);
+    };
+
+    SearchAutocomplete.prototype.onSearchInputClick = function(e) {
+      return e.stopImmediatePropagation();
+    };
+
+    SearchAutocomplete.prototype.onSearchInputFocus = function() {
+      this.isFocused = true;
+      this.wrap.addClass('search-active');
+      if (this.getValue() === '') {
+        return this.getData();
+      }
+    };
+
+    SearchAutocomplete.prototype.getValue = function() {
+      return this.searchInput.val();
+    };
+
+    SearchAutocomplete.prototype.onClearInputClick = function(e) {
+      e.preventDefault();
+      return this.searchInput.val('').focus();
+    };
+
+    SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
+      this.isFocused = false;
+      this.wrap.removeClass('search-active');
+      if (this.searchInput.val() === '') {
+        return this.restoreOriginalState();
+      }
+    };
+
+    SearchAutocomplete.prototype.addLocationBadge = function(item) {
+      var badgeText, category, value;
+      category = item.category != null ? item.category + ": " : '';
+      value = item.value != null ? item.value : '';
+      badgeText = "" + category + value;
+      this.locationBadgeEl.text(badgeText).show();
+      return this.wrap.addClass('has-location-badge');
+    };
+
+    SearchAutocomplete.prototype.hasLocationBadge = function() {
+      return this.wrap.is('.has-location-badge');
+    };
+
+    SearchAutocomplete.prototype.restoreOriginalState = function() {
+      var i, input, inputs, len;
+      inputs = Object.keys(this.originalState);
+      for (i = 0, len = inputs.length; i < len; i++) {
+        input = inputs[i];
+        this.getElement("#" + input).val(this.originalState[input]);
+      }
+      if (this.originalState._location === '') {
+        return this.locationBadgeEl.hide();
+      } else {
+        return this.addLocationBadge({
+          value: this.originalState._location
+        });
+      }
+    };
+
+    SearchAutocomplete.prototype.badgePresent = function() {
+      return this.locationBadgeEl.length;
+    };
+
+    SearchAutocomplete.prototype.resetSearchState = function() {
+      var i, input, inputs, len, results;
+      inputs = Object.keys(this.originalState);
+      results = [];
+      for (i = 0, len = inputs.length; i < len; i++) {
+        input = inputs[i];
+        if (input === '_location') {
+          break;
+        }
+        results.push(this.getElement("#" + input).val(''));
+      }
+      return results;
+    };
+
+    SearchAutocomplete.prototype.removeLocationBadge = function() {
+      this.locationBadgeEl.hide();
+      this.resetSearchState();
+      this.wrap.removeClass('has-location-badge');
+      return this.disableAutocomplete();
+    };
+
+    SearchAutocomplete.prototype.disableAutocomplete = function() {
+      this.searchInput.addClass('disabled');
+      this.dropdown.removeClass('open');
+      return this.restoreMenu();
+    };
+
+    SearchAutocomplete.prototype.restoreMenu = function() {
+      var html;
+      html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
+      return this.dropdownContent.html(html);
+    };
+
+    SearchAutocomplete.prototype.onClick = function(item, $el, e) {
+      if (location.pathname.indexOf(item.url) !== -1) {
+        e.preventDefault();
+        if (!this.badgePresent) {
+          if (item.category === 'Projects') {
+            this.projectInputEl.val(item.id);
+            this.addLocationBadge({
+              value: 'This project'
+            });
+          }
+          if (item.category === 'Groups') {
+            this.groupInputEl.val(item.id);
+            this.addLocationBadge({
+              value: 'This group'
+            });
+          }
+        }
+        $el.removeClass('is-active');
+        this.disableAutocomplete();
+        return this.searchInput.val('').focus();
+      }
+    };
+
+    return SearchAutocomplete;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
deleted file mode 100644
index 72b1d3dfb1e207be046d0b9a6aeb7ec51b9f32e6..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ /dev/null
@@ -1,334 +0,0 @@
-class @SearchAutocomplete
-
-  KEYCODE =
-    ESCAPE: 27
-    BACKSPACE: 8
-    ENTER: 13
-
-  constructor: (opts = {}) ->
-    {
-      @wrap = $('.search')
-
-      @optsEl = @wrap.find('.search-autocomplete-opts')
-      @autocompletePath = @optsEl.data('autocomplete-path')
-      @projectId = @optsEl.data('autocomplete-project-id') || ''
-      @projectRef = @optsEl.data('autocomplete-project-ref') || ''
-
-    } = opts
-
-    # Dropdown Element
-    @dropdown = @wrap.find('.dropdown')
-    @dropdownContent = @dropdown.find('.dropdown-content')
-
-    @locationBadgeEl = @getElement('.location-badge')
-    @scopeInputEl = @getElement('#scope')
-    @searchInput = @getElement('.search-input')
-    @projectInputEl = @getElement('#search_project_id')
-    @groupInputEl = @getElement('#group_id')
-    @searchCodeInputEl = @getElement('#search_code')
-    @repositoryInputEl = @getElement('#repository_ref')
-    @clearInput = @getElement('.js-clear-input')
-
-    @saveOriginalState()
-
-    # Only when user is logged in
-    @createAutocomplete() if gon.current_user_id
-
-    @searchInput.addClass('disabled')
-
-    @saveTextLength()
-
-    @bindEvents()
-
-  # Finds an element inside wrapper element
-  getElement: (selector) ->
-    @wrap.find(selector)
-
-  saveOriginalState: ->
-    @originalState = @serializeState()
-
-  saveTextLength: ->
-    @lastTextLength = @searchInput.val().length
-
-  createAutocomplete: ->
-    @searchInput.glDropdown
-        filterInputBlur: false
-        filterable: true
-        filterRemote: true
-        highlight: true
-        enterCallback: false
-        filterInput: 'input#search'
-        search:
-          fields: ['text']
-        data: @getData.bind(@)
-        selectable: true
-        clicked: @onClick.bind(@)
-
-  getData: (term, callback) ->
-    _this = @
-
-    unless term
-      if contents = @getCategoryContents()
-        @searchInput.data('glDropdown').filter.options.callback contents
-        @enableAutocomplete()
-
-      return
-
-    # Prevent multiple ajax calls
-    return if @loadingSuggestions
-
-    @loadingSuggestions = true
-
-    jqXHR = $.get(@autocompletePath, {
-        project_id: @projectId
-        project_ref: @projectRef
-        term: term
-      }, (response) ->
-        # Hide dropdown menu if no suggestions returns
-        if !response.length
-          _this.disableAutocomplete()
-          return
-
-        data = []
-
-        # List results
-        firstCategory = true
-        for suggestion in response
-
-          # Add group header before list each group
-          if lastCategory isnt suggestion.category
-            data.push 'separator' if !firstCategory
-
-            firstCategory = false if firstCategory
-
-            data.push
-              header: suggestion.category
-
-            lastCategory = suggestion.category
-
-          data.push
-            id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
-            category: suggestion.category
-            text: suggestion.label
-            url: suggestion.url
-
-        # Add option to proceed with the search
-        if data.length
-          data.push('separator')
-          data.push
-            text: "Result name contains \"#{term}\""
-            url: "/search?\
-                  search=#{term}\
-                  &project_id=#{_this.projectInputEl.val()}\
-                  &group_id=#{_this.groupInputEl.val()}"
-
-        callback(data)
-    ).always ->
-      _this.loadingSuggestions = false
-
-
-  getCategoryContents: ->
-
-    userId = gon.current_user_id
-    { utils, projectOptions, groupOptions, dashboardOptions } = gl
-
-    if utils.isInGroupsPage() and groupOptions
-      options = groupOptions[utils.getGroupSlug()]
-
-    else if utils.isInProjectPage() and projectOptions
-      options = projectOptions[utils.getProjectSlug()]
-
-    else if dashboardOptions
-      options = dashboardOptions
-
-    { issuesPath, mrPath, name } = options
-
-    items = [
-      { header: "#{name}" }
-      { text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" }
-      { text: "Issues I've created",   url: "#{issuesPath}/?author_id=#{userId}"   }
-      'separator'
-      { text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" }
-      { text: "Merge requests I've created",   url: "#{mrPath}/?author_id=#{userId}"   }
-    ]
-
-    items.splice 0, 1 unless name
-
-    return items
-
-
-  serializeState: ->
-    {
-      # Search Criteria
-      search_project_id: @projectInputEl.val()
-      group_id: @groupInputEl.val()
-      search_code: @searchCodeInputEl.val()
-      repository_ref: @repositoryInputEl.val()
-      scope: @scopeInputEl.val()
-
-      # Location badge
-      _location: @locationBadgeEl.text()
-    }
-
-  bindEvents: ->
-    @searchInput.on 'keydown', @onSearchInputKeyDown
-    @searchInput.on 'keyup', @onSearchInputKeyUp
-    @searchInput.on 'click', @onSearchInputClick
-    @searchInput.on 'focus', @onSearchInputFocus
-    @searchInput.on 'blur', @onSearchInputBlur
-    @clearInput.on 'click', @onClearInputClick
-    @locationBadgeEl.on 'click', =>
-      @searchInput.focus()
-
-  enableAutocomplete: ->
-    # No need to enable anything if user is not logged in
-    return if !gon.current_user_id
-
-    unless @dropdown.hasClass('open')
-      _this = @
-      @loadingSuggestions = false
-
-      @dropdown
-        .addClass('open')
-        .trigger('shown.bs.dropdown')
-      @searchInput.removeClass('disabled')
-
-  onSearchInputKeyDown: =>
-    # Saves last length of the entered text
-    @saveTextLength()
-
-  onSearchInputKeyUp: (e) =>
-    switch e.keyCode
-      when KEYCODE.BACKSPACE
-        # when trying to remove the location badge
-        if @lastTextLength is 0 and @badgePresent()
-            @removeLocationBadge()
-
-        # When removing the last character and no badge is present
-        if @lastTextLength is 1
-          @disableAutocomplete()
-
-        # When removing any character from existin value
-        if @lastTextLength > 1
-          @enableAutocomplete()
-
-      when KEYCODE.ESCAPE
-        @restoreOriginalState()
-
-      else
-        # Handle the case when deleting the input value other than backspace
-        # e.g. Pressing ctrl + backspace or ctrl + x
-        if @searchInput.val() is ''
-          @disableAutocomplete()
-        else
-          # We should display the menu only when input is not empty
-          @enableAutocomplete() if e.keyCode isnt KEYCODE.ENTER
-
-    @wrap.toggleClass 'has-value', !!e.target.value
-
-    # Avoid falsy value to be returned
-    return
-
-  onSearchInputClick: (e) =>
-    # Prevents closing the dropdown menu
-    e.stopImmediatePropagation()
-
-  onSearchInputFocus: =>
-    @isFocused = true
-    @wrap.addClass('search-active')
-
-    @getData()  if @getValue() is ''
-
-
-  getValue: -> return @searchInput.val()
-
-
-  onClearInputClick: (e) =>
-    e.preventDefault()
-    @searchInput.val('').focus()
-
-  onSearchInputBlur: (e) =>
-    @isFocused = false
-    @wrap.removeClass('search-active')
-
-    # If input is blank then restore state
-    if @searchInput.val() is ''
-      @restoreOriginalState()
-
-  addLocationBadge: (item) ->
-    category = if item.category? then "#{item.category}: " else ''
-    value = if item.value? then item.value else ''
-
-    badgeText = "#{category}#{value}"
-    @locationBadgeEl.text(badgeText).show()
-    @wrap.addClass('has-location-badge')
-
-
-  hasLocationBadge: -> return @wrap.is '.has-location-badge'
-
-
-  restoreOriginalState: ->
-    inputs = Object.keys @originalState
-
-    for input in inputs
-      @getElement("##{input}").val(@originalState[input])
-
-    if @originalState._location is ''
-      @locationBadgeEl.hide()
-    else
-      @addLocationBadge(
-        value: @originalState._location
-      )
-
-  badgePresent: ->
-    @locationBadgeEl.length
-
-  resetSearchState: ->
-    inputs = Object.keys @originalState
-
-    for input in inputs
-
-      # _location isnt a input
-      break if input is '_location'
-
-      @getElement("##{input}").val('')
-
-
-  removeLocationBadge: ->
-
-    @locationBadgeEl.hide()
-    @resetSearchState()
-    @wrap.removeClass('has-location-badge')
-    @disableAutocomplete()
-
-
-  disableAutocomplete: ->
-    @searchInput.addClass('disabled')
-    @dropdown.removeClass('open')
-    @restoreMenu()
-
-  restoreMenu: ->
-    html = "<ul>
-              <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
-            </ul>"
-    @dropdownContent.html(html)
-
-  onClick: (item, $el, e) ->
-    if location.pathname.indexOf(item.url) isnt -1
-      e.preventDefault()
-      if not @badgePresent
-        if item.category is 'Projects'
-          @projectInputEl.val(item.id)
-          @addLocationBadge(
-            value: 'This project'
-          )
-
-        if item.category is 'Groups'
-          @groupInputEl.val(item.id)
-          @addLocationBadge(
-            value: 'This group'
-          )
-
-      $el.removeClass('is-active')
-      @disableAutocomplete()
-      @searchInput.val('').focus()
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b28332854a0ad0b2651e730c6bac236bab59314
--- /dev/null
+++ b/app/assets/javascripts/shortcuts.js
@@ -0,0 +1,97 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Shortcuts = (function() {
+    function Shortcuts(skipResetBindings) {
+      this.onToggleHelp = bind(this.onToggleHelp, this);
+      this.enabledHelp = [];
+      if (!skipResetBindings) {
+        Mousetrap.reset();
+      }
+      Mousetrap.bind('?', this.onToggleHelp);
+      Mousetrap.bind('s', Shortcuts.focusSearch);
+      Mousetrap.bind('f', (function(_this) {
+        return function(e) {
+          return _this.focusFilter(e);
+        };
+      })(this));
+      Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
+      if (typeof findFileURL !== "undefined" && findFileURL !== null) {
+        Mousetrap.bind('t', function() {
+          return Turbolinks.visit(findFileURL);
+        });
+      }
+    }
+
+    Shortcuts.prototype.onToggleHelp = function(e) {
+      e.preventDefault();
+      return Shortcuts.toggleHelp(this.enabledHelp);
+    };
+
+    Shortcuts.prototype.toggleMarkdownPreview = function(e) {
+      return $(document).triggerHandler('markdown-preview:toggle', [e]);
+    };
+
+    Shortcuts.toggleHelp = function(location) {
+      var $modal;
+      $modal = $('#modal-shortcuts');
+      if ($modal.length) {
+        $modal.modal('toggle');
+        return;
+      }
+      return $.ajax({
+        url: gon.shortcuts_path,
+        dataType: 'script',
+        success: function(e) {
+          var i, l, len, results;
+          if (location && location.length > 0) {
+            results = [];
+            for (i = 0, len = location.length; i < len; i++) {
+              l = location[i];
+              results.push($(l).show());
+            }
+            return results;
+          } else {
+            $('.hidden-shortcut').show();
+            return $('.js-more-help-button').remove();
+          }
+        }
+      });
+    };
+
+    Shortcuts.prototype.focusFilter = function(e) {
+      if (this.filterInput == null) {
+        this.filterInput = $('input[type=search]', '.nav-controls');
+      }
+      this.filterInput.focus();
+      return e.preventDefault();
+    };
+
+    Shortcuts.focusSearch = function(e) {
+      $('#search').focus();
+      return e.preventDefault();
+    };
+
+    return Shortcuts;
+
+  })();
+
+  $(document).on('click.more_help', '.js-more-help-button', function(e) {
+    $(this).remove();
+    $('.hidden-shortcut').show();
+    return e.preventDefault();
+  });
+
+  Mousetrap.stopCallback = (function() {
+    var defaultStopCallback;
+    defaultStopCallback = Mousetrap.stopCallback;
+    return function(e, element, combo) {
+      if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
+        return false;
+      } else {
+        return defaultStopCallback.apply(this, arguments);
+      }
+    };
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
deleted file mode 100644
index 8c8689baceed9f5672eb4480b5aeb0b17c32cf55..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ /dev/null
@@ -1,60 +0,0 @@
-class @Shortcuts
-  constructor: (skipResetBindings) ->
-    @enabledHelp = []
-    Mousetrap.reset() if not skipResetBindings
-    Mousetrap.bind '?', @onToggleHelp
-    Mousetrap.bind 's', Shortcuts.focusSearch
-    Mousetrap.bind 'f', (e) => @focusFilter e
-    Mousetrap.bind ['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview
-    Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
-
-  onToggleHelp: (e) =>
-    e.preventDefault()
-    Shortcuts.toggleHelp(@enabledHelp)
-
-  toggleMarkdownPreview: (e) ->
-    $(document).triggerHandler('markdown-preview:toggle', [e])
-
-  @toggleHelp: (location) ->
-    $modal = $('#modal-shortcuts')
-
-    if $modal.length
-      $modal.modal('toggle')
-      return
-
-    $.ajax(
-      url: gon.shortcuts_path,
-      dataType: 'script',
-      success: (e) ->
-        if location and location.length > 0
-          $(l).show() for l in location
-        else
-          $('.hidden-shortcut').show()
-          $('.js-more-help-button').remove()
-    )
-
-  focusFilter: (e) ->
-    @filterInput ?= $('input[type=search]', '.nav-controls')
-    @filterInput.focus()
-    e.preventDefault()
-
-  @focusSearch: (e) ->
-    $('#search').focus()
-    e.preventDefault()
-
-
-$(document).on 'click.more_help', '.js-more-help-button', (e) ->
-  $(@).remove()
-  $('.hidden-shortcut').show()
-  e.preventDefault()
-
-Mousetrap.stopCallback = (->
-  defaultStopCallback = Mousetrap.stopCallback
-
-  return (e, element, combo) ->
-    # allowed shortcuts if textarea, input, contenteditable are focused
-    if ['ctrl+shift+p', 'command+shift+p'].indexOf(combo) != -1
-      return false
-    else
-      return defaultStopCallback.apply(@, arguments)
-)()
diff --git a/app/assets/javascripts/shortcuts_blob.coffee b/app/assets/javascripts/shortcuts_blob.coffee
deleted file mode 100644
index 6d21e5d115023803648b275edc7686d5a9ff3caa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_blob.coffee
+++ /dev/null
@@ -1,10 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsBlob extends Shortcuts
-  constructor: (skipResetBindings) ->
-    super skipResetBindings
-    Mousetrap.bind('y', ShortcutsBlob.copyToClipboard)
-
-  @copyToClipboard: ->
-    clipboardButton = $('.btn-clipboard')
-    clipboardButton.click() if clipboardButton
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
new file mode 100644
index 0000000000000000000000000000000000000000..b931eab638f2d1dc3ea4dcae387b5652b7b1ea98
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -0,0 +1,28 @@
+
+/*= require shortcuts */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsBlob = (function(superClass) {
+    extend(ShortcutsBlob, superClass);
+
+    function ShortcutsBlob(skipResetBindings) {
+      ShortcutsBlob.__super__.constructor.call(this, skipResetBindings);
+      Mousetrap.bind('y', ShortcutsBlob.copyToClipboard);
+    }
+
+    ShortcutsBlob.copyToClipboard = function() {
+      var clipboardButton;
+      clipboardButton = $('.btn-clipboard');
+      if (clipboardButton) {
+        return clipboardButton.click();
+      }
+    };
+
+    return ShortcutsBlob;
+
+  })(Shortcuts);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
new file mode 100644
index 0000000000000000000000000000000000000000..f7492a2aa5c8e6bd7a75e222d0c430d87d144e34
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -0,0 +1,39 @@
+
+/*= require shortcuts */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsDashboardNavigation = (function(superClass) {
+    extend(ShortcutsDashboardNavigation, superClass);
+
+    function ShortcutsDashboardNavigation() {
+      ShortcutsDashboardNavigation.__super__.constructor.call(this);
+      Mousetrap.bind('g a', function() {
+        return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity');
+      });
+      Mousetrap.bind('g i', function() {
+        return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues');
+      });
+      Mousetrap.bind('g m', function() {
+        return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests');
+      });
+      Mousetrap.bind('g p', function() {
+        return ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects');
+      });
+    }
+
+    ShortcutsDashboardNavigation.findAndFollowLink = function(selector) {
+      var link;
+      link = $(selector).attr('href');
+      if (link) {
+        return window.location = link;
+      }
+    };
+
+    return ShortcutsDashboardNavigation;
+
+  })(Shortcuts);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
deleted file mode 100644
index cca2b8a1fccadfe79f41c2ed8895b3e2e5cdacfd..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee
+++ /dev/null
@@ -1,14 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsDashboardNavigation extends Shortcuts
- constructor: ->
-   super()
-   Mousetrap.bind('g a', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-activity'))
-   Mousetrap.bind('g i', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-issues'))
-   Mousetrap.bind('g m', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-merge_requests'))
-   Mousetrap.bind('g p', -> ShortcutsDashboardNavigation.findAndFollowLink('.dashboard-shortcuts-projects'))
-
- @findAndFollowLink: (selector) ->
-   link = $(selector).attr('href')
-   if link
-     window.location = link
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c78914d3386dd56159fafa8232cf89d10f77d0b
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -0,0 +1,35 @@
+
+/*= require shortcuts_navigation */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsFindFile = (function(superClass) {
+    extend(ShortcutsFindFile, superClass);
+
+    function ShortcutsFindFile(projectFindFile) {
+      var _oldStopCallback;
+      this.projectFindFile = projectFindFile;
+      ShortcutsFindFile.__super__.constructor.call(this);
+      _oldStopCallback = Mousetrap.stopCallback;
+      Mousetrap.stopCallback = (function(_this) {
+        return function(event, element, combo) {
+          if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) {
+            event.preventDefault();
+            return false;
+          }
+          return _oldStopCallback(event, element, combo);
+        };
+      })(this);
+      Mousetrap.bind('up', this.projectFindFile.selectRowUp);
+      Mousetrap.bind('down', this.projectFindFile.selectRowDown);
+      Mousetrap.bind('esc', this.projectFindFile.goToTree);
+      Mousetrap.bind('enter', this.projectFindFile.goToBlob);
+    }
+
+    return ShortcutsFindFile;
+
+  })(ShortcutsNavigation);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_find_file.js.coffee b/app/assets/javascripts/shortcuts_find_file.js.coffee
deleted file mode 100644
index 311e80bae19e1bc048de907c53a8f13905137ed5..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_find_file.js.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsFindFile extends ShortcutsNavigation
-  constructor: (@projectFindFile) ->
-    super()
-    _oldStopCallback = Mousetrap.stopCallback
-    # override to fire shortcuts action when focus in textbox
-    Mousetrap.stopCallback = (event, element, combo) =>
-      if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter')
-        # when press up/down key in textbox, cusor prevent to move to home/end
-        event.preventDefault()
-        return false
-
-      return _oldStopCallback(event, element, combo)
-
-    Mousetrap.bind('up', @projectFindFile.selectRowUp)
-    Mousetrap.bind('down', @projectFindFile.selectRowDown)
-    Mousetrap.bind('esc', @projectFindFile.goToTree)
-    Mousetrap.bind('enter', @projectFindFile.goToBlob)
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
deleted file mode 100644
index c93bcf3ceec4f7f6c566eaa2e92285792d9ef967..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ /dev/null
@@ -1,53 +0,0 @@
-#= require mousetrap
-#= require shortcuts_navigation
-
-class @ShortcutsIssuable extends ShortcutsNavigation
-  constructor: (isMergeRequest) ->
-    super()
-    Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
-    Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
-    Mousetrap.bind('r', =>
-      @replyWithSelectedText()
-      return false
-    )
-    Mousetrap.bind('e', =>
-      @editIssue()
-      return false
-    )
-    Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
-
-    if isMergeRequest
-      @enabledHelp.push('.hidden-shortcut.merge_requests')
-    else
-      @enabledHelp.push('.hidden-shortcut.issues')
-
-  replyWithSelectedText: ->
-    if window.getSelection
-      selected = window.getSelection().toString()
-      replyField = $('.js-main-target-form #note_note')
-
-      return if selected.trim() == ""
-
-      # Put a '>' character before each non-empty line in the selection
-      quote = _.map selected.split("\n"), (val) ->
-        "> #{val}\n" if val.trim() != ''
-
-      # If replyField already has some content, add a newline before our quote
-      separator = replyField.val().trim() != "" and "\n" or ''
-
-      replyField.val (_, current) ->
-        current + separator + quote.join('') + "\n"
-
-      # Trigger autosave for the added text
-      replyField.trigger('input')
-
-      # Focus the input field
-      replyField.focus()
-
-  editIssue: ->
-    $editBtn = $('.issuable-edit')
-    Turbolinks.visit($editBtn.attr('href'))
-
-  openSidebarDropdown: (name) ->
-    sidebar.openDropdown(name)
-    return false
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f3a8a9dfd9cbf70573499436c6aa135c5f2bc67
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -0,0 +1,75 @@
+
+/*= require mousetrap */
+
+
+/*= require shortcuts_navigation */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsIssuable = (function(superClass) {
+    extend(ShortcutsIssuable, superClass);
+
+    function ShortcutsIssuable(isMergeRequest) {
+      ShortcutsIssuable.__super__.constructor.call(this);
+      Mousetrap.bind('a', this.openSidebarDropdown.bind(this, 'assignee'));
+      Mousetrap.bind('m', this.openSidebarDropdown.bind(this, 'milestone'));
+      Mousetrap.bind('r', (function(_this) {
+        return function() {
+          _this.replyWithSelectedText();
+          return false;
+        };
+      })(this));
+      Mousetrap.bind('e', (function(_this) {
+        return function() {
+          _this.editIssue();
+          return false;
+        };
+      })(this));
+      Mousetrap.bind('l', this.openSidebarDropdown.bind(this, 'labels'));
+      if (isMergeRequest) {
+        this.enabledHelp.push('.hidden-shortcut.merge_requests');
+      } else {
+        this.enabledHelp.push('.hidden-shortcut.issues');
+      }
+    }
+
+    ShortcutsIssuable.prototype.replyWithSelectedText = function() {
+      var quote, replyField, selected, separator;
+      if (window.getSelection) {
+        selected = window.getSelection().toString();
+        replyField = $('.js-main-target-form #note_note');
+        if (selected.trim() === "") {
+          return;
+        }
+        quote = _.map(selected.split("\n"), function(val) {
+          if (val.trim() !== '') {
+            return "> " + val + "\n";
+          }
+        });
+        separator = replyField.val().trim() !== "" && "\n" || '';
+        replyField.val(function(_, current) {
+          return current + separator + quote.join('') + "\n";
+        });
+        replyField.trigger('input');
+        return replyField.focus();
+      }
+    };
+
+    ShortcutsIssuable.prototype.editIssue = function() {
+      var $editBtn;
+      $editBtn = $('.issuable-edit');
+      return Turbolinks.visit($editBtn.attr('href'));
+    };
+
+    ShortcutsIssuable.prototype.openSidebarDropdown = function(name) {
+      sidebar.openDropdown(name);
+      return false;
+    };
+
+    return ShortcutsIssuable;
+
+  })(ShortcutsNavigation);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
deleted file mode 100644
index f39504e0645693a4acf2f71115a81839be325e87..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require shortcuts
-
-class @ShortcutsNavigation extends Shortcuts
-  constructor: ->
-    super()
-    Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
-    Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
-    Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
-    Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
-    Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
-    Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
-    Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
-    Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
-    Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
-    Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
-    Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
-    Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
-    @enabledHelp.push('.hidden-shortcut.project')
-
-  @findAndFollowLink: (selector) ->
-   link = $(selector).attr('href')
-   if link
-     window.location = link
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
new file mode 100644
index 0000000000000000000000000000000000000000..469e25482bbcbcd84d15eadf8d38b29216a89d7c
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -0,0 +1,64 @@
+
+/*= require shortcuts */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsNavigation = (function(superClass) {
+    extend(ShortcutsNavigation, superClass);
+
+    function ShortcutsNavigation() {
+      ShortcutsNavigation.__super__.constructor.call(this);
+      Mousetrap.bind('g p', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-project');
+      });
+      Mousetrap.bind('g e', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity');
+      });
+      Mousetrap.bind('g f', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-tree');
+      });
+      Mousetrap.bind('g c', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-commits');
+      });
+      Mousetrap.bind('g b', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-builds');
+      });
+      Mousetrap.bind('g n', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-network');
+      });
+      Mousetrap.bind('g g', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs');
+      });
+      Mousetrap.bind('g i', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
+      });
+      Mousetrap.bind('g m', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
+      });
+      Mousetrap.bind('g w', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki');
+      });
+      Mousetrap.bind('g s', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets');
+      });
+      Mousetrap.bind('i', function() {
+        return ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue');
+      });
+      this.enabledHelp.push('.hidden-shortcut.project');
+    }
+
+    ShortcutsNavigation.findAndFollowLink = function(selector) {
+      var link;
+      link = $(selector).attr('href');
+      if (link) {
+        return window.location = link;
+      }
+    };
+
+    return ShortcutsNavigation;
+
+  })(Shortcuts);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js
new file mode 100644
index 0000000000000000000000000000000000000000..fb2b39e757e7f2445f9de5a0968c5df123787e4e
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_network.js
@@ -0,0 +1,27 @@
+
+/*= require shortcuts_navigation */
+
+(function() {
+  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+    hasProp = {}.hasOwnProperty;
+
+  this.ShortcutsNetwork = (function(superClass) {
+    extend(ShortcutsNetwork, superClass);
+
+    function ShortcutsNetwork(graph) {
+      this.graph = graph;
+      ShortcutsNetwork.__super__.constructor.call(this);
+      Mousetrap.bind(['left', 'h'], this.graph.scrollLeft);
+      Mousetrap.bind(['right', 'l'], this.graph.scrollRight);
+      Mousetrap.bind(['up', 'k'], this.graph.scrollUp);
+      Mousetrap.bind(['down', 'j'], this.graph.scrollDown);
+      Mousetrap.bind(['shift+up', 'shift+k'], this.graph.scrollTop);
+      Mousetrap.bind(['shift+down', 'shift+j'], this.graph.scrollBottom);
+      this.enabledHelp.push('.hidden-shortcut.network');
+    }
+
+    return ShortcutsNetwork;
+
+  })(ShortcutsNavigation);
+
+}).call(this);
diff --git a/app/assets/javascripts/shortcuts_network.js.coffee b/app/assets/javascripts/shortcuts_network.js.coffee
deleted file mode 100644
index cc95ad7ebfed105d114e61f7f941083c3c1207ad..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/shortcuts_network.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsNetwork extends ShortcutsNavigation
-  constructor: (@graph) ->
-    super()
-    Mousetrap.bind(['left', 'h'], @graph.scrollLeft)
-    Mousetrap.bind(['right', 'l'], @graph.scrollRight)
-    Mousetrap.bind(['up', 'k'], @graph.scrollUp)
-    Mousetrap.bind(['down', 'j'], @graph.scrollDown)
-    Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop)
-    Mousetrap.bind(['shift+down', 'shift+j'],  @graph.scrollBottom)
-    @enabledHelp.push('.hidden-shortcut.network')
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd0c1194b361ea5f0e3c177d6d48ebf96ecda335
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js
@@ -0,0 +1,41 @@
+(function() {
+  var collapsed, expanded, toggleSidebar;
+
+  collapsed = 'page-sidebar-collapsed';
+
+  expanded = 'page-sidebar-expanded';
+
+  toggleSidebar = function() {
+    $('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
+    $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
+    if ($.cookie('pin_nav') === 'true') {
+      $('.navbar-fixed-top').toggleClass('header-pinned-nav');
+      $('.page-with-sidebar').toggleClass('page-sidebar-pinned');
+    }
+    return setTimeout((function() {
+      var niceScrollBars;
+      niceScrollBars = $('.nav-sidebar').niceScroll();
+      return niceScrollBars.updateScrollBar();
+    }), 300);
+  };
+
+  $(document).off('click', 'body').on('click', 'body', function(e) {
+    var $nav, $target, $toggle, pageExpanded;
+    if ($.cookie('pin_nav') !== 'true') {
+      $target = $(e.target);
+      $nav = $target.closest('.sidebar-wrapper');
+      pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
+      $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
+      if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
+        $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
+        return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
+      }
+    }
+  });
+
+  $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
+    e.preventDefault();
+    return toggleSidebar();
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
deleted file mode 100644
index 68009e586452cfdbce7adf185bfc6bed39289d2a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/sidebar.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-collapsed = 'page-sidebar-collapsed'
-expanded = 'page-sidebar-expanded'
-
-toggleSidebar = ->
-  $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
-  $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded")
-
-  if $.cookie('pin_nav') is 'true'
-    $('.navbar-fixed-top').toggleClass('header-pinned-nav')
-    $('.page-with-sidebar').toggleClass('page-sidebar-pinned')
-
-  setTimeout ( ->
-    niceScrollBars = $('.nav-sidebar').niceScroll();
-    niceScrollBars.updateScrollBar();
-  ), 300
-
-$(document)
-  .off 'click', 'body'
-  .on 'click', 'body', (e) ->
-    unless $.cookie('pin_nav') is 'true'
-      $target = $(e.target)
-      $nav = $target.closest('.sidebar-wrapper')
-      pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
-      $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle')
-
-      if $nav.length is 0 and pageExpanded and $toggle.length is 0
-        $('.page-with-sidebar')
-          .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
-
-        $('.navbar-fixed-top')
-          .toggleClass('header-collapsed header-expanded')
-
-$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
-  e.preventDefault()
-
-  toggleSidebar()
-)
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
new file mode 100644
index 0000000000000000000000000000000000000000..b9ae497b0e598e132c098dce19188d9cc4005ec0
--- /dev/null
+++ b/app/assets/javascripts/single_file_diff.js
@@ -0,0 +1,77 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.SingleFileDiff = (function() {
+    var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+
+    WRAPPER = '<div class="diff-content diff-wrap-lines"></div>';
+
+    LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
+
+    ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+
+    COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>';
+
+    function SingleFileDiff(file) {
+      this.file = file;
+      this.toggleDiff = bind(this.toggleDiff, this);
+      this.content = $('.diff-content', this.file);
+      this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
+      this.isOpen = !this.diffForPath;
+      if (this.diffForPath) {
+        this.collapsedContent = this.content;
+        this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+        this.content = null;
+        this.collapsedContent.after(this.loadingContent);
+      } else {
+        this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+        this.content.after(this.collapsedContent);
+      }
+      this.collapsedContent.on('click', this.toggleDiff);
+      $('.file-title > a', this.file).on('click', this.toggleDiff);
+    }
+
+    SingleFileDiff.prototype.toggleDiff = function(e) {
+      this.isOpen = !this.isOpen;
+      if (!this.isOpen && !this.hasError) {
+        this.content.hide();
+        return this.collapsedContent.show();
+      } else if (this.content) {
+        this.collapsedContent.hide();
+        return this.content.show();
+      } else {
+        return this.getContentHTML();
+      }
+    };
+
+    SingleFileDiff.prototype.getContentHTML = function() {
+      this.collapsedContent.hide();
+      this.loadingContent.show();
+      $.get(this.diffForPath, (function(_this) {
+        return function(data) {
+          _this.loadingContent.hide();
+          if (data.html) {
+            _this.content = $(data.html);
+            _this.content.syntaxHighlight();
+          } else {
+            _this.hasError = true;
+            _this.content = $(ERROR_HTML);
+          }
+          return _this.collapsedContent.after(_this.content);
+        };
+      })(this));
+    };
+
+    return SingleFileDiff;
+
+  })();
+
+  $.fn.singleFileDiff = function() {
+    return this.each(function() {
+      if (!$.data(this, 'singleFileDiff')) {
+        return $.data(this, 'singleFileDiff', new SingleFileDiff(this));
+      }
+    });
+  };
+
+}).call(this);
diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee
deleted file mode 100644
index f3e225c37287cc1773896284115403c6bdc1d7b8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/single_file_diff.js.coffee
+++ /dev/null
@@ -1,54 +0,0 @@
-class @SingleFileDiff
-
-  WRAPPER = '<div class="diff-content diff-wrap-lines"></div>'
-  LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'
-  ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'
-  COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'
-
-  constructor: (@file) ->
-    @content = $('.diff-content', @file)
-    @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path'
-    @isOpen = !@diffForPath
-
-    if @diffForPath
-      @collapsedContent = @content
-      @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide()
-      @content = null
-      @collapsedContent.after(@loadingContent)
-    else
-      @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide()
-      @content.after(@collapsedContent)
-
-    @collapsedContent.on 'click', @toggleDiff
-
-    $('.file-title > a', @file).on 'click', @toggleDiff
-
-  toggleDiff: (e) =>
-    @isOpen = !@isOpen
-    if not @isOpen and not @hasError
-      @content.hide()
-      @collapsedContent.show()
-    else if @content
-      @collapsedContent.hide()
-      @content.show()
-    else
-      @getContentHTML()
-
-  getContentHTML: ->
-    @collapsedContent.hide()
-    @loadingContent.show()
-    $.get @diffForPath, (data) =>
-      @loadingContent.hide()
-      if data.html
-        @content = $(data.html)
-        @content.syntaxHighlight()
-      else
-        @hasError = true
-        @content = $(ERROR_HTML)
-      @collapsedContent.after(@content)
-    return
-
-$.fn.singleFileDiff = ->
-  return @each ->
-    if not $.data this, 'singleFileDiff'
-      $.data this, 'singleFileDiff', new SingleFileDiff this
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
new file mode 100644
index 0000000000000000000000000000000000000000..10509313c12d7524640bc6700311bbcc92b8584e
--- /dev/null
+++ b/app/assets/javascripts/star.js
@@ -0,0 +1,31 @@
+(function() {
+  this.Star = (function() {
+    function Star() {
+      $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
+        var $starIcon, $starSpan, $this, toggleStar;
+        $this = $(this);
+        $starSpan = $this.find('span');
+        $starIcon = $this.find('i');
+        toggleStar = function(isStarred) {
+          $this.parent().find('.star-count').text(data.star_count);
+          if (isStarred) {
+            $starSpan.removeClass('starred').text('Star');
+            gl.utils.updateTooltipTitle($this, 'Star project');
+            $starIcon.removeClass('fa-star').addClass('fa-star-o');
+          } else {
+            $starSpan.addClass('starred').text('Unstar');
+            gl.utils.updateTooltipTitle($this, 'Unstar project');
+            $starIcon.removeClass('fa-star-o').addClass('fa-star');
+          }
+        };
+        toggleStar($starSpan.hasClass('starred'));
+      }).on('ajax:error', function(e, xhr, status, error) {
+        new Flash('Star toggle failed. Try again later.', 'alert');
+      });
+    }
+
+    return Star;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee
deleted file mode 100644
index 01b28171f72218bac33288f2acd07a330a2039f8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/star.js.coffee
+++ /dev/null
@@ -1,24 +0,0 @@
-class @Star
-  constructor: ->
-    $('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) ->
-      $this = $(this)
-      $starSpan = $this.find('span')
-      $starIcon = $this.find('i')
-
-      toggleStar = (isStarred) ->
-        $this.parent().find('.star-count').text data.star_count
-        if isStarred
-          $starSpan.removeClass('starred').text 'Star'
-          gl.utils.updateTooltipTitle $this, 'Star project'
-          $starIcon.removeClass('fa-star').addClass 'fa-star-o'
-        else
-          $starSpan.addClass('starred').text 'Unstar'
-          gl.utils.updateTooltipTitle $this, 'Unstar project'
-          $starIcon.removeClass('fa-star-o').addClass 'fa-star'
-        return
-
-      toggleStar $starSpan.hasClass('starred')
-      return
-    ).on 'ajax:error', (e, xhr, status, error) ->
-      new Flash('Star toggle failed. Try again later.', 'alert')
-      return
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
new file mode 100644
index 0000000000000000000000000000000000000000..5e3c5983d754e9b16a93edcf0596914a257ec412
--- /dev/null
+++ b/app/assets/javascripts/subscription.js
@@ -0,0 +1,41 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Subscription = (function() {
+    function Subscription(container) {
+      this.toggleSubscription = bind(this.toggleSubscription, this);
+      var $container;
+      $container = $(container);
+      this.url = $container.attr('data-url');
+      this.subscribe_button = $container.find('.js-subscribe-button');
+      this.subscription_status = $container.find('.subscription-status');
+      this.subscribe_button.unbind('click').click(this.toggleSubscription);
+    }
+
+    Subscription.prototype.toggleSubscription = function(event) {
+      var action, btn, current_status;
+      btn = $(event.currentTarget);
+      action = btn.find('span').text();
+      current_status = this.subscription_status.attr('data-status');
+      btn.addClass('disabled');
+      return $.post(this.url, (function(_this) {
+        return function() {
+          var status;
+          btn.removeClass('disabled');
+          status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
+          _this.subscription_status.attr('data-status', status);
+          action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
+          btn.find('span').text(action);
+          _this.subscription_status.find('>div').toggleClass('hidden');
+          if (btn.attr('data-original-title')) {
+            return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
+          }
+        };
+      })(this));
+    };
+
+    return Subscription;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
deleted file mode 100644
index 08d494aba9fdb2129c9a4327b46882ee5f4be178..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/subscription.js.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-class @Subscription
-  constructor: (container) ->
-    $container = $(container)
-    @url = $container.attr('data-url')
-    @subscribe_button = $container.find('.js-subscribe-button')
-    @subscription_status = $container.find('.subscription-status')
-    @subscribe_button.unbind('click').click(@toggleSubscription)
-
-  toggleSubscription: (event) =>
-    btn = $(event.currentTarget)
-    action = btn.find('span').text()
-    current_status = @subscription_status.attr('data-status')
-    btn.addClass('disabled')
-
-    $.post @url, =>
-      btn.removeClass('disabled')
-      status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
-      @subscription_status.attr('data-status', status)
-      action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
-      btn.find('span').text(action)
-      @subscription_status.find('>div').toggleClass('hidden')
-
-      if btn.attr('data-original-title')
-        btn.tooltip('hide')
-          .attr('data-original-title', action)
-          .tooltip('fixTitle')
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..d6c219603d103f3cfa56b9b445312f584e6e247e
--- /dev/null
+++ b/app/assets/javascripts/subscription_select.js
@@ -0,0 +1,35 @@
+(function() {
+  this.SubscriptionSelect = (function() {
+    function SubscriptionSelect() {
+      $('.js-subscription-event').each(function(i, el) {
+        var fieldName;
+        fieldName = $(el).data("field-name");
+        return $(el).glDropdown({
+          selectable: true,
+          fieldName: fieldName,
+          toggleLabel: (function(_this) {
+            return function(selected, el, instance) {
+              var $item, label;
+              label = 'Subscription';
+              $item = instance.dropdown.find('.is-active');
+              if ($item.length) {
+                label = $item.text();
+              }
+              return label;
+            };
+          })(this),
+          clicked: function(item, $el, e) {
+            return e.preventDefault();
+          },
+          id: function(obj, el) {
+            return $(el).data("id");
+          }
+        });
+      });
+    }
+
+    return SubscriptionSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/subscription_select.js.coffee b/app/assets/javascripts/subscription_select.js.coffee
deleted file mode 100644
index e5eb7a50d803351557579aade6834be2badde712..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/subscription_select.js.coffee
+++ /dev/null
@@ -1,18 +0,0 @@
-class @SubscriptionSelect
-  constructor: ->
-    $('.js-subscription-event').each (i, el) ->
-      fieldName = $(el).data("field-name")
-
-      $(el).glDropdown(
-        selectable: true
-        fieldName: fieldName
-        toggleLabel: (selected, el, instance) =>
-          label = 'Subscription'
-          $item = instance.dropdown.find('.is-active')
-          label = $item.text() if $item.length
-          label
-        clicked: (item, $el, e)->
-          e.preventDefault()
-        id: (obj, el) ->
-          $(el).data("id")
-      )
diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee
deleted file mode 100644
index 980f0232d101a8efe4722f131197991d66e7499a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/syntax_highlight.coffee
+++ /dev/null
@@ -1,20 +0,0 @@
-# Syntax Highlighter
-#
-# Applies a syntax highlighting color scheme CSS class to any element with the
-# `js-syntax-highlight` class
-#
-# ### Example Markup
-#
-#   <div class="js-syntax-highlight"></div>
-#
-$.fn.syntaxHighlight = ->
-  if $(this).hasClass('js-syntax-highlight')
-    # Given the element itself, apply highlighting
-    $(this).addClass(gon.user_color_scheme)
-  else
-    # Given a parent element, recurse to any of its applicable children
-    $children = $(this).find('.js-syntax-highlight')
-    $children.syntaxHighlight() if $children.length
-
-$(document).on 'ready page:load', ->
-  $('.js-syntax-highlight').syntaxHighlight()
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
new file mode 100644
index 0000000000000000000000000000000000000000..dba62638c78143bb71f79eb7fd3a468ae4a219fb
--- /dev/null
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -0,0 +1,18 @@
+(function() {
+  $.fn.syntaxHighlight = function() {
+    var $children;
+    if ($(this).hasClass('js-syntax-highlight')) {
+      return $(this).addClass(gon.user_color_scheme);
+    } else {
+      $children = $(this).find('.js-syntax-highlight');
+      if ($children.length) {
+        return $children.syntaxHighlight();
+      }
+    }
+  };
+
+  $(document).on('ready page:load', function() {
+    return $('.js-syntax-highlight').syntaxHighlight();
+  });
+
+}).call(this);
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e677fa8cc6907a6349d58d6571d5f777f3a13cd
--- /dev/null
+++ b/app/assets/javascripts/todos.js
@@ -0,0 +1,144 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Todos = (function() {
+    function Todos(opts) {
+      var ref;
+      if (opts == null) {
+        opts = {};
+      }
+      this.allDoneClicked = bind(this.allDoneClicked, this);
+      this.doneClicked = bind(this.doneClicked, this);
+      this.el = (ref = opts.el) != null ? ref : $('.js-todos-options');
+      this.perPage = this.el.data('perPage');
+      this.clearListeners();
+      this.initBtnListeners();
+    }
+
+    Todos.prototype.clearListeners = function() {
+      $('.done-todo').off('click');
+      $('.js-todos-mark-all').off('click');
+      return $('.todo').off('click');
+    };
+
+    Todos.prototype.initBtnListeners = function() {
+      $('.done-todo').on('click', this.doneClicked);
+      $('.js-todos-mark-all').on('click', this.allDoneClicked);
+      return $('.todo').on('click', this.goToTodoUrl);
+    };
+
+    Todos.prototype.doneClicked = function(e) {
+      var $this;
+      e.preventDefault();
+      e.stopImmediatePropagation();
+      $this = $(e.currentTarget);
+      $this.disable();
+      return $.ajax({
+        type: 'POST',
+        url: $this.attr('href'),
+        dataType: 'json',
+        data: {
+          '_method': 'delete'
+        },
+        success: (function(_this) {
+          return function(data) {
+            _this.redirectIfNeeded(data.count);
+            _this.clearDone($this.closest('li'));
+            return _this.updateBadges(data);
+          };
+        })(this)
+      });
+    };
+
+    Todos.prototype.allDoneClicked = function(e) {
+      var $this;
+      e.preventDefault();
+      e.stopImmediatePropagation();
+      $this = $(e.currentTarget);
+      $this.disable();
+      return $.ajax({
+        type: 'POST',
+        url: $this.attr('href'),
+        dataType: 'json',
+        data: {
+          '_method': 'delete'
+        },
+        success: (function(_this) {
+          return function(data) {
+            $this.remove();
+            $('.js-todos-list').remove();
+            return _this.updateBadges(data);
+          };
+        })(this)
+      });
+    };
+
+    Todos.prototype.clearDone = function($row) {
+      var $ul;
+      $ul = $row.closest('ul');
+      $row.remove();
+      if (!$ul.find('li').length) {
+        return $ul.parents('.panel').remove();
+      }
+    };
+
+    Todos.prototype.updateBadges = function(data) {
+      $('.todos-pending .badge, .todos-pending-count').text(data.count);
+      return $('.todos-done .badge').text(data.done_count);
+    };
+
+    Todos.prototype.getTotalPages = function() {
+      return this.el.data('totalPages');
+    };
+
+    Todos.prototype.getCurrentPage = function() {
+      return this.el.data('currentPage');
+    };
+
+    Todos.prototype.getTodosPerPage = function() {
+      return this.el.data('perPage');
+    };
+
+    Todos.prototype.redirectIfNeeded = function(total) {
+      var currPage, currPages, newPages, pageParams, url;
+      currPages = this.getTotalPages();
+      currPage = this.getCurrentPage();
+      if (!total) {
+        location.reload();
+        return;
+      }
+      if (!currPages) {
+        return;
+      }
+      newPages = Math.ceil(total / this.getTodosPerPage());
+      url = location.href;
+      if (newPages !== currPages) {
+        if (currPages > 1 && currPage === currPages) {
+          pageParams = {
+            page: currPages - 1
+          };
+          url = gl.utils.mergeUrlParams(pageParams, url);
+        }
+        return Turbolinks.visit(url);
+      }
+    };
+
+    Todos.prototype.goToTodoUrl = function(e) {
+      var todoLink;
+      todoLink = $(this).data('url');
+      if (!todoLink) {
+        return;
+      }
+      if (e.metaKey || e.which === 2) {
+        e.preventDefault();
+        return window.open(todoLink, '_blank');
+      } else {
+        return Turbolinks.visit(todoLink);
+      }
+    };
+
+    return Todos;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee
deleted file mode 100644
index 10bef96f43d53f13b2c3b31573910a4f76226e8a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/todos.js.coffee
+++ /dev/null
@@ -1,110 +0,0 @@
-class @Todos
-  constructor: (opts = {}) ->
-    {
-      @el = $('.js-todos-options')
-    } = opts
-
-    @perPage = @el.data('perPage')
-
-    @clearListeners()
-    @initBtnListeners()
-
-  clearListeners: ->
-    $('.done-todo').off('click')
-    $('.js-todos-mark-all').off('click')
-    $('.todo').off('click')
-
-  initBtnListeners: ->
-    $('.done-todo').on('click', @doneClicked)
-    $('.js-todos-mark-all').on('click', @allDoneClicked)
-    $('.todo').on('click', @goToTodoUrl)
-
-  doneClicked: (e) =>
-    e.preventDefault()
-    e.stopImmediatePropagation()
-
-    $this = $(e.currentTarget)
-    $this.disable()
-
-    $.ajax
-      type: 'POST'
-      url: $this.attr('href')
-      dataType: 'json'
-      data: '_method': 'delete'
-      success: (data) =>
-        @redirectIfNeeded data.count
-        @clearDone $this.closest('li')
-        @updateBadges data
-
-  allDoneClicked: (e) =>
-    e.preventDefault()
-    e.stopImmediatePropagation()
-
-    $this = $(e.currentTarget)
-    $this.disable()
-
-    $.ajax
-      type: 'POST'
-      url: $this.attr('href')
-      dataType: 'json'
-      data: '_method': 'delete'
-      success: (data) =>
-        $this.remove()
-        $('.js-todos-list').remove()
-        @updateBadges data
-
-  clearDone: ($row) ->
-    $ul = $row.closest('ul')
-    $row.remove()
-
-    if not $ul.find('li').length
-      $ul.parents('.panel').remove()
-
-  updateBadges: (data) ->
-    $('.todos-pending .badge, .todos-pending-count').text data.count
-    $('.todos-done .badge').text data.done_count
-
-  getTotalPages: ->
-    @el.data('totalPages')
-
-  getCurrentPage: ->
-    @el.data('currentPage')
-
-  getTodosPerPage: ->
-    @el.data('perPage')
-
-  redirectIfNeeded: (total) ->
-    currPages = @getTotalPages()
-    currPage = @getCurrentPage()
-
-    # Refresh if no remaining Todos
-    if not total
-      location.reload()
-      return
-
-    # Do nothing if no pagination
-    return if not currPages
-
-    newPages = Math.ceil(total / @getTodosPerPage())
-    url = location.href # Includes query strings
-
-    # If new total of pages is different than we have now
-    if newPages isnt currPages
-      # Redirect to previous page if there's one available
-      if currPages > 1 and currPage is currPages
-        pageParams =
-          page: currPages - 1
-        url = gl.utils.mergeUrlParams(pageParams, url)
-
-      Turbolinks.visit(url)
-
-  goToTodoUrl: (e)->
-    todoLink = $(this).data('url')
-    return unless todoLink
-
-    # Allow Meta-Click or Mouse3-click to open in a new tab
-    if e.metaKey or e.which is 2
-      e.preventDefault()
-      window.open(todoLink,'_blank')
-    else
-      Turbolinks.visit(todoLink)
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
new file mode 100644
index 0000000000000000000000000000000000000000..78e159a7ed97c3c397a31a224e23f4da57ccd975
--- /dev/null
+++ b/app/assets/javascripts/tree.js
@@ -0,0 +1,65 @@
+(function() {
+  this.TreeView = (function() {
+    function TreeView() {
+      this.initKeyNav();
+      $(".tree-content-holder .tree-item").on('click', function(e) {
+        var $clickedEl, path;
+        $clickedEl = $(e.target);
+        path = $('.tree-item-file-name a', this).attr('href');
+        if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
+          if (e.metaKey || e.which === 2) {
+            e.preventDefault();
+            return window.open(path, '_blank');
+          } else {
+            return Turbolinks.visit(path);
+          }
+        }
+      });
+      $('span.log_loading:first').removeClass('hide');
+    }
+
+    TreeView.prototype.initKeyNav = function() {
+      var li, liSelected;
+      li = $("tr.tree-item");
+      liSelected = null;
+      return $('body').keydown(function(e) {
+        var next, path;
+        if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+          return false;
+        }
+        if (e.which === 40) {
+          if (liSelected) {
+            next = liSelected.next();
+            if (next.length > 0) {
+              liSelected.removeClass("selected");
+              liSelected = next.addClass("selected");
+            }
+          } else {
+            liSelected = li.eq(0).addClass("selected");
+          }
+          return $(liSelected).focus();
+        } else if (e.which === 38) {
+          if (liSelected) {
+            next = liSelected.prev();
+            if (next.length > 0) {
+              liSelected.removeClass("selected");
+              liSelected = next.addClass("selected");
+            }
+          } else {
+            liSelected = li.last().addClass("selected");
+          }
+          return $(liSelected).focus();
+        } else if (e.which === 13) {
+          path = $('.tree-item.selected .tree-item-file-name a').attr('href');
+          if (path) {
+            return Turbolinks.visit(path);
+          }
+        }
+      });
+    };
+
+    return TreeView;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
deleted file mode 100644
index 83de584f2d92ab6240a340cac8e2a05a5e25de67..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/tree.js.coffee
+++ /dev/null
@@ -1,50 +0,0 @@
-class @TreeView
-  constructor: ->
-    @initKeyNav()
-
-    # Code browser tree slider
-    # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
-    $(".tree-content-holder .tree-item").on 'click', (e) ->
-      $clickedEl = $(e.target)
-      path = $('.tree-item-file-name a', this).attr('href')
-
-      if not $clickedEl.is('a') and not $clickedEl.is('.str-truncated')
-        if e.metaKey or e.which is 2
-          e.preventDefault()
-          window.open path, '_blank'
-        else
-          Turbolinks.visit path
-
-    # Show the "Loading commit data" for only the first element
-    $('span.log_loading:first').removeClass('hide')
-
-  initKeyNav: ->
-    li = $("tr.tree-item")
-    liSelected = null
-    $('body').keydown (e) ->
-      if $("input:focus").length > 0 && (e.which == 38 || e.which == 40)
-        return false
-
-      if e.which is 40
-        if liSelected
-          next = liSelected.next()
-          if next.length > 0
-            liSelected.removeClass "selected"
-            liSelected = next.addClass("selected")
-        else
-          liSelected = li.eq(0).addClass("selected")
-
-        $(liSelected).focus()
-      else if e.which is 38
-        if liSelected
-          next = liSelected.prev()
-          if next.length > 0
-            liSelected.removeClass "selected"
-            liSelected = next.addClass("selected")
-        else
-          liSelected = li.last().addClass("selected")
-
-        $(liSelected).focus()
-      else if e.which is 13
-        path = $('.tree-item.selected .tree-item-file-name a').attr('href')
-        if path then Turbolinks.visit(path)
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ba847fb0c2c782199845235ed4477d88049ec85
--- /dev/null
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -0,0 +1,89 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.U2FAuthenticate = (function() {
+    function U2FAuthenticate(container, u2fParams) {
+      this.container = container;
+      this.renderNotSupported = bind(this.renderNotSupported, this);
+      this.renderAuthenticated = bind(this.renderAuthenticated, this);
+      this.renderError = bind(this.renderError, this);
+      this.renderInProgress = bind(this.renderInProgress, this);
+      this.renderSetup = bind(this.renderSetup, this);
+      this.renderTemplate = bind(this.renderTemplate, this);
+      this.authenticate = bind(this.authenticate, this);
+      this.start = bind(this.start, this);
+      this.appId = u2fParams.app_id;
+      this.challenge = u2fParams.challenge;
+      this.signRequests = u2fParams.sign_requests.map(function(request) {
+        return _(request).omit('challenge');
+      });
+    }
+
+    U2FAuthenticate.prototype.start = function() {
+      if (U2FUtil.isU2FSupported()) {
+        return this.renderSetup();
+      } else {
+        return this.renderNotSupported();
+      }
+    };
+
+    U2FAuthenticate.prototype.authenticate = function() {
+      return u2f.sign(this.appId, this.challenge, this.signRequests, (function(_this) {
+        return function(response) {
+          var error;
+          if (response.errorCode) {
+            error = new U2FError(response.errorCode);
+            return _this.renderError(error);
+          } else {
+            return _this.renderAuthenticated(JSON.stringify(response));
+          }
+        };
+      })(this), 10);
+    };
+
+    U2FAuthenticate.prototype.templates = {
+      "notSupported": "#js-authenticate-u2f-not-supported",
+      "setup": '#js-authenticate-u2f-setup',
+      "inProgress": '#js-authenticate-u2f-in-progress',
+      "error": '#js-authenticate-u2f-error',
+      "authenticated": '#js-authenticate-u2f-authenticated'
+    };
+
+    U2FAuthenticate.prototype.renderTemplate = function(name, params) {
+      var template, templateString;
+      templateString = $(this.templates[name]).html();
+      template = _.template(templateString);
+      return this.container.html(template(params));
+    };
+
+    U2FAuthenticate.prototype.renderSetup = function() {
+      this.renderTemplate('setup');
+      return this.container.find('#js-login-u2f-device').on('click', this.renderInProgress);
+    };
+
+    U2FAuthenticate.prototype.renderInProgress = function() {
+      this.renderTemplate('inProgress');
+      return this.authenticate();
+    };
+
+    U2FAuthenticate.prototype.renderError = function(error) {
+      this.renderTemplate('error', {
+        error_message: error.message()
+      });
+      return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
+    };
+
+    U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
+      this.renderTemplate('authenticated');
+      return this.container.find("#js-device-response").val(deviceResponse);
+    };
+
+    U2FAuthenticate.prototype.renderNotSupported = function() {
+      return this.renderTemplate('notSupported');
+    };
+
+    return U2FAuthenticate;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee
deleted file mode 100644
index 918c0a560fdd902be56e4098d34d951ac37fc3c8..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/u2f/authenticate.js.coffee
+++ /dev/null
@@ -1,75 +0,0 @@
-# Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
-#
-# State Flow #1: setup -> in_progress -> authenticated -> POST to server
-# State Flow #2: setup -> in_progress -> error -> setup
-
-class @U2FAuthenticate
-  constructor: (@container, u2fParams) ->
-    @appId = u2fParams.app_id
-    @challenge = u2fParams.challenge
-
-    # The U2F Javascript API v1.1 requires a single challenge, with
-    # _no challenges per-request_. The U2F Javascript API v1.0 requires a
-    # challenge per-request, which is done by copying the single challenge
-    # into every request.
-    #
-    # In either case, we don't need the per-request challenges that the server
-    # has generated, so we can remove them.
-    #
-    # Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
-    # This can be removed once we upgrade.
-    # https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
-    @signRequests = u2fParams.sign_requests.map (request) -> _(request).omit('challenge')
-
-  start: () =>
-    if U2FUtil.isU2FSupported()
-      @renderSetup()
-    else
-      @renderNotSupported()
-
-  authenticate: () =>
-    u2f.sign(@appId, @challenge, @signRequests, (response) =>
-      if response.errorCode
-        error = new U2FError(response.errorCode)
-        @renderError(error);
-      else
-        @renderAuthenticated(JSON.stringify(response))
-    , 10)
-
-  #############
-  # Rendering #
-  #############
-
-  templates: {
-    "notSupported": "#js-authenticate-u2f-not-supported",
-    "setup": '#js-authenticate-u2f-setup',
-    "inProgress": '#js-authenticate-u2f-in-progress',
-    "error": '#js-authenticate-u2f-error',
-    "authenticated": '#js-authenticate-u2f-authenticated'
-  }
-
-  renderTemplate: (name, params) =>
-    templateString = $(@templates[name]).html()
-    template = _.template(templateString)
-    @container.html(template(params))
-
-  renderSetup: () =>
-    @renderTemplate('setup')
-    @container.find('#js-login-u2f-device').on('click', @renderInProgress)
-
-  renderInProgress: () =>
-    @renderTemplate('inProgress')
-    @authenticate()
-
-  renderError: (error) =>
-    @renderTemplate('error', {error_message: error.message()})
-    @container.find('#js-u2f-try-again').on('click', @renderSetup)
-
-  renderAuthenticated: (deviceResponse) =>
-    @renderTemplate('authenticated')
-    # Prefer to do this instead of interpolating using Underscore templates
-    # because of JSON escaping issues.
-    @container.find("#js-device-response").val(deviceResponse)
-
-  renderNotSupported: () =>
-    @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc48c67c4f27e2116aa150b22a47f53481f915a6
--- /dev/null
+++ b/app/assets/javascripts/u2f/error.js
@@ -0,0 +1,27 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.U2FError = (function() {
+    function U2FError(errorCode) {
+      this.errorCode = errorCode;
+      this.message = bind(this.message, this);
+      this.httpsDisabled = window.location.protocol !== 'https:';
+      console.error("U2F Error Code: " + this.errorCode);
+    }
+
+    U2FError.prototype.message = function() {
+      switch (false) {
+        case !(this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled):
+          return "U2F only works with HTTPS-enabled websites. Contact your administrator for more details.";
+        case this.errorCode !== u2f.ErrorCodes.DEVICE_INELIGIBLE:
+          return "This device has already been registered with us.";
+        default:
+          return "There was a problem communicating with your device.";
+      }
+    };
+
+    return U2FError;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/u2f/error.js.coffee b/app/assets/javascripts/u2f/error.js.coffee
deleted file mode 100644
index 1a2fc3e757f4183f28d297022781273ca6da7139..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/u2f/error.js.coffee
+++ /dev/null
@@ -1,13 +0,0 @@
-class @U2FError
-  constructor: (@errorCode) ->
-    @httpsDisabled = (window.location.protocol isnt 'https:')
-    console.error("U2F Error Code: #{@errorCode}")
-
-  message: () =>
-    switch
-      when (@errorCode is u2f.ErrorCodes.BAD_REQUEST and @httpsDisabled)
-        "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
-      when @errorCode is u2f.ErrorCodes.DEVICE_INELIGIBLE
-        "This device has already been registered with us."
-      else
-        "There was a problem communicating with your device."
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
new file mode 100644
index 0000000000000000000000000000000000000000..c87e0840df33feaab53ddf79faf4e2f419f62d64
--- /dev/null
+++ b/app/assets/javascripts/u2f/register.js
@@ -0,0 +1,87 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.U2FRegister = (function() {
+    function U2FRegister(container, u2fParams) {
+      this.container = container;
+      this.renderNotSupported = bind(this.renderNotSupported, this);
+      this.renderRegistered = bind(this.renderRegistered, this);
+      this.renderError = bind(this.renderError, this);
+      this.renderInProgress = bind(this.renderInProgress, this);
+      this.renderSetup = bind(this.renderSetup, this);
+      this.renderTemplate = bind(this.renderTemplate, this);
+      this.register = bind(this.register, this);
+      this.start = bind(this.start, this);
+      this.appId = u2fParams.app_id;
+      this.registerRequests = u2fParams.register_requests;
+      this.signRequests = u2fParams.sign_requests;
+    }
+
+    U2FRegister.prototype.start = function() {
+      if (U2FUtil.isU2FSupported()) {
+        return this.renderSetup();
+      } else {
+        return this.renderNotSupported();
+      }
+    };
+
+    U2FRegister.prototype.register = function() {
+      return u2f.register(this.appId, this.registerRequests, this.signRequests, (function(_this) {
+        return function(response) {
+          var error;
+          if (response.errorCode) {
+            error = new U2FError(response.errorCode);
+            return _this.renderError(error);
+          } else {
+            return _this.renderRegistered(JSON.stringify(response));
+          }
+        };
+      })(this), 10);
+    };
+
+    U2FRegister.prototype.templates = {
+      "notSupported": "#js-register-u2f-not-supported",
+      "setup": '#js-register-u2f-setup',
+      "inProgress": '#js-register-u2f-in-progress',
+      "error": '#js-register-u2f-error',
+      "registered": '#js-register-u2f-registered'
+    };
+
+    U2FRegister.prototype.renderTemplate = function(name, params) {
+      var template, templateString;
+      templateString = $(this.templates[name]).html();
+      template = _.template(templateString);
+      return this.container.html(template(params));
+    };
+
+    U2FRegister.prototype.renderSetup = function() {
+      this.renderTemplate('setup');
+      return this.container.find('#js-setup-u2f-device').on('click', this.renderInProgress);
+    };
+
+    U2FRegister.prototype.renderInProgress = function() {
+      this.renderTemplate('inProgress');
+      return this.register();
+    };
+
+    U2FRegister.prototype.renderError = function(error) {
+      this.renderTemplate('error', {
+        error_message: error.message()
+      });
+      return this.container.find('#js-u2f-try-again').on('click', this.renderSetup);
+    };
+
+    U2FRegister.prototype.renderRegistered = function(deviceResponse) {
+      this.renderTemplate('registered');
+      return this.container.find("#js-device-response").val(deviceResponse);
+    };
+
+    U2FRegister.prototype.renderNotSupported = function() {
+      return this.renderTemplate('notSupported');
+    };
+
+    return U2FRegister;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/u2f/register.js.coffee b/app/assets/javascripts/u2f/register.js.coffee
deleted file mode 100644
index 74472cfa1208724727fb463b4fc3c2125ec9df37..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/u2f/register.js.coffee
+++ /dev/null
@@ -1,63 +0,0 @@
-# Register U2F (universal 2nd factor) devices for users to authenticate with.
-#
-# State Flow #1: setup -> in_progress -> registered -> POST to server
-# State Flow #2: setup -> in_progress -> error -> setup
-
-class @U2FRegister
-  constructor: (@container, u2fParams) ->
-    @appId = u2fParams.app_id
-    @registerRequests = u2fParams.register_requests
-    @signRequests = u2fParams.sign_requests
-
-  start: () =>
-    if U2FUtil.isU2FSupported()
-      @renderSetup()
-    else
-      @renderNotSupported()
-
-  register: () =>
-    u2f.register(@appId, @registerRequests, @signRequests, (response) =>
-      if response.errorCode
-        error = new U2FError(response.errorCode)
-        @renderError(error);
-      else
-        @renderRegistered(JSON.stringify(response))
-    , 10)
-
-  #############
-  # Rendering #
-  #############
-
-  templates: {
-    "notSupported": "#js-register-u2f-not-supported",
-    "setup": '#js-register-u2f-setup',
-    "inProgress": '#js-register-u2f-in-progress',
-    "error": '#js-register-u2f-error',
-    "registered": '#js-register-u2f-registered'
-  }
-
-  renderTemplate: (name, params) =>
-    templateString = $(@templates[name]).html()
-    template = _.template(templateString)
-    @container.html(template(params))
-
-  renderSetup: () =>
-    @renderTemplate('setup')
-    @container.find('#js-setup-u2f-device').on('click', @renderInProgress)
-
-  renderInProgress: () =>
-    @renderTemplate('inProgress')
-    @register()
-
-  renderError: (error) =>
-    @renderTemplate('error', {error_message: error.message()})
-    @container.find('#js-u2f-try-again').on('click', @renderSetup)
-
-  renderRegistered: (deviceResponse) =>
-    @renderTemplate('registered')
-    # Prefer to do this instead of interpolating using Underscore templates
-    # because of JSON escaping issues.
-    @container.find("#js-device-response").val(deviceResponse)
-
-  renderNotSupported: () =>
-    @renderTemplate('notSupported')
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..907e640161a41998f98fde9f76936c4973eb6f2f
--- /dev/null
+++ b/app/assets/javascripts/u2f/util.js
@@ -0,0 +1,13 @@
+(function() {
+  this.U2FUtil = (function() {
+    function U2FUtil() {}
+
+    U2FUtil.isU2FSupported = function() {
+      return window.u2f;
+    };
+
+    return U2FUtil;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/u2f/util.js.coffee b/app/assets/javascripts/u2f/util.js.coffee
deleted file mode 100644
index 5ef324f609ddc7446c47b1a63d16081959e3a907..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/u2f/util.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-class @U2FUtil
-  @isU2FSupported: ->
-    window.u2f
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
new file mode 100644
index 0000000000000000000000000000000000000000..b46390ad8f43008ab8d2be72560fa1047c299806
--- /dev/null
+++ b/app/assets/javascripts/user.js
@@ -0,0 +1,31 @@
+(function() {
+  this.User = (function() {
+    function User(opts) {
+      this.opts = opts;
+      $('.profile-groups-avatars').tooltip({
+        "placement": "top"
+      });
+      this.initTabs();
+      $('.hide-project-limit-message').on('click', function(e) {
+        var path;
+        path = '/';
+        $.cookie('hide_project_limit_message', 'false', {
+          path: path
+        });
+        $(this).parents('.project-limit-message').remove();
+        return e.preventDefault();
+      });
+    }
+
+    User.prototype.initTabs = function() {
+      return new UserTabs({
+        parentEl: '.user-profile',
+        action: this.opts.action
+      });
+    };
+
+    return User;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
deleted file mode 100644
index 2882a90d118ba0107f915546735ef289d38b3df4..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/user.js.coffee
+++ /dev/null
@@ -1,17 +0,0 @@
-class @User
-  constructor: (@opts) ->
-    $('.profile-groups-avatars').tooltip("placement": "top")
-
-    @initTabs()
-
-    $('.hide-project-limit-message').on 'click', (e) ->
-      path = '/'
-      $.cookie('hide_project_limit_message', 'false', { path: path })
-      $(@).parents('.project-limit-message').remove()
-      e.preventDefault()
-
-  initTabs: ->
-    new UserTabs(
-        parentEl: '.user-profile'
-        action: @opts.action
-      )
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
new file mode 100644
index 0000000000000000000000000000000000000000..e5e75701feecf9b663649dac086ae9470a64d85b
--- /dev/null
+++ b/app/assets/javascripts/user_tabs.js
@@ -0,0 +1,119 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.UserTabs = (function() {
+    function UserTabs(opts) {
+      this.tabShown = bind(this.tabShown, this);
+      var i, item, len, ref, ref1, ref2, ref3;
+      this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document);
+      if (typeof this.parentEl === 'string') {
+        this.parentEl = $(this.parentEl);
+      }
+      this._location = location;
+      this.loaded = {};
+      ref3 = this.parentEl.find('.nav-links a');
+      for (i = 0, len = ref3.length; i < len; i++) {
+        item = ref3[i];
+        this.loaded[$(item).attr('data-action')] = false;
+      }
+      this.actions = Object.keys(this.loaded);
+      this.bindEvents();
+      if (this.action === 'show') {
+        this.action = this.defaultAction;
+      }
+      this.activateTab(this.action);
+    }
+
+    UserTabs.prototype.bindEvents = function() {
+      return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown);
+    };
+
+    UserTabs.prototype.tabShown = function(event) {
+      var $target, action, source;
+      $target = $(event.target);
+      action = $target.data('action');
+      source = $target.attr('href');
+      this.setTab(source, action);
+      return this.setCurrentAction(action);
+    };
+
+    UserTabs.prototype.activateTab = function(action) {
+      return this.parentEl.find(".nav-links .js-" + action + "-tab a").tab('show');
+    };
+
+    UserTabs.prototype.setTab = function(source, action) {
+      if (this.loaded[action] === true) {
+        return;
+      }
+      if (action === 'activity') {
+        this.loadActivities(source);
+      }
+      if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') {
+        return this.loadTab(source, action);
+      }
+    };
+
+    UserTabs.prototype.loadTab = function(source, action) {
+      return $.ajax({
+        beforeSend: (function(_this) {
+          return function() {
+            return _this.toggleLoading(true);
+          };
+        })(this),
+        complete: (function(_this) {
+          return function() {
+            return _this.toggleLoading(false);
+          };
+        })(this),
+        dataType: 'json',
+        type: 'GET',
+        url: source + ".json",
+        success: (function(_this) {
+          return function(data) {
+            var tabSelector;
+            tabSelector = 'div#' + action;
+            _this.parentEl.find(tabSelector).html(data.html);
+            _this.loaded[action] = true;
+            return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+          };
+        })(this)
+      });
+    };
+
+    UserTabs.prototype.loadActivities = function(source) {
+      var $calendarWrap;
+      if (this.loaded['activity'] === true) {
+        return;
+      }
+      $calendarWrap = this.parentEl.find('.user-calendar');
+      $calendarWrap.load($calendarWrap.data('href'));
+      new Activities();
+      return this.loaded['activity'] = true;
+    };
+
+    UserTabs.prototype.toggleLoading = function(status) {
+      return this.parentEl.find('.loading-status .loading').toggle(status);
+    };
+
+    UserTabs.prototype.setCurrentAction = function(action) {
+      var new_state, regExp;
+      regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$');
+      new_state = this._location.pathname;
+      new_state = new_state.replace(/\/+$/, "");
+      new_state = new_state.replace(regExp, '');
+      if (action !== this.defaultAction) {
+        new_state += "/" + action;
+      }
+      new_state += this._location.search + this._location.hash;
+      history.replaceState({
+        turbolinks: true,
+        url: new_state
+      }, document.title, new_state);
+      return new_state;
+    };
+
+    return UserTabs;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
deleted file mode 100644
index 29dad21faed5611589a425a8eb08e3497b8f8866..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/user_tabs.js.coffee
+++ /dev/null
@@ -1,156 +0,0 @@
-# UserTabs
-#
-# Handles persisting and restoring the current tab selection and lazily-loading
-# content on the Users#show page.
-#
-# ### Example Markup
-#
-#   <ul class="nav-links">
-#     <li class="activity-tab active">
-#       <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
-#         Activity
-#       </a>
-#     </li>
-#     <li class="groups-tab">
-#       <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
-#         Groups
-#       </a>
-#     </li>
-#     <li class="contributed-tab">
-#       <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
-#         Contributed projects
-#       </a>
-#     </li>
-#     <li class="projects-tab">
-#       <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
-#         Personal projects
-#       </a>
-#     </li>
-#    <li class="snippets-tab">
-#       <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
-#       </a>
-#     </li>
-#   </ul>
-#
-#   <div class="tab-content">
-#     <div class="tab-pane" id="activity">
-#       Activity Content
-#     </div>
-#     <div class="tab-pane" id="groups">
-#       Groups Content
-#     </div>
-#     <div class="tab-pane" id="contributed">
-#       Contributed projects content
-#     </div>
-#     <div class="tab-pane" id="projects">
-#       Projects content
-#     </div>
-#     <div class="tab-pane" id="snippets">
-#       Snippets content
-#     </div>
-#   </div>
-#
-#   <div class="loading-status">
-#     <div class="loading">
-#       Loading Animation
-#     </div>
-#   </div>
-#
-class @UserTabs
-  constructor: (opts) ->
-    {
-      @action = 'activity'
-      @defaultAction = 'activity'
-      @parentEl = $(document)
-    } = opts
-
-    # Make jQuery object if selector is provided
-    @parentEl = $(@parentEl) if typeof @parentEl is 'string'
-
-    # Store the `location` object, allowing for easier stubbing in tests
-    @_location = location
-
-    # Set tab states
-    @loaded = {}
-    for item in @parentEl.find('.nav-links a')
-      @loaded[$(item).attr 'data-action'] = false
-
-    # Actions
-    @actions = Object.keys @loaded
-
-    @bindEvents()
-
-    # Set active tab
-    @action = @defaultAction if @action is 'show'
-    @activateTab(@action)
-
-  bindEvents: ->
-    # Toggle event listeners
-    @parentEl
-      .off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]'
-      .on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown
-
-  tabShown: (event) =>
-    $target = $(event.target)
-    action = $target.data('action')
-    source = $target.attr('href')
-
-    @setTab(source, action)
-    @setCurrentAction(action)
-
-  activateTab: (action) ->
-    @parentEl.find(".nav-links .js-#{action}-tab a").tab('show')
-
-  setTab: (source, action) ->
-    return if @loaded[action] is true
-
-    if action is 'activity'
-      @loadActivities(source)
-
-    if action in ['groups', 'contributed', 'projects', 'snippets']
-      @loadTab(source, action)
-
-  loadTab: (source, action) ->
-    $.ajax
-      beforeSend: => @toggleLoading(true)
-      complete:   => @toggleLoading(false)
-      dataType: 'json'
-      type: 'GET'
-      url: "#{source}.json"
-      success: (data) =>
-        tabSelector = 'div#' + action
-        @parentEl.find(tabSelector).html(data.html)
-        @loaded[action] = true
-
-        # Fix tooltips
-        gl.utils.localTimeAgo($('.js-timeago', tabSelector))
-
-  loadActivities: (source) ->
-    return if @loaded['activity'] is true
-
-    $calendarWrap = @parentEl.find('.user-calendar')
-    $calendarWrap.load($calendarWrap.data('href'))
-
-    new Activities()
-    @loaded['activity'] = true
-
-  toggleLoading: (status) ->
-    @parentEl.find('.loading-status .loading').toggle(status)
-
-  setCurrentAction: (action) ->
-    # Remove possible actions from URL
-    regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$')
-    new_state = @_location.pathname
-    new_state = new_state.replace(/\/+$/, "") # remove trailing slashes
-    new_state = new_state.replace(regExp, '')
-
-    # Append the new action if we're on a tab other than 'activity'
-    unless action == @defaultAction
-      new_state += "/#{action}"
-
-    # Ensure parameters and hash come along for the ride
-    new_state += @_location.search + @_location.hash
-
-    history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
-
-    new_state
diff --git a/app/assets/javascripts/users/application.js.coffee b/app/assets/javascripts/users/application.js.coffee
deleted file mode 100644
index 91cacfece463abccaffbc9dc2bc27074e6719436..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/users/application.js.coffee
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-#= require_tree .
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b3dbf5f5ae1f096a9dde213887ed708a12f686d
--- /dev/null
+++ b/app/assets/javascripts/users/calendar.js
@@ -0,0 +1,192 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Calendar = (function() {
+    function Calendar(timestamps, calendar_activities_path) {
+      var group, i;
+      this.calendar_activities_path = calendar_activities_path;
+      this.clickDay = bind(this.clickDay, this);
+      this.currentSelectedDate = '';
+      this.daySpace = 1;
+      this.daySize = 15;
+      this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
+      this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+      this.months = [];
+      this.timestampsTmp = [];
+      i = 0;
+      group = 0;
+      _.each(timestamps, (function(_this) {
+        return function(count, date) {
+          var day, innerArray, newDate;
+          newDate = new Date(parseInt(date) * 1000);
+          day = newDate.getDay();
+          if ((day === 0 && i !== 0) || i === 0) {
+            _this.timestampsTmp.push([]);
+            group++;
+          }
+          innerArray = _this.timestampsTmp[group - 1];
+          innerArray.push({
+            count: count,
+            date: newDate,
+            day: day
+          });
+          return i++;
+        };
+      })(this));
+      this.colorKey = this.initColorKey();
+      this.color = this.initColor();
+      this.renderSvg(group);
+      this.renderDays();
+      this.renderMonths();
+      this.renderDayTitles();
+      this.renderKey();
+      this.initTooltips();
+    }
+
+    Calendar.prototype.renderSvg = function(group) {
+      return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar');
+    };
+
+    Calendar.prototype.renderDays = function() {
+      return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
+        return function(group, i) {
+          _.each(group, function(stamp, a) {
+            var lastMonth, lastMonthX, month, x;
+            if (a === 0 && stamp.day === 0) {
+              month = stamp.date.getMonth();
+              x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
+              lastMonth = _.last(_this.months);
+              if (lastMonth != null) {
+                lastMonthX = lastMonth.x;
+              }
+              if (lastMonth == null) {
+                return _this.months.push({
+                  month: month,
+                  x: x
+                });
+              } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
+                return _this.months.push({
+                  month: month,
+                  x: x
+                });
+              }
+            }
+          });
+          return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
+        };
+      })(this)).selectAll('rect').data(function(stamp) {
+        return stamp;
+      }).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
+        return function(stamp, i) {
+          return _this.daySizeWithSpace * stamp.day;
+        };
+      })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
+        return function(stamp) {
+          var contribText, date, dateText;
+          date = new Date(stamp.date);
+          contribText = 'No contributions';
+          if (stamp.count > 0) {
+            contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
+          }
+          dateText = dateFormat(date, 'mmm d, yyyy');
+          return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
+        };
+      })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
+        return function(stamp) {
+          if (stamp.count !== 0) {
+            return _this.color(Math.min(stamp.count, 40));
+          } else {
+            return '#ededed';
+          }
+        };
+      })(this)).attr('data-container', 'body').on('click', this.clickDay);
+    };
+
+    Calendar.prototype.renderDayTitles = function() {
+      var days;
+      days = [
+        {
+          text: 'M',
+          y: 29 + (this.daySizeWithSpace * 1)
+        }, {
+          text: 'W',
+          y: 29 + (this.daySizeWithSpace * 3)
+        }, {
+          text: 'F',
+          y: 29 + (this.daySizeWithSpace * 5)
+        }
+      ];
+      return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
+        return day.y;
+      }).text(function(day) {
+        return day.text;
+      }).attr('class', 'user-contrib-text');
+    };
+
+    Calendar.prototype.renderMonths = function() {
+      return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
+        return date.x;
+      }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
+        return function(date) {
+          return _this.monthNames[date.month];
+        };
+      })(this));
+    };
+
+    Calendar.prototype.renderKey = function() {
+      var keyColors;
+      keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+      return this.svg.append('g').attr('transform', "translate(18, " + (this.daySizeWithSpace * 8 + 16) + ")").selectAll('rect').data(keyColors).enter().append('rect').attr('width', this.daySize).attr('height', this.daySize).attr('x', (function(_this) {
+        return function(color, i) {
+          return _this.daySizeWithSpace * i;
+        };
+      })(this)).attr('y', 0).attr('fill', function(color) {
+        return color;
+      });
+    };
+
+    Calendar.prototype.initColor = function() {
+      var colorRange;
+      colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+      return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
+    };
+
+    Calendar.prototype.initColorKey = function() {
+      return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
+    };
+
+    Calendar.prototype.clickDay = function(stamp) {
+      var formatted_date;
+      if (this.currentSelectedDate !== stamp.date) {
+        this.currentSelectedDate = stamp.date;
+        formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
+        return $.ajax({
+          url: this.calendar_activities_path,
+          data: {
+            date: formatted_date
+          },
+          cache: false,
+          dataType: 'html',
+          beforeSend: function() {
+            return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
+          },
+          success: function(data) {
+            return $('.user-calendar-activities').html(data);
+          }
+        });
+      } else {
+        return $('.user-calendar-activities').html('');
+      }
+    };
+
+    Calendar.prototype.initTooltips = function() {
+      return $('.js-contrib-calendar .js-tooltip').tooltip({
+        html: true
+      });
+    };
+
+    return Calendar;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee
deleted file mode 100644
index c49ba5186f2f36edcdfc1bf957d24654f13d4d5c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/users/calendar.js.coffee
+++ /dev/null
@@ -1,194 +0,0 @@
-class @Calendar
-  constructor: (timestamps, @calendar_activities_path) ->
-    @currentSelectedDate = ''
-    @daySpace = 1
-    @daySize = 15
-    @daySizeWithSpace = @daySize + (@daySpace * 2)
-    @monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-    @months = []
-
-    # Loop through the timestamps to create a group of objects
-    # The group of objects will be grouped based on the day of the week they are
-    @timestampsTmp = []
-    i = 0
-    group = 0
-    _.each timestamps, (count, date) =>
-      newDate = new Date parseInt(date) * 1000
-      day = newDate.getDay()
-
-      # Create a new group array if this is the first day of the week
-      # or if is first object
-      if (day is 0 and i isnt 0) or i is 0
-        @timestampsTmp.push []
-        group++
-
-      innerArray = @timestampsTmp[group-1]
-
-      # Push to the inner array the values that will be used to render map
-      innerArray.push
-        count: count
-        date: newDate
-        day: day
-
-      i++
-
-    # Init color functions
-    @colorKey = @initColorKey()
-    @color = @initColor()
-
-    # Init the svg element
-    @renderSvg(group)
-    @renderDays()
-    @renderMonths()
-    @renderDayTitles()
-    @renderKey()
-
-    @initTooltips()
-
-  renderSvg: (group) ->
-    @svg = d3.select '.js-contrib-calendar'
-      .append 'svg'
-      .attr 'width', (group + 1) * @daySizeWithSpace
-      .attr 'height', 167
-      .attr 'class', 'contrib-calendar'
-
-  renderDays: ->
-    @svg.selectAll 'g'
-      .data @timestampsTmp
-      .enter()
-      .append 'g'
-      .attr 'transform', (group, i) =>
-        _.each group, (stamp, a) =>
-          if a is 0 and stamp.day is 0
-            month = stamp.date.getMonth()
-            x = (@daySizeWithSpace * i + 1) + @daySizeWithSpace
-            lastMonth = _.last(@months)
-            if lastMonth?
-              lastMonthX = lastMonth.x
-
-            if !lastMonth?
-              @months.push
-                month: month
-                x: x
-            else if month isnt lastMonth.month and x - @daySizeWithSpace isnt lastMonthX
-              @months.push
-                month: month
-                x: x
-
-        "translate(#{(@daySizeWithSpace * i + 1) + @daySizeWithSpace}, 18)"
-      .selectAll 'rect'
-      .data (stamp) ->
-        stamp
-      .enter()
-      .append 'rect'
-      .attr 'x', '0'
-      .attr 'y', (stamp, i) =>
-        (@daySizeWithSpace * stamp.day)
-      .attr 'width', @daySize
-      .attr 'height', @daySize
-      .attr 'title', (stamp) =>
-        date = new Date(stamp.date)
-        contribText = 'No contributions'
-
-        if stamp.count > 0
-          contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}"
-
-        dateText = dateFormat(date, 'mmm d, yyyy')
-
-        "#{contribText}<br />#{gl.utils.getDayName(date)} #{dateText}"
-      .attr 'class', 'user-contrib-cell js-tooltip'
-      .attr 'fill', (stamp) =>
-        if stamp.count isnt 0
-          @color(Math.min(stamp.count, 40))
-        else
-          '#ededed'
-      .attr 'data-container', 'body'
-      .on 'click', @clickDay
-
-  renderDayTitles: ->
-    days = [{
-      text: 'M'
-      y: 29 + (@daySizeWithSpace * 1)
-    }, {
-      text: 'W'
-      y: 29 + (@daySizeWithSpace * 3)
-    }, {
-      text: 'F'
-      y: 29 + (@daySizeWithSpace * 5)
-    }]
-    @svg.append 'g'
-      .selectAll 'text'
-      .data days
-      .enter()
-      .append 'text'
-      .attr 'text-anchor', 'middle'
-      .attr 'x', 8
-      .attr 'y', (day) ->
-        day.y
-      .text (day) ->
-        day.text
-      .attr 'class', 'user-contrib-text'
-
-  renderMonths: ->
-    @svg.append 'g'
-      .selectAll 'text'
-      .data @months
-      .enter()
-      .append 'text'
-      .attr 'x', (date) ->
-        date.x
-      .attr 'y', 10
-      .attr 'class', 'user-contrib-text'
-      .text (date) =>
-        @monthNames[date.month]
-
-  renderKey: ->
-    keyColors = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
-    @svg.append 'g'
-      .attr 'transform', "translate(18, #{@daySizeWithSpace * 8 + 16})"
-      .selectAll 'rect'
-      .data keyColors
-      .enter()
-      .append 'rect'
-      .attr 'width', @daySize
-      .attr 'height', @daySize
-      .attr 'x', (color, i) =>
-        @daySizeWithSpace * i
-      .attr 'y', 0
-      .attr 'fill', (color) ->
-        color
-
-  initColor: ->
-    colorRange = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
-    d3.scale
-      .threshold()
-      .domain([0, 10, 20, 30])
-      .range(colorRange)
-
-  initColorKey: ->
-    d3.scale
-      .linear()
-      .range(['#acd5f2', '#254e77'])
-      .domain([0, 3])
-
-  clickDay: (stamp) =>
-    if @currentSelectedDate isnt stamp.date
-      @currentSelectedDate = stamp.date
-      formatted_date = @currentSelectedDate.getFullYear() + "-" + (@currentSelectedDate.getMonth()+1) + "-" + @currentSelectedDate.getDate()
-
-      $.ajax
-        url: @calendar_activities_path
-        data:
-          date: formatted_date
-        cache: false
-        dataType: 'html'
-        beforeSend: ->
-          $('.user-calendar-activities').html '<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>'
-        success: (data) ->
-          $('.user-calendar-activities').html data
-    else
-      $('.user-calendar-activities').html ''
-
-  initTooltips: ->
-    $('.js-contrib-calendar .js-tooltip').tooltip
-      html: true
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..b95faadc8e72f17e7cdb90eab9203622916bee02
--- /dev/null
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -0,0 +1,7 @@
+
+/*= require_tree . */
+
+(function() {
+
+
+}).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..4af2a214e12bc48f9cdf58168a523f34dd2f09f4
--- /dev/null
+++ b/app/assets/javascripts/users_select.js
@@ -0,0 +1,344 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+    slice = [].slice;
+
+  this.UsersSelect = (function() {
+    function UsersSelect(currentUser) {
+      this.users = bind(this.users, this);
+      this.user = bind(this.user, this);
+      this.usersPath = "/autocomplete/users.json";
+      this.userPath = "/autocomplete/users/:id.json";
+      if (currentUser != null) {
+        this.currentUser = JSON.parse(currentUser);
+      }
+      $('.js-user-search').each((function(_this) {
+        return function(i, dropdown) {
+          var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser;
+          $dropdown = $(dropdown);
+          _this.projectId = $dropdown.data('project-id');
+          _this.showCurrentUser = $dropdown.data('current-user');
+          showNullUser = $dropdown.data('null-user');
+          showAnyUser = $dropdown.data('any-user');
+          firstUser = $dropdown.data('first-user');
+          _this.authorId = $dropdown.data('author-id');
+          selectedId = $dropdown.data('selected');
+          defaultLabel = $dropdown.data('default-label');
+          issueURL = $dropdown.data('issueUpdate');
+          $selectbox = $dropdown.closest('.selectbox');
+          $block = $selectbox.closest('.block');
+          abilityName = $dropdown.data('ability-name');
+          $value = $block.find('.value');
+          $collapsedSidebar = $block.find('.sidebar-collapsed-user');
+          $loading = $block.find('.block-loading').fadeOut();
+          $block.on('click', '.js-assign-yourself', function(e) {
+            e.preventDefault();
+            return assignTo(_this.currentUser.id);
+          });
+          assignTo = function(selected) {
+            var data;
+            data = {};
+            data[abilityName] = {};
+            data[abilityName].assignee_id = selected != null ? selected : null;
+            $loading.fadeIn();
+            $dropdown.trigger('loading.gl.dropdown');
+            return $.ajax({
+              type: 'PUT',
+              dataType: 'json',
+              url: issueURL,
+              data: data
+            }).done(function(data) {
+              var user;
+              $dropdown.trigger('loaded.gl.dropdown');
+              $loading.fadeOut();
+              $selectbox.hide();
+              if (data.assignee) {
+                user = {
+                  name: data.assignee.name,
+                  username: data.assignee.username,
+                  avatar: data.assignee.avatar_url
+                };
+              } else {
+                user = {
+                  name: 'Unassigned',
+                  username: '',
+                  avatar: ''
+                };
+              }
+              $value.html(assigneeTemplate(user));
+              $collapsedSidebar.attr('title', user.name).tooltip('fixTitle');
+              return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
+            });
+          };
+          collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/u/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
+          assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/u/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
+          return $dropdown.glDropdown({
+            data: function(term, callback) {
+              var isAuthorFilter;
+              isAuthorFilter = $('.js-author-search');
+              return _this.users(term, function(users) {
+                var anyUser, index, j, len, name, obj, showDivider;
+                if (term.length === 0) {
+                  showDivider = 0;
+                  if (firstUser) {
+                    for (index = j = 0, len = users.length; j < len; index = ++j) {
+                      obj = users[index];
+                      if (obj.username === firstUser) {
+                        users.splice(index, 1);
+                        users.unshift(obj);
+                        break;
+                      }
+                    }
+                  }
+                  if (showNullUser) {
+                    showDivider += 1;
+                    users.unshift({
+                      beforeDivider: true,
+                      name: 'Unassigned',
+                      id: 0
+                    });
+                  }
+                  if (showAnyUser) {
+                    showDivider += 1;
+                    name = showAnyUser;
+                    if (name === true) {
+                      name = 'Any User';
+                    }
+                    anyUser = {
+                      beforeDivider: true,
+                      name: name,
+                      id: null
+                    };
+                    users.unshift(anyUser);
+                  }
+                }
+                if (showDivider) {
+                  users.splice(showDivider, 0, "divider");
+                }
+                return callback(users);
+              });
+            },
+            filterable: true,
+            filterRemote: true,
+            search: {
+              fields: ['name', 'username']
+            },
+            selectable: true,
+            fieldName: $dropdown.data('field-name'),
+            toggleLabel: function(selected) {
+              if (selected && 'id' in selected) {
+                if (selected.text) {
+                  return selected.text;
+                } else {
+                  return selected.name;
+                }
+              } else {
+                return defaultLabel;
+              }
+            },
+            inputId: 'issue_assignee_id',
+            hidden: function(e) {
+              $selectbox.hide();
+              return $value.css('display', '');
+            },
+            clicked: function(user) {
+              var isIssueIndex, isMRIndex, page, selected;
+              page = $('body').data('page');
+              isIssueIndex = page === 'projects:issues:index';
+              isMRIndex = (page === page && page === 'projects:merge_requests:index');
+              if ($dropdown.hasClass('js-filter-bulk-update')) {
+                return;
+              }
+              if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+                selectedId = user.id;
+                return Issuable.filterResults($dropdown.closest('form'));
+              } else if ($dropdown.hasClass('js-filter-submit')) {
+                return $dropdown.closest('form').submit();
+              } else {
+                selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
+                return assignTo(selected);
+              }
+            },
+            renderRow: function(user) {
+              var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username;
+              username = user.username ? "@" + user.username : "";
+              avatar = user.avatar_url ? user.avatar_url : false;
+              selected = user.id === selectedId ? "is-active" : "";
+              img = "";
+              if (user.beforeDivider != null) {
+                "<li> <a href='#' class='" + selected + "'> " + user.name + " </a> </li>";
+              } else {
+                if (avatar) {
+                  img = "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />";
+                }
+              }
+              listWithName = "<li> <a href='#' class='dropdown-menu-user-link " + selected + "'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
+              listWithUserName = "<span class='dropdown-menu-user-username'> " + username + " </span>";
+              listClosingTags = "</a> </li>";
+              if (username === '') {
+                listWithUserName = '';
+              }
+              return listWithName + listWithUserName + listClosingTags;
+            }
+          });
+        };
+      })(this));
+      $('.ajax-users-select').each((function(_this) {
+        return function(i, select) {
+          var firstUser, showAnyUser, showEmailUser, showNullUser;
+          _this.projectId = $(select).data('project-id');
+          _this.groupId = $(select).data('group-id');
+          _this.showCurrentUser = $(select).data('current-user');
+          _this.authorId = $(select).data('author-id');
+          _this.skipUsers = $(select).data('skip-users');
+          showNullUser = $(select).data('null-user');
+          showAnyUser = $(select).data('any-user');
+          showEmailUser = $(select).data('email-user');
+          firstUser = $(select).data('first-user');
+          return $(select).select2({
+            placeholder: "Search for a user",
+            multiple: $(select).hasClass('multiselect'),
+            minimumInputLength: 0,
+            query: function(query) {
+              return _this.users(query.term, function(users) {
+                var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref;
+                data = {
+                  results: users
+                };
+                if (query.term.length === 0) {
+                  if (firstUser) {
+                    ref = data.results;
+                    for (index = j = 0, len = ref.length; j < len; index = ++j) {
+                      obj = ref[index];
+                      if (obj.username === firstUser) {
+                        data.results.splice(index, 1);
+                        data.results.unshift(obj);
+                        break;
+                      }
+                    }
+                  }
+                  if (showNullUser) {
+                    nullUser = {
+                      name: 'Unassigned',
+                      id: 0
+                    };
+                    data.results.unshift(nullUser);
+                  }
+                  if (showAnyUser) {
+                    name = showAnyUser;
+                    if (name === true) {
+                      name = 'Any User';
+                    }
+                    anyUser = {
+                      name: name,
+                      id: null
+                    };
+                    data.results.unshift(anyUser);
+                  }
+                }
+                if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
+                  emailUser = {
+                    name: "Invite \"" + query.term + "\"",
+                    username: query.term,
+                    id: query.term
+                  };
+                  data.results.unshift(emailUser);
+                }
+                return query.callback(data);
+              });
+            },
+            initSelection: function() {
+              var args;
+              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+              return _this.initSelection.apply(_this, args);
+            },
+            formatResult: function() {
+              var args;
+              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+              return _this.formatResult.apply(_this, args);
+            },
+            formatSelection: function() {
+              var args;
+              args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+              return _this.formatSelection.apply(_this, args);
+            },
+            dropdownCssClass: "ajax-users-dropdown",
+            escapeMarkup: function(m) {
+              return m;
+            }
+          });
+        };
+      })(this));
+    }
+
+    UsersSelect.prototype.initSelection = function(element, callback) {
+      var id, nullUser;
+      id = $(element).val();
+      if (id === "0") {
+        nullUser = {
+          name: 'Unassigned'
+        };
+        return callback(nullUser);
+      } else if (id !== "") {
+        return this.user(id, callback);
+      }
+    };
+
+    UsersSelect.prototype.formatResult = function(user) {
+      var avatar;
+      if (user.avatar_url) {
+        avatar = user.avatar_url;
+      } else {
+        avatar = gon.default_avatar_url;
+      }
+      return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar s24' src='" + avatar + "'></div> <div class='user-name'>" + user.name + "</div> <div class='user-username'>" + (user.username || "") + "</div> </div>";
+    };
+
+    UsersSelect.prototype.formatSelection = function(user) {
+      return user.name;
+    };
+
+    UsersSelect.prototype.user = function(user_id, callback) {
+      var url;
+      url = this.buildUrl(this.userPath);
+      url = url.replace(':id', user_id);
+      return $.ajax({
+        url: url,
+        dataType: "json"
+      }).done(function(user) {
+        return callback(user);
+      });
+    };
+
+    UsersSelect.prototype.users = function(query, callback) {
+      var url;
+      url = this.buildUrl(this.usersPath);
+      return $.ajax({
+        url: url,
+        data: {
+          search: query,
+          per_page: 20,
+          active: true,
+          project_id: this.projectId,
+          group_id: this.groupId,
+          current_user: this.showCurrentUser,
+          author_id: this.authorId,
+          skip_users: this.skipUsers
+        },
+        dataType: "json"
+      }).done(function(users) {
+        return callback(users);
+      });
+    };
+
+    UsersSelect.prototype.buildUrl = function(url) {
+      if (gon.relative_url_root != null) {
+        url = gon.relative_url_root.replace(/\/$/, '') + url;
+      }
+      return url;
+    };
+
+    return UsersSelect;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
deleted file mode 100644
index 344be811e0dfc82d56717cf987efd2d6aee0eacc..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/users_select.js.coffee
+++ /dev/null
@@ -1,330 +0,0 @@
-class @UsersSelect
-  constructor: (currentUser) ->
-    @usersPath = "/autocomplete/users.json"
-    @userPath = "/autocomplete/users/:id.json"
-    if currentUser?
-      @currentUser = JSON.parse(currentUser)
-
-    $('.js-user-search').each (i, dropdown) =>
-      $dropdown = $(dropdown)
-      @projectId = $dropdown.data('project-id')
-      @showCurrentUser = $dropdown.data('current-user')
-      showNullUser = $dropdown.data('null-user')
-      showAnyUser = $dropdown.data('any-user')
-      firstUser = $dropdown.data('first-user')
-      @authorId = $dropdown.data('author-id')
-      selectedId = $dropdown.data('selected')
-      defaultLabel = $dropdown.data('default-label')
-      issueURL = $dropdown.data('issueUpdate')
-      $selectbox = $dropdown.closest('.selectbox')
-      $block = $selectbox.closest('.block')
-      abilityName = $dropdown.data('ability-name')
-      $value = $block.find('.value')
-      $collapsedSidebar = $block.find('.sidebar-collapsed-user')
-      $loading = $block.find('.block-loading').fadeOut()
-
-      $block.on('click', '.js-assign-yourself', (e) =>
-        e.preventDefault()
-        assignTo(@currentUser.id)
-      )
-
-      assignTo = (selected) ->
-        data = {}
-        data[abilityName] = {}
-        data[abilityName].assignee_id = if selected? then selected else null
-        $loading
-          .fadeIn()
-        $dropdown.trigger('loading.gl.dropdown')
-        $.ajax(
-          type: 'PUT'
-          dataType: 'json'
-          url: issueURL
-          data: data
-        ).done (data) ->
-          $dropdown.trigger('loaded.gl.dropdown')
-          $loading.fadeOut()
-          $selectbox.hide()
-
-          if data.assignee
-            user =
-              name: data.assignee.name
-              username: data.assignee.username
-              avatar: data.assignee.avatar_url
-          else
-            user =
-              name: 'Unassigned'
-              username: ''
-              avatar: ''
-          $value.html(assigneeTemplate(user))
-
-          $collapsedSidebar
-            .attr('title', user.name)
-            .tooltip('fixTitle')
-
-          $collapsedSidebar.html(collapsedAssigneeTemplate(user))
-
-
-      collapsedAssigneeTemplate = _.template(
-        '<% if( avatar ) { %>
-        <a class="author_link" href="/u/<%- username %>">
-          <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>">
-        </a>
-        <% } else { %>
-        <i class="fa fa-user"></i>
-        <% } %>'
-      )
-
-      assigneeTemplate = _.template(
-        '<% if (username) { %>
-        <a class="author_link bold" href="/u/<%- username %>">
-          <% if( avatar ) { %>
-          <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>">
-          <% } %>
-          <span class="author"><%- name %></span>
-          <span class="username">
-            @<%- username %>
-          </span>
-        </a>
-          <% } else { %>
-        <span class="no-value assign-yourself">
-          No assignee -
-          <a href="#" class="js-assign-yourself">
-            assign yourself
-          </a>
-        </span>
-          <% } %>'
-      )
-
-      $dropdown.glDropdown(
-        data: (term, callback) =>
-          isAuthorFilter = $('.js-author-search')
-
-          @users term, (users) =>
-            if term.length is 0
-              showDivider = 0
-
-              if firstUser
-                # Move current user to the front of the list
-                for obj, index in users
-                  if obj.username == firstUser
-                    users.splice(index, 1)
-                    users.unshift(obj)
-                    break
-
-              if showNullUser
-                showDivider += 1
-                users.unshift(
-                  beforeDivider: true
-                  name: 'Unassigned',
-                  id: 0
-                )
-
-              if showAnyUser
-                showDivider += 1
-                name = showAnyUser
-                name = 'Any User' if name == true
-                anyUser = {
-                  beforeDivider: true
-                  name: name,
-                  id: null
-                }
-                users.unshift(anyUser)
-
-            if showDivider
-              users.splice(showDivider, 0, "divider")
-
-            # Send the data back
-            callback users
-        filterable: true
-        filterRemote: true
-        search:
-          fields: ['name', 'username']
-        selectable: true
-        fieldName: $dropdown.data('field-name')
-
-        toggleLabel: (selected) ->
-          if selected && 'id' of selected
-            if selected.text then selected.text else selected.name
-          else
-            defaultLabel
-
-        inputId: 'issue_assignee_id'
-
-        hidden: (e) ->
-          $selectbox.hide()
-          # display:block overrides the hide-collapse rule
-          $value.css('display', '')
-
-        clicked: (user) ->
-          page = $('body').data 'page'
-          isIssueIndex = page is 'projects:issues:index'
-          isMRIndex = page is page is 'projects:merge_requests:index'
-          if $dropdown.hasClass('js-filter-bulk-update')
-            return
-
-          if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
-            selectedId = user.id
-            Issuable.filterResults $dropdown.closest('form')
-          else if $dropdown.hasClass 'js-filter-submit'
-            $dropdown.closest('form').submit()
-          else
-            selected = $dropdown
-              .closest('.selectbox')
-              .find("input[name='#{$dropdown.data('field-name')}']").val()
-            assignTo(selected)
-
-        renderRow: (user) ->
-          username = if user.username then "@#{user.username}" else ""
-          avatar = if user.avatar_url then user.avatar_url else false
-          selected = if user.id is selectedId then "is-active" else ""
-          img = ""
-
-          if user.beforeDivider?
-            "<li>
-              <a href='#' class='#{selected}'>
-                #{user.name}
-              </a>
-            </li>"
-          else
-            if avatar
-              img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
-
-          # split into three parts so we can remove the username section if nessesary
-          listWithName = "<li>
-            <a href='#' class='dropdown-menu-user-link #{selected}'>
-              #{img}
-              <strong class='dropdown-menu-user-full-name'>
-                #{user.name}
-              </strong>"
-
-          listWithUserName = "<span class='dropdown-menu-user-username'>
-                #{username}
-              </span>"
-          listClosingTags = "</a>
-          </li>"
-
-
-          if username is ''
-            listWithUserName = ''
-
-          listWithName + listWithUserName + listClosingTags
-      )
-
-    $('.ajax-users-select').each (i, select) =>
-      @projectId = $(select).data('project-id')
-      @groupId = $(select).data('group-id')
-      @showCurrentUser = $(select).data('current-user')
-      @authorId = $(select).data('author-id')
-      showNullUser = $(select).data('null-user')
-      showAnyUser = $(select).data('any-user')
-      showEmailUser = $(select).data('email-user')
-      firstUser = $(select).data('first-user')
-
-      $(select).select2
-        placeholder: "Search for a user"
-        multiple: $(select).hasClass('multiselect')
-        minimumInputLength: 0
-        query: (query) =>
-          @users query.term, (users) =>
-            data = { results: users }
-
-            if query.term.length == 0
-              if firstUser
-                # Move current user to the front of the list
-                for obj, index in data.results
-                  if obj.username == firstUser
-                    data.results.splice(index, 1)
-                    data.results.unshift(obj)
-                    break
-
-              if showNullUser
-                nullUser = {
-                  name: 'Unassigned',
-                  id: 0
-                }
-                data.results.unshift(nullUser)
-
-              if showAnyUser
-                name = showAnyUser
-                name = 'Any User' if name == true
-                anyUser = {
-                  name: name,
-                  id: null
-                }
-                data.results.unshift(anyUser)
-
-            if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/)
-              emailUser = {
-                name: "Invite \"#{query.term}\"",
-                username: query.term,
-                id: query.term
-              }
-              data.results.unshift(emailUser)
-
-            query.callback(data)
-
-        initSelection: (args...) =>
-          @initSelection(args...)
-        formatResult: (args...) =>
-          @formatResult(args...)
-        formatSelection: (args...) =>
-          @formatSelection(args...)
-        dropdownCssClass: "ajax-users-dropdown"
-        escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
-          m
-
-  initSelection: (element, callback) ->
-    id = $(element).val()
-    if id == "0"
-      nullUser = { name: 'Unassigned' }
-      callback(nullUser)
-    else if id != ""
-      @user(id, callback)
-
-  formatResult: (user) ->
-    if user.avatar_url
-      avatar = user.avatar_url
-    else
-      avatar = gon.default_avatar_url
-
-    "<div class='user-result #{'no-username' unless user.username}'>
-       <div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
-       <div class='user-name'>#{user.name}</div>
-       <div class='user-username'>#{user.username || ""}</div>
-     </div>"
-
-  formatSelection: (user) ->
-    user.name
-
-  user: (user_id, callback) =>
-    url = @buildUrl(@userPath)
-    url = url.replace(':id', user_id)
-
-    $.ajax(
-      url: url
-      dataType: "json"
-    ).done (user) ->
-      callback(user)
-
-  # Return users list. Filtered by query
-  # Only active users retrieved
-  users: (query, callback) =>
-    url = @buildUrl(@usersPath)
-
-    $.ajax(
-      url: url
-      data:
-        search: query
-        per_page: 20
-        active: true
-        project_id: @projectId
-        group_id: @groupId
-        current_user: @showCurrentUser
-        author_id: @authorId
-      dataType: "json"
-    ).done (users) ->
-      callback(users)
-
-  buildUrl: (url) ->
-    url = gon.relative_url_root.replace(/\/$/, '') + url if gon.relative_url_root?
-    return url
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
new file mode 100644
index 0000000000000000000000000000000000000000..35401231fbf9c817e1d804a4a9345350edbeab4b
--- /dev/null
+++ b/app/assets/javascripts/wikis.js
@@ -0,0 +1,37 @@
+
+/*= require latinise */
+
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.Wikis = (function() {
+    function Wikis() {
+      this.slugify = bind(this.slugify, this);
+      $('.new-wiki-page').on('submit', (function(_this) {
+        return function(e) {
+          var field, path, slug;
+          $('[data-error~=slug]').addClass('hidden');
+          field = $('#new_wiki_path');
+          slug = _this.slugify(field.val());
+          if (slug.length > 0) {
+            path = field.attr('data-wikis-path');
+            location.href = path + '/' + slug;
+            return e.preventDefault();
+          }
+        };
+      })(this));
+    }
+
+    Wikis.prototype.dasherize = function(value) {
+      return value.replace(/[_\s]+/g, '-');
+    };
+
+    Wikis.prototype.slugify = function(value) {
+      return this.dasherize(value.trim().toLowerCase().latinise());
+    };
+
+    return Wikis;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
deleted file mode 100644
index 1ee827f1fa3c2b4c7dc93db15a9b6374024b4d3f..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/wikis.js.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require latinise
-
-class @Wikis
-  constructor: ->
-    $('.new-wiki-page').on 'submit', (e) =>
-      $('[data-error~=slug]').addClass('hidden')
-      field = $('#new_wiki_path')
-      slug = @slugify(field.val())
-
-      if (slug.length > 0)
-        path = field.attr('data-wikis-path')
-        location.href = path + '/' + slug
-        e.preventDefault()
-
-  dasherize: (value) ->
-    value.replace(/[_\s]+/g, '-')
-
-  slugify: (value) =>
-    @dasherize(value.trim().toLowerCase().latinise())
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
new file mode 100644
index 0000000000000000000000000000000000000000..71236c6a27d1c552a83d1b0f6b34d94d6f186bfd
--- /dev/null
+++ b/app/assets/javascripts/zen_mode.js
@@ -0,0 +1,80 @@
+
+/*= provides zen_mode:enter */
+
+
+/*= provides zen_mode:leave */
+
+
+/*= require jquery.scrollTo */
+
+
+/*= require dropzone */
+
+
+/*= require mousetrap */
+
+
+/*= require mousetrap/pause */
+
+(function() {
+  this.ZenMode = (function() {
+    function ZenMode() {
+      this.active_backdrop = null;
+      this.active_textarea = null;
+      $(document).on('click', '.js-zen-enter', function(e) {
+        e.preventDefault();
+        return $(e.currentTarget).trigger('zen_mode:enter');
+      });
+      $(document).on('click', '.js-zen-leave', function(e) {
+        e.preventDefault();
+        return $(e.currentTarget).trigger('zen_mode:leave');
+      });
+      $(document).on('zen_mode:enter', (function(_this) {
+        return function(e) {
+          return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
+        };
+      })(this));
+      $(document).on('zen_mode:leave', (function(_this) {
+        return function(e) {
+          return _this.exit();
+        };
+      })(this));
+      $(document).on('keydown', function(e) {
+        if (e.keyCode === 27) {
+          e.preventDefault();
+          return $(document).trigger('zen_mode:leave');
+        }
+      });
+    }
+
+    ZenMode.prototype.enter = function(backdrop) {
+      Mousetrap.pause();
+      this.active_backdrop = $(backdrop);
+      this.active_backdrop.addClass('fullscreen');
+      this.active_textarea = this.active_backdrop.find('textarea');
+      this.active_textarea.removeAttr('style');
+      return this.active_textarea.focus();
+    };
+
+    ZenMode.prototype.exit = function() {
+      if (this.active_textarea) {
+        Mousetrap.unpause();
+        this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
+        this.scrollTo(this.active_textarea);
+        this.active_textarea = null;
+        this.active_backdrop = null;
+        return Dropzone.forElement('.div-dropzone').enable();
+      }
+    };
+
+    ZenMode.prototype.scrollTo = function(zen_area) {
+      return $.scrollTo(zen_area, 0, {
+        offset: -150
+      });
+    };
+
+    return ZenMode;
+
+  })();
+
+}).call(this);
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
deleted file mode 100644
index 99f35ecfb0fbc4ae69a731d927bbdd69809925fa..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ /dev/null
@@ -1,80 +0,0 @@
-# Zen Mode (full screen) textarea
-#
-#= provides zen_mode:enter
-#= provides zen_mode:leave
-#
-#= require jquery.scrollTo
-#= require dropzone
-#= require mousetrap
-#= require mousetrap/pause
-#
-# ### Events
-#
-# `zen_mode:enter`
-#
-# Fired when the "Edit in fullscreen" link is clicked.
-#
-# **Synchronicity** Sync
-# **Bubbles** Yes
-# **Cancelable** No
-# **Target** a.js-zen-enter
-#
-# `zen_mode:leave`
-#
-# Fired when the "Leave Fullscreen" link is clicked.
-#
-# **Synchronicity** Sync
-# **Bubbles** Yes
-# **Cancelable** No
-# **Target** a.js-zen-leave
-#
-class @ZenMode
-  constructor: ->
-    @active_backdrop = null
-    @active_textarea = null
-
-    $(document).on 'click', '.js-zen-enter', (e) ->
-      e.preventDefault()
-      $(e.currentTarget).trigger('zen_mode:enter')
-
-    $(document).on 'click', '.js-zen-leave', (e) ->
-      e.preventDefault()
-      $(e.currentTarget).trigger('zen_mode:leave')
-
-    $(document).on 'zen_mode:enter', (e) =>
-      @enter($(e.target).closest('.md-area').find('.zen-backdrop'))
-    $(document).on 'zen_mode:leave', (e) =>
-      @exit()
-
-    $(document).on 'keydown', (e) ->
-      if e.keyCode == 27 # Esc
-        e.preventDefault()
-        $(document).trigger('zen_mode:leave')
-
-  enter: (backdrop) ->
-    Mousetrap.pause()
-
-    @active_backdrop = $(backdrop)
-    @active_backdrop.addClass('fullscreen')
-
-    @active_textarea = @active_backdrop.find('textarea')
-
-    # Prevent a user-resized textarea from persisting to fullscreen
-    @active_textarea.removeAttr('style')
-    @active_textarea.focus()
-
-  exit: ->
-    if @active_textarea
-      Mousetrap.unpause()
-
-      @active_textarea.closest('.zen-backdrop').removeClass('fullscreen')
-
-      @scrollTo(@active_textarea)
-
-      @active_textarea = null
-      @active_backdrop = null
-
-      Dropzone.forElement('.div-dropzone').enable()
-
-  scrollTo: (zen_area) ->
-    $.scrollTo(zen_area, 0, offset: -150)
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 8b6ddf8ba18d7542db144b3144cf760073a99672..c79b22d4d21036cdd690fd9a9e332a6814c299d9 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -5,6 +5,7 @@
   height: 40px;
   padding: 0;
   @include border-radius($avatar_radius);
+  border: 1px solid rgba(0, 0, 0, .1);
 
   &.avatar-inline {
     float: none;
@@ -15,8 +16,9 @@
     &.s24 { margin-right: 4px; }
   }
 
-  &.group-avatar, &.project-avatar, &.avatar-tile {
+  &.avatar-tile {
     @include border-radius(0);
+    border: none;
   }
 
   &.s16 { width: 16px; height: 16px; margin-right: 6px; }
@@ -43,12 +45,12 @@
   &.s16 { font-size: 12px; line-height: 1.33; }
   &.s24 { font-size: 14px; line-height: 1.8; }
   &.s26 { font-size: 20px; line-height: 1.33; }
-  &.s32 { font-size: 20px; line-height: 32px; }
-  &.s40 { font-size: 16px; line-height: 40px; }
-  &.s60 { font-size: 32px; line-height: 60px; }
-  &.s70 { font-size: 34px; line-height: 70px; }
-  &.s90 { font-size: 36px; line-height: 90px; }
-  &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
-  &.s140 { font-size: 72px; line-height: 140px; }
-  &.s160 { font-size: 96px; line-height: 160px; }
+  &.s32 { font-size: 20px; line-height: 30px; }
+  &.s40 { font-size: 16px; line-height: 38px; }
+  &.s60 { font-size: 32px; line-height: 58px; }
+  &.s70 { font-size: 34px; line-height: 68px; }
+  &.s90 { font-size: 36px; line-height: 88px; }
+  &.s110 { font-size: 40px; line-height: 108px; font-weight: 300; }
+  &.s140 { font-size: 72px; line-height: 138px; }
+  &.s160 { font-size: 96px; line-height: 158px; }
 }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index ad94e457cfdd523b234f4ccee5468a15c825ad25..7ce203d2ec7bc0e6d3ea50b2dbf7683480baa011 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -232,7 +232,9 @@
 .nav-block {
   .controls {
     float: right;
-    margin-top: 11px;
+    margin-top: 8px;
+    padding-bottom: 7px;
+    border-bottom: 1px solid $border-color;
   }
 }
 
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 590b8f54363bf06a0d738d1a38d47f80be57c95e..473530cf09435059b18da95a578463a97bed37ea 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -49,6 +49,17 @@
     border-color: $border-dark;
     color: $color;
   }
+
+  svg {
+
+    path {
+      fill: $color;
+    }
+
+    use {
+      stroke: $color;
+    }
+  }
 }
 
 @mixin btn-green {
@@ -124,6 +135,15 @@
     @include btn-green;
   }
 
+  &.btn-inverted {
+    &.btn-success,
+    &.btn-new,
+    &.btn-create,
+    &.btn-save {
+      @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light);
+    }
+  }
+
   &.btn-gray {
     @include btn-gray;
   }
@@ -173,6 +193,13 @@
   .caret {
     margin-left: 5px;
   }
+
+  svg {
+    height: 15px;
+    width: auto;
+    position: relative;
+    top: 2px;
+  }
 }
 
 .btn-lg {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4e900f80ef453b0796b6bfadf4f60dcd321ed36..c54eb0d64790824cced0fd1df5f3718dd7ca0231 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -350,6 +350,7 @@
 
 .dropdown-input-field, .default-dropdown-input {
   width: 100%;
+  min-height: 30px;
   padding: 0 7px;
   color: $dropdown-input-color;
   line-height: 30px;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 5d3273ea64ddc8c50012b8ce6aadf0ccb42a9812..96565da1bc9ae788741cdaa94f4bf339e5cd015e 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -98,13 +98,30 @@
 
 .md {
   &.md-preview-holder {
-    code {
-      white-space: pre-wrap;
-      word-break: keep-all;
-    }
-
+    // Reset ul style types since we're nested inside a ul already
     @include bulleted-list;
   }
+
+  // On diffs code should wrap nicely and not overflow
+  code {
+    white-space: pre-wrap;
+    word-break: keep-all;
+  }
+
+  hr {
+    // Darken 'whitesmoke' a bit to make it more visible in note bodies
+    border-color: darken(#f5f5f5, 8%);
+    margin: 10px 0;
+  }
+
+  // Border around images in issue and MR comments.
+  img:not(.emoji) {
+    border: 1px solid $table-border-gray;
+    padding: 5px;
+    margin: 5px 0;
+    // Ensure that image does not exceed viewport
+    max-height: calc(100vh - 100px);
+  }
 }
 
 .toolbar-group {
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index d52e8f0017292d3efc74b986a6790f2757668fa0..3fa4a22258dfa3eb25dda78b70c46802a2cc1134 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -198,6 +198,10 @@ header.header-pinned-nav {
 
   .sidebar-collapsed-icon {
     cursor: pointer;
+
+    .btn {
+      background-color: $gray-light;
+    }
   }
 }
 
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index f0e7002e4cd6367ef27344df5fea1dc9872f2798..1882d4e888db70bd8e700269b9d84995b68df8e2 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -16,7 +16,7 @@ $border-color:          #e5e5e5;
 $focus-border-color:    #3aabf0;
 $table-border-color:    #f0f0f0;
 $background-color:      #fafafa;
-$dark-background-color: #f7f7f7;
+$dark-background-color: #f5f5f5;
 $table-text-gray:       #8f8f8f;
 
 /*
diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss
index 7f645d3089d0e22b13e8ac2430c180337fe26704..33aedf1f7c101c002fbf04d661ca35a75f285582 100644
--- a/app/assets/stylesheets/mailers/repository_push_email.scss
+++ b/app/assets/stylesheets/mailers/repository_push_email.scss
@@ -10,83 +10,48 @@
 // preference): plain class selectors, type (element name) selectors, or
 // explicit child selectors.
 
-table.code {
-  width: 100%;
+.code {
+  background-color: #fff;
   font-family: monospace;
-  border: none;
-  border-collapse: separate;
-  margin: 0;
-  padding: 0;
+  font-size: $code_font_size;
   -premailer-cellpadding: 0;
   -premailer-cellspacing: 0;
   -premailer-width: 100%;
 
-  > tr > td {
+  > tr {
     line-height: $code_line_height;
-    font-family: monospace;
-    font-size: $code_font_size;
-
-    &.diff-line-num {
-      margin: 0;
-      padding: 0;
-      border: none;
-      padding: 0 5px;
-      border-right: 1px solid;
-      text-align: right;
-      min-width: 35px;
-      max-width: 50px;
-      width: 35px;
-    }
-
-    &.line_content {
-      display: block;
-      margin: 0;
-      padding: 0 0.5em;
-      border: none;
-      white-space: pre;
-    }
   }
 }
 
-.line-numbers, .diff-line-num {
+.diff-line-num {
+  padding: 0 5px;
+  text-align: right;
+  width: 35px;
   background-color: $background-color;
-}
-
-.diff-line-num, .diff-line-num a {
   color: $black-transparent;
-}
-
-pre.code, .diff-line-num {
-  border-color: $table-border-gray;
-}
+  border-right: 1px solid $table-border-gray;
 
-.code.white, pre.code, .line_content {
-  background-color: #fff;
-  color: #333;
-}
-
-.diff-line-num {
   &.old {
     background-color: $line-number-old;
-    border-color: $line-removed-dark;
+    border-right-color: $line-removed-dark;
   }
 
   &.new {
     background-color: $line-number-new;
-    border-color: $line-added-dark;
-  }
-
-  &.hll:not(.empty-cell) {
-    background-color: $line-number-select;
-    border-color: $line-select-yellow-dark;
+    border-right-color: $line-added-dark;
   }
 }
 
 .line_content {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+  white-space: pre;
+
   &.old {
     background-color: $line-removed;
 
-    > .line > span.idiff, > .line > span > span.idiff {
+    > .line > span.idiff,
+    > .line > span > span.idiff {
       background-color: $line-removed-dark;
     }
   }
@@ -94,7 +59,8 @@ pre.code, .diff-line-num {
   &.new {
     background-color: $line-added;
 
-    > .line > span.idiff, > .line > span > span.idiff {
+    > .line > span.idiff,
+    > .line > span > span.idiff {
       background-color: $line-added-dark;
     }
   }
@@ -103,14 +69,6 @@ pre.code, .diff-line-num {
     color: $black-transparent;
     background-color: $match-line;
   }
-
-  &.hll:not(.empty-cell) {
-    background-color: $line-select-yellow;
-  }
-}
-
-pre > .hll {
-  background-color: #f8eec7 !important;
 }
 
 span.highlight_word {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 99a2cd306cfb85c672b47e402d6c2c9af2ed7dc9..e26f8f7080d7ae94f7abf91ccac6c63dbb9f783a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -53,6 +53,14 @@
       left: 70px;
     }
   }
+
+  .nav-links {
+    svg {
+      position: relative;
+      top: 2px;
+      margin-right: 3px;
+    }
+  }
 }
 
 .build-header {
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 35ab28b3fea7563f65234c03c5526353e6701a38..bbe0c6c5f1fa1842bcf0528c402ecb3701c80a90 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -68,6 +68,12 @@
   }
 }
 
+.ci-status-link {
+  svg {
+    overflow: visible;
+  }
+}
+
 .commit-box {
   border-top: 1px solid $border-color;
 
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 0298577c494cfb1641a9995af5c49181f1630d58..6a58b445afaf580328f8c2b85a7b7b82d136ad56 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,8 +1,6 @@
 .commits-compare-switch {
   @include btn-default;
   @include btn-white;
-  background: image-url("switch_icon.png") no-repeat center center;
-  text-indent: -9999px;
   float: left;
   margin-right: 9px;
 }
@@ -61,6 +59,10 @@
     font-size: 0;
   }
 
+  .ci-status-link {
+    display: inline-block;
+  }
+
   .btn-clipboard, .btn-transparent {
     padding-left: 0;
     padding-right: 0;
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index cf7567513ec6945aedfcc6f883a007af3b871e8a..42928ee279c252c15401dc91f83eb08344ec0ceb 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -36,10 +36,6 @@
 
 .dash-project-avatar {
   float: left;
-
-  .avatar {
-    @include border-radius(50%);
-  }
 }
 
 .dash-project-access-icon {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 21b1c223c88b8432d2fa35f2de1f716385bb92ec..21cee2e3a70cced31cea9364f02e9ac5278b8f24 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -164,7 +164,10 @@
       line-height: 0;
       img {
         border: 1px solid #fff;
-        background: image-url('trans_bg.gif');
+        background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%),
+        linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%);
+        background-size: 10px 10px;
+        background-position: 0 0, 5px 5px;
         max-width: 100%;
       }
       &.deleted {
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
index b731abc7450aa4cec8efe9bb2eacbd805a9f10f1..f17797b23819706aa4b129e5e6afb253b9dbfe8f 100644
--- a/app/assets/stylesheets/pages/emojis.scss
+++ b/app/assets/stylesheets/pages/emojis.scss
@@ -1,4 +1,4 @@
-.emoji-0023-20E3 { background-position: 0 0; }
+.emoji-0023-20E3 { background-position: 0 0px; }
 .emoji-002A-20E3 { background-position: -20px 0; }
 .emoji-0030-20E3 { background-position: 0 -20px; }
 .emoji-0031-20E3 { background-position: -20px -20px; }
@@ -452,1271 +452,1344 @@
 .emoji-1F391 { background-position: -420px -200px; }
 .emoji-1F392 { background-position: -420px -220px; }
 .emoji-1F393 { background-position: -420px -240px; }
-.emoji-1F394 { background-position: -420px -260px; }
-.emoji-1F395 { background-position: -420px -280px; }
-.emoji-1F396 { background-position: -420px -300px; }
-.emoji-1F397 { background-position: -420px -320px; }
-.emoji-1F398 { background-position: -420px -340px; }
-.emoji-1F399 { background-position: -420px -360px; }
-.emoji-1F39A { background-position: -420px -380px; }
-.emoji-1F39B { background-position: -420px -400px; }
-.emoji-1F39C { background-position: 0 -420px; }
-.emoji-1F39D { background-position: -20px -420px; }
-.emoji-1F39E { background-position: -40px -420px; }
-.emoji-1F39F { background-position: -60px -420px; }
-.emoji-1F3A0 { background-position: -80px -420px; }
-.emoji-1F3A1 { background-position: -100px -420px; }
-.emoji-1F3A2 { background-position: -120px -420px; }
-.emoji-1F3A3 { background-position: -140px -420px; }
-.emoji-1F3A4 { background-position: -160px -420px; }
-.emoji-1F3A5 { background-position: -180px -420px; }
-.emoji-1F3A6 { background-position: -200px -420px; }
-.emoji-1F3A7 { background-position: -220px -420px; }
-.emoji-1F3A8 { background-position: -240px -420px; }
-.emoji-1F3A9 { background-position: -260px -420px; }
-.emoji-1F3AA { background-position: -280px -420px; }
-.emoji-1F3AB { background-position: -300px -420px; }
-.emoji-1F3AC { background-position: -320px -420px; }
-.emoji-1F3AD { background-position: -340px -420px; }
-.emoji-1F3AE { background-position: -360px -420px; }
-.emoji-1F3AF { background-position: -380px -420px; }
-.emoji-1F3B0 { background-position: -400px -420px; }
-.emoji-1F3B1 { background-position: -420px -420px; }
-.emoji-1F3B2 { background-position: -440px 0; }
-.emoji-1F3B3 { background-position: -440px -20px; }
-.emoji-1F3B4 { background-position: -440px -40px; }
-.emoji-1F3B5 { background-position: -440px -60px; }
-.emoji-1F3B6 { background-position: -440px -80px; }
-.emoji-1F3B7 { background-position: -440px -100px; }
-.emoji-1F3B8 { background-position: -440px -120px; }
-.emoji-1F3B9 { background-position: -440px -140px; }
-.emoji-1F3BA { background-position: -440px -160px; }
-.emoji-1F3BB { background-position: -440px -180px; }
-.emoji-1F3BC { background-position: -440px -200px; }
-.emoji-1F3BD { background-position: -440px -220px; }
-.emoji-1F3BE { background-position: -440px -240px; }
-.emoji-1F3BF { background-position: -440px -260px; }
-.emoji-1F3C0 { background-position: -440px -280px; }
-.emoji-1F3C1 { background-position: -440px -300px; }
-.emoji-1F3C2 { background-position: -440px -320px; }
-.emoji-1F3C3 { background-position: -440px -340px; }
-.emoji-1F3C3-1F3FB { background-position: -440px -360px; }
-.emoji-1F3C3-1F3FC { background-position: -440px -380px; }
-.emoji-1F3C3-1F3FD { background-position: -440px -400px; }
-.emoji-1F3C3-1F3FE { background-position: -440px -420px; }
-.emoji-1F3C3-1F3FF { background-position: 0 -440px; }
-.emoji-1F3C4 { background-position: -20px -440px; }
-.emoji-1F3C4-1F3FB { background-position: -40px -440px; }
-.emoji-1F3C4-1F3FC { background-position: -60px -440px; }
-.emoji-1F3C4-1F3FD { background-position: -80px -440px; }
-.emoji-1F3C4-1F3FE { background-position: -100px -440px; }
-.emoji-1F3C4-1F3FF { background-position: -120px -440px; }
-.emoji-1F3C5 { background-position: -140px -440px; }
-.emoji-1F3C6 { background-position: -160px -440px; }
-.emoji-1F3C7 { background-position: -180px -440px; }
-.emoji-1F3C7-1F3FB { background-position: -200px -440px; }
-.emoji-1F3C7-1F3FC { background-position: -220px -440px; }
-.emoji-1F3C7-1F3FD { background-position: -240px -440px; }
-.emoji-1F3C7-1F3FE { background-position: -260px -440px; }
-.emoji-1F3C7-1F3FF { background-position: -280px -440px; }
-.emoji-1F3C8 { background-position: -300px -440px; }
-.emoji-1F3C9 { background-position: -320px -440px; }
-.emoji-1F3CA { background-position: -340px -440px; }
-.emoji-1F3CA-1F3FB { background-position: -360px -440px; }
-.emoji-1F3CA-1F3FC { background-position: -380px -440px; }
-.emoji-1F3CA-1F3FD { background-position: -400px -440px; }
-.emoji-1F3CA-1F3FE { background-position: -420px -440px; }
-.emoji-1F3CA-1F3FF { background-position: -440px -440px; }
-.emoji-1F3CB { background-position: -460px 0; }
-.emoji-1F3CB-1F3FB { background-position: -460px -20px; }
-.emoji-1F3CB-1F3FC { background-position: -460px -40px; }
-.emoji-1F3CB-1F3FD { background-position: -460px -60px; }
-.emoji-1F3CB-1F3FE { background-position: -460px -80px; }
-.emoji-1F3CB-1F3FF { background-position: -460px -100px; }
-.emoji-1F3CC { background-position: -460px -120px; }
-.emoji-1F3CD { background-position: -460px -140px; }
-.emoji-1F3CE { background-position: -460px -160px; }
-.emoji-1F3CF { background-position: -460px -180px; }
-.emoji-1F3D0 { background-position: -460px -200px; }
-.emoji-1F3D1 { background-position: -460px -220px; }
-.emoji-1F3D2 { background-position: -460px -240px; }
-.emoji-1F3D3 { background-position: -460px -260px; }
-.emoji-1F3D4 { background-position: -460px -280px; }
-.emoji-1F3D5 { background-position: -460px -300px; }
-.emoji-1F3D6 { background-position: -460px -320px; }
-.emoji-1F3D7 { background-position: -460px -340px; }
-.emoji-1F3D8 { background-position: -460px -360px; }
-.emoji-1F3D9 { background-position: -460px -380px; }
-.emoji-1F3DA { background-position: -460px -400px; }
-.emoji-1F3DB { background-position: -460px -420px; }
-.emoji-1F3DC { background-position: -460px -440px; }
-.emoji-1F3DD { background-position: 0 -460px; }
-.emoji-1F3DE { background-position: -20px -460px; }
-.emoji-1F3DF { background-position: -40px -460px; }
-.emoji-1F3E0 { background-position: -60px -460px; }
-.emoji-1F3E1 { background-position: -80px -460px; }
-.emoji-1F3E2 { background-position: -100px -460px; }
-.emoji-1F3E3 { background-position: -120px -460px; }
-.emoji-1F3E4 { background-position: -140px -460px; }
-.emoji-1F3E5 { background-position: -160px -460px; }
-.emoji-1F3E6 { background-position: -180px -460px; }
-.emoji-1F3E7 { background-position: -200px -460px; }
-.emoji-1F3E8 { background-position: -220px -460px; }
-.emoji-1F3E9 { background-position: -240px -460px; }
-.emoji-1F3EA { background-position: -260px -460px; }
-.emoji-1F3EB { background-position: -280px -460px; }
-.emoji-1F3EC { background-position: -300px -460px; }
-.emoji-1F3ED { background-position: -320px -460px; }
-.emoji-1F3EE { background-position: -340px -460px; }
-.emoji-1F3EF { background-position: -360px -460px; }
-.emoji-1F3F0 { background-position: -380px -460px; }
-.emoji-1F3F1 { background-position: -400px -460px; }
-.emoji-1F3F2 { background-position: -420px -460px; }
-.emoji-1F3F3 { background-position: -440px -460px; }
-.emoji-1F3F4 { background-position: -460px -460px; }
-.emoji-1F3F5 { background-position: -480px 0; }
-.emoji-1F3F6 { background-position: -480px -20px; }
-.emoji-1F3F7 { background-position: -480px -40px; }
-.emoji-1F3F8 { background-position: -480px -60px; }
-.emoji-1F3F9 { background-position: -480px -80px; }
-.emoji-1F3FA { background-position: -480px -100px; }
-.emoji-1F3FB { background-position: -480px -120px; }
-.emoji-1F3FC { background-position: -480px -140px; }
-.emoji-1F3FD { background-position: -480px -160px; }
-.emoji-1F3FE { background-position: -480px -180px; }
-.emoji-1F3FF { background-position: -480px -200px; }
-.emoji-1F400 { background-position: -480px -220px; }
-.emoji-1F401 { background-position: -480px -240px; }
-.emoji-1F402 { background-position: -480px -260px; }
-.emoji-1F403 { background-position: -480px -280px; }
-.emoji-1F404 { background-position: -480px -300px; }
-.emoji-1F405 { background-position: -480px -320px; }
-.emoji-1F406 { background-position: -480px -340px; }
-.emoji-1F407 { background-position: -480px -360px; }
-.emoji-1F408 { background-position: -480px -380px; }
-.emoji-1F409 { background-position: -480px -400px; }
-.emoji-1F40A { background-position: -480px -420px; }
-.emoji-1F40B { background-position: -480px -440px; }
-.emoji-1F40C { background-position: -480px -460px; }
-.emoji-1F40D { background-position: 0 -480px; }
-.emoji-1F40E { background-position: -20px -480px; }
-.emoji-1F40F { background-position: -40px -480px; }
-.emoji-1F410 { background-position: -60px -480px; }
-.emoji-1F411 { background-position: -80px -480px; }
-.emoji-1F412 { background-position: -100px -480px; }
-.emoji-1F413 { background-position: -120px -480px; }
-.emoji-1F414 { background-position: -140px -480px; }
-.emoji-1F415 { background-position: -160px -480px; }
-.emoji-1F416 { background-position: -180px -480px; }
-.emoji-1F417 { background-position: -200px -480px; }
-.emoji-1F418 { background-position: -220px -480px; }
-.emoji-1F419 { background-position: -240px -480px; }
-.emoji-1F41A { background-position: -260px -480px; }
-.emoji-1F41B { background-position: -280px -480px; }
-.emoji-1F41C { background-position: -300px -480px; }
-.emoji-1F41D { background-position: -320px -480px; }
-.emoji-1F41E { background-position: -340px -480px; }
-.emoji-1F41F { background-position: -360px -480px; }
-.emoji-1F420 { background-position: -380px -480px; }
-.emoji-1F421 { background-position: -400px -480px; }
-.emoji-1F422 { background-position: -420px -480px; }
-.emoji-1F423 { background-position: -440px -480px; }
-.emoji-1F424 { background-position: -460px -480px; }
-.emoji-1F425 { background-position: -480px -480px; }
-.emoji-1F426 { background-position: -500px 0; }
-.emoji-1F427 { background-position: -500px -20px; }
-.emoji-1F428 { background-position: -500px -40px; }
-.emoji-1F429 { background-position: -500px -60px; }
-.emoji-1F42A { background-position: -500px -80px; }
-.emoji-1F42B { background-position: -500px -100px; }
-.emoji-1F42C { background-position: -500px -120px; }
-.emoji-1F42D { background-position: -500px -140px; }
-.emoji-1F42E { background-position: -500px -160px; }
-.emoji-1F42F { background-position: -500px -180px; }
-.emoji-1F430 { background-position: -500px -200px; }
-.emoji-1F431 { background-position: -500px -220px; }
-.emoji-1F432 { background-position: -500px -240px; }
-.emoji-1F433 { background-position: -500px -260px; }
-.emoji-1F434 { background-position: -500px -280px; }
-.emoji-1F435 { background-position: -500px -300px; }
-.emoji-1F436 { background-position: -500px -320px; }
-.emoji-1F437 { background-position: -500px -340px; }
-.emoji-1F438 { background-position: -500px -360px; }
-.emoji-1F439 { background-position: -500px -380px; }
-.emoji-1F43A { background-position: -500px -400px; }
-.emoji-1F43B { background-position: -500px -420px; }
-.emoji-1F43C { background-position: -500px -440px; }
-.emoji-1F43D { background-position: -500px -460px; }
-.emoji-1F43E { background-position: -500px -480px; }
-.emoji-1F43F { background-position: 0 -500px; }
-.emoji-1F440 { background-position: -20px -500px; }
-.emoji-1F441 { background-position: -40px -500px; }
-.emoji-1F441-1F5E8 { background-position: -60px -500px; }
-.emoji-1F442 { background-position: -80px -500px; }
-.emoji-1F442-1F3FB { background-position: -100px -500px; }
-.emoji-1F442-1F3FC { background-position: -120px -500px; }
-.emoji-1F442-1F3FD { background-position: -140px -500px; }
-.emoji-1F442-1F3FE { background-position: -160px -500px; }
-.emoji-1F442-1F3FF { background-position: -180px -500px; }
-.emoji-1F443 { background-position: -200px -500px; }
-.emoji-1F443-1F3FB { background-position: -220px -500px; }
-.emoji-1F443-1F3FC { background-position: -240px -500px; }
-.emoji-1F443-1F3FD { background-position: -260px -500px; }
-.emoji-1F443-1F3FE { background-position: -280px -500px; }
-.emoji-1F443-1F3FF { background-position: -300px -500px; }
-.emoji-1F444 { background-position: -320px -500px; }
-.emoji-1F445 { background-position: -340px -500px; }
-.emoji-1F446 { background-position: -360px -500px; }
-.emoji-1F446-1F3FB { background-position: -380px -500px; }
-.emoji-1F446-1F3FC { background-position: -400px -500px; }
-.emoji-1F446-1F3FD { background-position: -420px -500px; }
-.emoji-1F446-1F3FE { background-position: -440px -500px; }
-.emoji-1F446-1F3FF { background-position: -460px -500px; }
-.emoji-1F447 { background-position: -480px -500px; }
-.emoji-1F447-1F3FB { background-position: -500px -500px; }
-.emoji-1F447-1F3FC { background-position: -520px 0; }
-.emoji-1F447-1F3FD { background-position: -520px -20px; }
-.emoji-1F447-1F3FE { background-position: -520px -40px; }
-.emoji-1F447-1F3FF { background-position: -520px -60px; }
-.emoji-1F448 { background-position: -520px -80px; }
-.emoji-1F448-1F3FB { background-position: -520px -100px; }
-.emoji-1F448-1F3FC { background-position: -520px -120px; }
-.emoji-1F448-1F3FD { background-position: -520px -140px; }
-.emoji-1F448-1F3FE { background-position: -520px -160px; }
-.emoji-1F448-1F3FF { background-position: -520px -180px; }
-.emoji-1F449 { background-position: -520px -200px; }
-.emoji-1F449-1F3FB { background-position: -520px -220px; }
-.emoji-1F449-1F3FC { background-position: -520px -240px; }
-.emoji-1F449-1F3FD { background-position: -520px -260px; }
-.emoji-1F449-1F3FE { background-position: -520px -280px; }
-.emoji-1F449-1F3FF { background-position: -520px -300px; }
-.emoji-1F44A { background-position: -520px -320px; }
-.emoji-1F44A-1F3FB { background-position: -520px -340px; }
-.emoji-1F44A-1F3FC { background-position: -520px -360px; }
-.emoji-1F44A-1F3FD { background-position: -520px -380px; }
-.emoji-1F44A-1F3FE { background-position: -520px -400px; }
-.emoji-1F44A-1F3FF { background-position: -520px -420px; }
-.emoji-1F44B { background-position: -520px -440px; }
-.emoji-1F44B-1F3FB { background-position: -520px -460px; }
-.emoji-1F44B-1F3FC { background-position: -520px -480px; }
-.emoji-1F44B-1F3FD { background-position: -520px -500px; }
-.emoji-1F44B-1F3FE { background-position: 0 -520px; }
-.emoji-1F44B-1F3FF { background-position: -20px -520px; }
-.emoji-1F44C { background-position: -40px -520px; }
-.emoji-1F44C-1F3FB { background-position: -60px -520px; }
-.emoji-1F44C-1F3FC { background-position: -80px -520px; }
-.emoji-1F44C-1F3FD { background-position: -100px -520px; }
-.emoji-1F44C-1F3FE { background-position: -120px -520px; }
-.emoji-1F44C-1F3FF { background-position: -140px -520px; }
-.emoji-1F44D { background-position: -160px -520px; }
-.emoji-1F44D-1F3FB { background-position: -180px -520px; }
-.emoji-1F44D-1F3FC { background-position: -200px -520px; }
-.emoji-1F44D-1F3FD { background-position: -220px -520px; }
-.emoji-1F44D-1F3FE { background-position: -240px -520px; }
-.emoji-1F44D-1F3FF { background-position: -260px -520px; }
-.emoji-1F44E { background-position: -280px -520px; }
-.emoji-1F44E-1F3FB { background-position: -300px -520px; }
-.emoji-1F44E-1F3FC { background-position: -320px -520px; }
-.emoji-1F44E-1F3FD { background-position: -340px -520px; }
-.emoji-1F44E-1F3FE { background-position: -360px -520px; }
-.emoji-1F44E-1F3FF { background-position: -380px -520px; }
-.emoji-1F44F { background-position: -400px -520px; }
-.emoji-1F44F-1F3FB { background-position: -420px -520px; }
-.emoji-1F44F-1F3FC { background-position: -440px -520px; }
-.emoji-1F44F-1F3FD { background-position: -460px -520px; }
-.emoji-1F44F-1F3FE { background-position: -480px -520px; }
-.emoji-1F44F-1F3FF { background-position: -500px -520px; }
-.emoji-1F450 { background-position: -520px -520px; }
-.emoji-1F450-1F3FB { background-position: -540px 0; }
-.emoji-1F450-1F3FC { background-position: -540px -20px; }
-.emoji-1F450-1F3FD { background-position: -540px -40px; }
-.emoji-1F450-1F3FE { background-position: -540px -60px; }
-.emoji-1F450-1F3FF { background-position: -540px -80px; }
-.emoji-1F451 { background-position: -540px -100px; }
-.emoji-1F452 { background-position: -540px -120px; }
-.emoji-1F453 { background-position: -540px -140px; }
-.emoji-1F454 { background-position: -540px -160px; }
-.emoji-1F455 { background-position: -540px -180px; }
-.emoji-1F456 { background-position: -540px -200px; }
-.emoji-1F457 { background-position: -540px -220px; }
-.emoji-1F458 { background-position: -540px -240px; }
-.emoji-1F459 { background-position: -540px -260px; }
-.emoji-1F45A { background-position: -540px -280px; }
-.emoji-1F45B { background-position: -540px -300px; }
-.emoji-1F45C { background-position: -540px -320px; }
-.emoji-1F45D { background-position: -540px -340px; }
-.emoji-1F45E { background-position: -540px -360px; }
-.emoji-1F45F { background-position: -540px -380px; }
-.emoji-1F460 { background-position: -540px -400px; }
-.emoji-1F461 { background-position: -540px -420px; }
-.emoji-1F462 { background-position: -540px -440px; }
-.emoji-1F463 { background-position: -540px -460px; }
-.emoji-1F464 { background-position: -540px -480px; }
-.emoji-1F465 { background-position: -540px -500px; }
-.emoji-1F466 { background-position: -540px -520px; }
-.emoji-1F466-1F3FB { background-position: 0 -540px; }
-.emoji-1F466-1F3FC { background-position: -20px -540px; }
-.emoji-1F466-1F3FD { background-position: -40px -540px; }
-.emoji-1F466-1F3FE { background-position: -60px -540px; }
-.emoji-1F466-1F3FF { background-position: -80px -540px; }
-.emoji-1F467 { background-position: -100px -540px; }
-.emoji-1F467-1F3FB { background-position: -120px -540px; }
-.emoji-1F467-1F3FC { background-position: -140px -540px; }
-.emoji-1F467-1F3FD { background-position: -160px -540px; }
-.emoji-1F467-1F3FE { background-position: -180px -540px; }
-.emoji-1F467-1F3FF { background-position: -200px -540px; }
-.emoji-1F468 { background-position: -220px -540px; }
-.emoji-1F468-1F3FB { background-position: -240px -540px; }
-.emoji-1F468-1F3FC { background-position: -260px -540px; }
-.emoji-1F468-1F3FD { background-position: -280px -540px; }
-.emoji-1F468-1F3FE { background-position: -300px -540px; }
-.emoji-1F468-1F3FF { background-position: -320px -540px; }
-.emoji-1F468-1F468-1F466 { background-position: -340px -540px; }
-.emoji-1F468-1F468-1F466-1F466 { background-position: -360px -540px; }
-.emoji-1F468-1F468-1F467 { background-position: -380px -540px; }
-.emoji-1F468-1F468-1F467-1F466 { background-position: -400px -540px; }
-.emoji-1F468-1F468-1F467-1F467 { background-position: -420px -540px; }
-.emoji-1F468-1F469-1F466-1F466 { background-position: -440px -540px; }
-.emoji-1F468-1F469-1F467 { background-position: -460px -540px; }
-.emoji-1F468-1F469-1F467-1F466 { background-position: -480px -540px; }
-.emoji-1F468-1F469-1F467-1F467 { background-position: -500px -540px; }
-.emoji-1F468-2764-1F468 { background-position: -520px -540px; }
-.emoji-1F468-2764-1F48B-1F468 { background-position: -540px -540px; }
-.emoji-1F469 { background-position: -560px 0; }
-.emoji-1F469-1F3FB { background-position: -560px -20px; }
-.emoji-1F469-1F3FC { background-position: -560px -40px; }
-.emoji-1F469-1F3FD { background-position: -560px -60px; }
-.emoji-1F469-1F3FE { background-position: -560px -80px; }
-.emoji-1F469-1F3FF { background-position: -560px -100px; }
-.emoji-1F469-1F469-1F466 { background-position: -560px -120px; }
-.emoji-1F469-1F469-1F466-1F466 { background-position: -560px -140px; }
-.emoji-1F469-1F469-1F467 { background-position: -560px -160px; }
-.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -180px; }
-.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -200px; }
-.emoji-1F469-2764-1F469 { background-position: -560px -220px; }
-.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -240px; }
-.emoji-1F46A { background-position: -560px -260px; }
-.emoji-1F46B { background-position: -560px -280px; }
-.emoji-1F46C { background-position: -560px -300px; }
-.emoji-1F46D { background-position: -560px -320px; }
-.emoji-1F46E { background-position: -560px -340px; }
-.emoji-1F46E-1F3FB { background-position: -560px -360px; }
-.emoji-1F46E-1F3FC { background-position: -560px -380px; }
-.emoji-1F46E-1F3FD { background-position: -560px -400px; }
-.emoji-1F46E-1F3FE { background-position: -560px -420px; }
-.emoji-1F46E-1F3FF { background-position: -560px -440px; }
-.emoji-1F46F { background-position: -560px -460px; }
-.emoji-1F470 { background-position: -560px -480px; }
-.emoji-1F470-1F3FB { background-position: -560px -500px; }
-.emoji-1F470-1F3FC { background-position: -560px -520px; }
-.emoji-1F470-1F3FD { background-position: -560px -540px; }
-.emoji-1F470-1F3FE { background-position: 0 -560px; }
-.emoji-1F470-1F3FF { background-position: -20px -560px; }
-.emoji-1F471 { background-position: -40px -560px; }
-.emoji-1F471-1F3FB { background-position: -60px -560px; }
-.emoji-1F471-1F3FC { background-position: -80px -560px; }
-.emoji-1F471-1F3FD { background-position: -100px -560px; }
-.emoji-1F471-1F3FE { background-position: -120px -560px; }
-.emoji-1F471-1F3FF { background-position: -140px -560px; }
-.emoji-1F472 { background-position: -160px -560px; }
-.emoji-1F472-1F3FB { background-position: -180px -560px; }
-.emoji-1F472-1F3FC { background-position: -200px -560px; }
-.emoji-1F472-1F3FD { background-position: -220px -560px; }
-.emoji-1F472-1F3FE { background-position: -240px -560px; }
-.emoji-1F472-1F3FF { background-position: -260px -560px; }
-.emoji-1F473 { background-position: -280px -560px; }
-.emoji-1F473-1F3FB { background-position: -300px -560px; }
-.emoji-1F473-1F3FC { background-position: -320px -560px; }
-.emoji-1F473-1F3FD { background-position: -340px -560px; }
-.emoji-1F473-1F3FE { background-position: -360px -560px; }
-.emoji-1F473-1F3FF { background-position: -380px -560px; }
-.emoji-1F474 { background-position: -400px -560px; }
-.emoji-1F474-1F3FB { background-position: -420px -560px; }
-.emoji-1F474-1F3FC { background-position: -440px -560px; }
-.emoji-1F474-1F3FD { background-position: -460px -560px; }
-.emoji-1F474-1F3FE { background-position: -480px -560px; }
-.emoji-1F474-1F3FF { background-position: -500px -560px; }
-.emoji-1F475 { background-position: -520px -560px; }
-.emoji-1F475-1F3FB { background-position: -540px -560px; }
-.emoji-1F475-1F3FC { background-position: -560px -560px; }
-.emoji-1F475-1F3FD { background-position: -580px 0; }
-.emoji-1F475-1F3FE { background-position: -580px -20px; }
-.emoji-1F475-1F3FF { background-position: -580px -40px; }
-.emoji-1F476 { background-position: -580px -60px; }
-.emoji-1F476-1F3FB { background-position: -580px -80px; }
-.emoji-1F476-1F3FC { background-position: -580px -100px; }
-.emoji-1F476-1F3FD { background-position: -580px -120px; }
-.emoji-1F476-1F3FE { background-position: -580px -140px; }
-.emoji-1F476-1F3FF { background-position: -580px -160px; }
-.emoji-1F477 { background-position: -580px -180px; }
-.emoji-1F477-1F3FB { background-position: -580px -200px; }
-.emoji-1F477-1F3FC { background-position: -580px -220px; }
-.emoji-1F477-1F3FD { background-position: -580px -240px; }
-.emoji-1F477-1F3FE { background-position: -580px -260px; }
-.emoji-1F477-1F3FF { background-position: -580px -280px; }
-.emoji-1F478 { background-position: -580px -300px; }
-.emoji-1F478-1F3FB { background-position: -580px -320px; }
-.emoji-1F478-1F3FC { background-position: -580px -340px; }
-.emoji-1F478-1F3FD { background-position: -580px -360px; }
-.emoji-1F478-1F3FE { background-position: -580px -380px; }
-.emoji-1F478-1F3FF { background-position: -580px -400px; }
-.emoji-1F479 { background-position: -580px -420px; }
-.emoji-1F47A { background-position: -580px -440px; }
-.emoji-1F47B { background-position: -580px -460px; }
-.emoji-1F47C { background-position: -580px -480px; }
-.emoji-1F47C-1F3FB { background-position: -580px -500px; }
-.emoji-1F47C-1F3FC { background-position: -580px -520px; }
-.emoji-1F47C-1F3FD { background-position: -580px -540px; }
-.emoji-1F47C-1F3FE { background-position: -580px -560px; }
-.emoji-1F47C-1F3FF { background-position: 0 -580px; }
-.emoji-1F47D { background-position: -20px -580px; }
-.emoji-1F47E { background-position: -40px -580px; }
-.emoji-1F47F { background-position: -60px -580px; }
-.emoji-1F480 { background-position: -80px -580px; }
-.emoji-1F481 { background-position: -100px -580px; }
-.emoji-1F481-1F3FB { background-position: -120px -580px; }
-.emoji-1F481-1F3FC { background-position: -140px -580px; }
-.emoji-1F481-1F3FD { background-position: -160px -580px; }
-.emoji-1F481-1F3FE { background-position: -180px -580px; }
-.emoji-1F481-1F3FF { background-position: -200px -580px; }
-.emoji-1F482 { background-position: -220px -580px; }
-.emoji-1F482-1F3FB { background-position: -240px -580px; }
-.emoji-1F482-1F3FC { background-position: -260px -580px; }
-.emoji-1F482-1F3FD { background-position: -280px -580px; }
-.emoji-1F482-1F3FE { background-position: -300px -580px; }
-.emoji-1F482-1F3FF { background-position: -320px -580px; }
-.emoji-1F483 { background-position: -340px -580px; }
-.emoji-1F483-1F3FB { background-position: -360px -580px; }
-.emoji-1F483-1F3FC { background-position: -380px -580px; }
-.emoji-1F483-1F3FD { background-position: -400px -580px; }
-.emoji-1F483-1F3FE { background-position: -420px -580px; }
-.emoji-1F483-1F3FF { background-position: -440px -580px; }
-.emoji-1F484 { background-position: -460px -580px; }
-.emoji-1F485 { background-position: -480px -580px; }
-.emoji-1F485-1F3FB { background-position: -500px -580px; }
-.emoji-1F485-1F3FC { background-position: -520px -580px; }
-.emoji-1F485-1F3FD { background-position: -540px -580px; }
-.emoji-1F485-1F3FE { background-position: -560px -580px; }
-.emoji-1F485-1F3FF { background-position: -580px -580px; }
-.emoji-1F486 { background-position: -600px 0; }
-.emoji-1F486-1F3FB { background-position: -600px -20px; }
-.emoji-1F486-1F3FC { background-position: -600px -40px; }
-.emoji-1F486-1F3FD { background-position: -600px -60px; }
-.emoji-1F486-1F3FE { background-position: -600px -80px; }
-.emoji-1F486-1F3FF { background-position: -600px -100px; }
-.emoji-1F487 { background-position: -600px -120px; }
-.emoji-1F487-1F3FB { background-position: -600px -140px; }
-.emoji-1F487-1F3FC { background-position: -600px -160px; }
-.emoji-1F487-1F3FD { background-position: -600px -180px; }
-.emoji-1F487-1F3FE { background-position: -600px -200px; }
-.emoji-1F487-1F3FF { background-position: -600px -220px; }
-.emoji-1F488 { background-position: -600px -240px; }
-.emoji-1F489 { background-position: -600px -260px; }
-.emoji-1F48A { background-position: -600px -280px; }
-.emoji-1F48B { background-position: -600px -300px; }
-.emoji-1F48C { background-position: -600px -320px; }
-.emoji-1F48D { background-position: -600px -340px; }
-.emoji-1F48E { background-position: -600px -360px; }
-.emoji-1F48F { background-position: -600px -380px; }
-.emoji-1F490 { background-position: -600px -400px; }
-.emoji-1F491 { background-position: -600px -420px; }
-.emoji-1F492 { background-position: -600px -440px; }
-.emoji-1F493 { background-position: -600px -460px; }
-.emoji-1F494 { background-position: -600px -480px; }
-.emoji-1F495 { background-position: -600px -500px; }
-.emoji-1F496 { background-position: -600px -520px; }
-.emoji-1F497 { background-position: -600px -540px; }
-.emoji-1F498 { background-position: -600px -560px; }
-.emoji-1F499 { background-position: -600px -580px; }
-.emoji-1F49A { background-position: 0 -600px; }
-.emoji-1F49B { background-position: -20px -600px; }
-.emoji-1F49C { background-position: -40px -600px; }
-.emoji-1F49D { background-position: -60px -600px; }
-.emoji-1F49E { background-position: -80px -600px; }
-.emoji-1F49F { background-position: -100px -600px; }
-.emoji-1F4A0 { background-position: -120px -600px; }
-.emoji-1F4A1 { background-position: -140px -600px; }
-.emoji-1F4A2 { background-position: -160px -600px; }
-.emoji-1F4A3 { background-position: -180px -600px; }
-.emoji-1F4A4 { background-position: -200px -600px; }
-.emoji-1F4A5 { background-position: -220px -600px; }
-.emoji-1F4A6 { background-position: -240px -600px; }
-.emoji-1F4A7 { background-position: -260px -600px; }
-.emoji-1F4A8 { background-position: -280px -600px; }
-.emoji-1F4A9 { background-position: -300px -600px; }
-.emoji-1F4AA { background-position: -320px -600px; }
-.emoji-1F4AA-1F3FB { background-position: -340px -600px; }
-.emoji-1F4AA-1F3FC { background-position: -360px -600px; }
-.emoji-1F4AA-1F3FD { background-position: -380px -600px; }
-.emoji-1F4AA-1F3FE { background-position: -400px -600px; }
-.emoji-1F4AA-1F3FF { background-position: -420px -600px; }
-.emoji-1F4AB { background-position: -440px -600px; }
-.emoji-1F4AC { background-position: -460px -600px; }
-.emoji-1F4AD { background-position: -480px -600px; }
-.emoji-1F4AE { background-position: -500px -600px; }
-.emoji-1F4AF { background-position: -520px -600px; }
-.emoji-1F4B0 { background-position: -540px -600px; }
-.emoji-1F4B1 { background-position: -560px -600px; }
-.emoji-1F4B2 { background-position: -580px -600px; }
-.emoji-1F4B3 { background-position: -600px -600px; }
-.emoji-1F4B4 { background-position: -620px 0; }
-.emoji-1F4B5 { background-position: -620px -20px; }
-.emoji-1F4B6 { background-position: -620px -40px; }
-.emoji-1F4B7 { background-position: -620px -60px; }
-.emoji-1F4B8 { background-position: -620px -80px; }
-.emoji-1F4B9 { background-position: -620px -100px; }
-.emoji-1F4BA { background-position: -620px -120px; }
-.emoji-1F4BB { background-position: -620px -140px; }
-.emoji-1F4BC { background-position: -620px -160px; }
-.emoji-1F4BD { background-position: -620px -180px; }
-.emoji-1F4BE { background-position: -620px -200px; }
-.emoji-1F4BF { background-position: -620px -220px; }
-.emoji-1F4C0 { background-position: -620px -240px; }
-.emoji-1F4C1 { background-position: -620px -260px; }
-.emoji-1F4C2 { background-position: -620px -280px; }
-.emoji-1F4C3 { background-position: -620px -300px; }
-.emoji-1F4C4 { background-position: -620px -320px; }
-.emoji-1F4C5 { background-position: -620px -340px; }
-.emoji-1F4C6 { background-position: -620px -360px; }
-.emoji-1F4C7 { background-position: -620px -380px; }
-.emoji-1F4C8 { background-position: -620px -400px; }
-.emoji-1F4C9 { background-position: -620px -420px; }
-.emoji-1F4CA { background-position: -620px -440px; }
-.emoji-1F4CB { background-position: -620px -460px; }
-.emoji-1F4CC { background-position: -620px -480px; }
-.emoji-1F4CD { background-position: -620px -500px; }
-.emoji-1F4CE { background-position: -620px -520px; }
-.emoji-1F4CF { background-position: -620px -540px; }
-.emoji-1F4D0 { background-position: -620px -560px; }
-.emoji-1F4D1 { background-position: -620px -580px; }
-.emoji-1F4D2 { background-position: -620px -600px; }
-.emoji-1F4D3 { background-position: 0 -620px; }
-.emoji-1F4D4 { background-position: -20px -620px; }
-.emoji-1F4D5 { background-position: -40px -620px; }
-.emoji-1F4D6 { background-position: -60px -620px; }
-.emoji-1F4D7 { background-position: -80px -620px; }
-.emoji-1F4D8 { background-position: -100px -620px; }
-.emoji-1F4D9 { background-position: -120px -620px; }
-.emoji-1F4DA { background-position: -140px -620px; }
-.emoji-1F4DB { background-position: -160px -620px; }
-.emoji-1F4DC { background-position: -180px -620px; }
-.emoji-1F4DD { background-position: -200px -620px; }
-.emoji-1F4DE { background-position: -220px -620px; }
-.emoji-1F4DF { background-position: -240px -620px; }
-.emoji-1F4E0 { background-position: -260px -620px; }
-.emoji-1F4E1 { background-position: -280px -620px; }
-.emoji-1F4E2 { background-position: -300px -620px; }
-.emoji-1F4E3 { background-position: -320px -620px; }
-.emoji-1F4E4 { background-position: -340px -620px; }
-.emoji-1F4E5 { background-position: -360px -620px; }
-.emoji-1F4E6 { background-position: -380px -620px; }
-.emoji-1F4E7 { background-position: -400px -620px; }
-.emoji-1F4E8 { background-position: -420px -620px; }
-.emoji-1F4E9 { background-position: -440px -620px; }
-.emoji-1F4EA { background-position: -460px -620px; }
-.emoji-1F4EB { background-position: -480px -620px; }
-.emoji-1F4EC { background-position: -500px -620px; }
-.emoji-1F4ED { background-position: -520px -620px; }
-.emoji-1F4EE { background-position: -540px -620px; }
-.emoji-1F4EF { background-position: -560px -620px; }
-.emoji-1F4F0 { background-position: -580px -620px; }
-.emoji-1F4F1 { background-position: -600px -620px; }
-.emoji-1F4F2 { background-position: -620px -620px; }
-.emoji-1F4F3 { background-position: -640px 0; }
-.emoji-1F4F4 { background-position: -640px -20px; }
-.emoji-1F4F5 { background-position: -640px -40px; }
-.emoji-1F4F6 { background-position: -640px -60px; }
-.emoji-1F4F7 { background-position: -640px -80px; }
-.emoji-1F4F8 { background-position: -640px -100px; }
-.emoji-1F4F9 { background-position: -640px -120px; }
-.emoji-1F4FA { background-position: -640px -140px; }
-.emoji-1F4FB { background-position: -640px -160px; }
-.emoji-1F4FC { background-position: -640px -180px; }
-.emoji-1F4FD { background-position: -640px -200px; }
-.emoji-1F4FE { background-position: -640px -220px; }
-.emoji-1F4FF { background-position: -640px -240px; }
-.emoji-1F500 { background-position: -640px -260px; }
-.emoji-1F501 { background-position: -640px -280px; }
-.emoji-1F502 { background-position: -640px -300px; }
-.emoji-1F503 { background-position: -640px -320px; }
-.emoji-1F504 { background-position: -640px -340px; }
-.emoji-1F505 { background-position: -640px -360px; }
-.emoji-1F506 { background-position: -640px -380px; }
-.emoji-1F507 { background-position: -640px -400px; }
-.emoji-1F508 { background-position: -640px -420px; }
-.emoji-1F509 { background-position: -640px -440px; }
-.emoji-1F50A { background-position: -640px -460px; }
-.emoji-1F50B { background-position: -640px -480px; }
-.emoji-1F50C { background-position: -640px -500px; }
-.emoji-1F50D { background-position: -640px -520px; }
-.emoji-1F50E { background-position: -640px -540px; }
-.emoji-1F50F { background-position: -640px -560px; }
-.emoji-1F510 { background-position: -640px -580px; }
-.emoji-1F511 { background-position: -640px -600px; }
-.emoji-1F512 { background-position: -640px -620px; }
-.emoji-1F513 { background-position: 0 -640px; }
-.emoji-1F514 { background-position: -20px -640px; }
-.emoji-1F515 { background-position: -40px -640px; }
-.emoji-1F516 { background-position: -60px -640px; }
-.emoji-1F517 { background-position: -80px -640px; }
-.emoji-1F518 { background-position: -100px -640px; }
-.emoji-1F519 { background-position: -120px -640px; }
-.emoji-1F51A { background-position: -140px -640px; }
-.emoji-1F51B { background-position: -160px -640px; }
-.emoji-1F51C { background-position: -180px -640px; }
-.emoji-1F51D { background-position: -200px -640px; }
-.emoji-1F51E { background-position: -220px -640px; }
-.emoji-1F51F { background-position: -240px -640px; }
-.emoji-1F520 { background-position: -260px -640px; }
-.emoji-1F521 { background-position: -280px -640px; }
-.emoji-1F522 { background-position: -300px -640px; }
-.emoji-1F523 { background-position: -320px -640px; }
-.emoji-1F524 { background-position: -340px -640px; }
-.emoji-1F525 { background-position: -360px -640px; }
-.emoji-1F526 { background-position: -380px -640px; }
-.emoji-1F527 { background-position: -400px -640px; }
-.emoji-1F528 { background-position: -420px -640px; }
-.emoji-1F529 { background-position: -440px -640px; }
-.emoji-1F52A { background-position: -460px -640px; }
-.emoji-1F52B { background-position: -480px -640px; }
-.emoji-1F52C { background-position: -500px -640px; }
-.emoji-1F52D { background-position: -520px -640px; }
-.emoji-1F52E { background-position: -540px -640px; }
-.emoji-1F52F { background-position: -560px -640px; }
-.emoji-1F530 { background-position: -580px -640px; }
-.emoji-1F531 { background-position: -600px -640px; }
-.emoji-1F532 { background-position: -620px -640px; }
-.emoji-1F533 { background-position: -640px -640px; }
-.emoji-1F534 { background-position: -660px 0; }
-.emoji-1F535 { background-position: -660px -20px; }
-.emoji-1F536 { background-position: -660px -40px; }
-.emoji-1F537 { background-position: -660px -60px; }
-.emoji-1F538 { background-position: -660px -80px; }
-.emoji-1F539 { background-position: -660px -100px; }
-.emoji-1F53A { background-position: -660px -120px; }
-.emoji-1F53B { background-position: -660px -140px; }
-.emoji-1F53C { background-position: -660px -160px; }
-.emoji-1F53D { background-position: -660px -180px; }
-.emoji-1F546 { background-position: -660px -200px; }
-.emoji-1F547 { background-position: -660px -220px; }
-.emoji-1F548 { background-position: -660px -240px; }
-.emoji-1F549 { background-position: -660px -260px; }
-.emoji-1F54A { background-position: -660px -280px; }
-.emoji-1F54B { background-position: -660px -300px; }
-.emoji-1F54C { background-position: -660px -320px; }
-.emoji-1F54D { background-position: -660px -340px; }
-.emoji-1F54E { background-position: -660px -360px; }
-.emoji-1F550 { background-position: -660px -380px; }
-.emoji-1F551 { background-position: -660px -400px; }
-.emoji-1F552 { background-position: -660px -420px; }
-.emoji-1F553 { background-position: -660px -440px; }
-.emoji-1F554 { background-position: -660px -460px; }
-.emoji-1F555 { background-position: -660px -480px; }
-.emoji-1F556 { background-position: -660px -500px; }
-.emoji-1F557 { background-position: -660px -520px; }
-.emoji-1F558 { background-position: -660px -540px; }
-.emoji-1F559 { background-position: -660px -560px; }
-.emoji-1F55A { background-position: -660px -580px; }
-.emoji-1F55B { background-position: -660px -600px; }
-.emoji-1F55C { background-position: -660px -620px; }
-.emoji-1F55D { background-position: -660px -640px; }
-.emoji-1F55E { background-position: 0 -660px; }
-.emoji-1F55F { background-position: -20px -660px; }
-.emoji-1F560 { background-position: -40px -660px; }
-.emoji-1F561 { background-position: -60px -660px; }
-.emoji-1F562 { background-position: -80px -660px; }
-.emoji-1F563 { background-position: -100px -660px; }
-.emoji-1F564 { background-position: -120px -660px; }
-.emoji-1F565 { background-position: -140px -660px; }
-.emoji-1F566 { background-position: -160px -660px; }
-.emoji-1F567 { background-position: -180px -660px; }
-.emoji-1F568 { background-position: -200px -660px; }
-.emoji-1F569 { background-position: -220px -660px; }
-.emoji-1F56A { background-position: -240px -660px; }
-.emoji-1F56B { background-position: -260px -660px; }
-.emoji-1F56C { background-position: -280px -660px; }
-.emoji-1F56D { background-position: -300px -660px; }
-.emoji-1F56E { background-position: -320px -660px; }
-.emoji-1F56F { background-position: -340px -660px; }
-.emoji-1F570 { background-position: -360px -660px; }
-.emoji-1F571 { background-position: -380px -660px; }
-.emoji-1F572 { background-position: -400px -660px; }
-.emoji-1F573 { background-position: -420px -660px; }
-.emoji-1F574 { background-position: -440px -660px; }
-.emoji-1F575 { background-position: -460px -660px; }
-.emoji-1F575-1F3FB { background-position: -480px -660px; }
-.emoji-1F575-1F3FC { background-position: -500px -660px; }
-.emoji-1F575-1F3FD { background-position: -520px -660px; }
-.emoji-1F575-1F3FE { background-position: -540px -660px; }
-.emoji-1F575-1F3FF { background-position: -560px -660px; }
-.emoji-1F576 { background-position: -580px -660px; }
-.emoji-1F577 { background-position: -600px -660px; }
-.emoji-1F578 { background-position: -620px -660px; }
-.emoji-1F579 { background-position: -640px -660px; }
-.emoji-1F57B { background-position: -660px -660px; }
-.emoji-1F57E { background-position: -680px 0; }
-.emoji-1F57F { background-position: -680px -20px; }
-.emoji-1F581 { background-position: -680px -40px; }
-.emoji-1F582 { background-position: -680px -60px; }
-.emoji-1F583 { background-position: -680px -80px; }
-.emoji-1F585 { background-position: -680px -100px; }
-.emoji-1F586 { background-position: -680px -120px; }
-.emoji-1F587 { background-position: -680px -140px; }
-.emoji-1F588 { background-position: -680px -160px; }
-.emoji-1F589 { background-position: -680px -180px; }
-.emoji-1F58A { background-position: -680px -200px; }
-.emoji-1F58B { background-position: -680px -220px; }
-.emoji-1F58C { background-position: -680px -240px; }
-.emoji-1F58D { background-position: -680px -260px; }
-.emoji-1F58E { background-position: -680px -280px; }
-.emoji-1F58F { background-position: -680px -300px; }
-.emoji-1F590 { background-position: -680px -320px; }
-.emoji-1F590-1F3FB { background-position: -680px -340px; }
-.emoji-1F590-1F3FC { background-position: -680px -360px; }
-.emoji-1F590-1F3FD { background-position: -680px -380px; }
-.emoji-1F590-1F3FE { background-position: -680px -400px; }
-.emoji-1F590-1F3FF { background-position: -680px -420px; }
-.emoji-1F591 { background-position: -680px -440px; }
-.emoji-1F592 { background-position: -680px -460px; }
-.emoji-1F593 { background-position: -680px -480px; }
-.emoji-1F594 { background-position: -680px -500px; }
-.emoji-1F595 { background-position: -680px -520px; }
-.emoji-1F595-1F3FB { background-position: -680px -540px; }
-.emoji-1F595-1F3FC { background-position: -680px -560px; }
-.emoji-1F595-1F3FD { background-position: -680px -580px; }
-.emoji-1F595-1F3FE { background-position: -680px -600px; }
-.emoji-1F595-1F3FF { background-position: -680px -620px; }
-.emoji-1F596 { background-position: -680px -640px; }
-.emoji-1F596-1F3FB { background-position: -680px -660px; }
-.emoji-1F596-1F3FC { background-position: 0 -680px; }
-.emoji-1F596-1F3FD { background-position: -20px -680px; }
-.emoji-1F596-1F3FE { background-position: -40px -680px; }
-.emoji-1F596-1F3FF { background-position: -60px -680px; }
-.emoji-1F597 { background-position: -80px -680px; }
-.emoji-1F598 { background-position: -100px -680px; }
-.emoji-1F599 { background-position: -120px -680px; }
-.emoji-1F59E { background-position: -140px -680px; }
-.emoji-1F59F { background-position: -160px -680px; }
-.emoji-1F5A5 { background-position: -180px -680px; }
-.emoji-1F5A6 { background-position: -200px -680px; }
-.emoji-1F5A7 { background-position: -220px -680px; }
-.emoji-1F5A8 { background-position: -240px -680px; }
-.emoji-1F5A9 { background-position: -260px -680px; }
-.emoji-1F5AA { background-position: -280px -680px; }
-.emoji-1F5AB { background-position: -300px -680px; }
-.emoji-1F5AD { background-position: -320px -680px; }
-.emoji-1F5AE { background-position: -340px -680px; }
-.emoji-1F5AF { background-position: -360px -680px; }
-.emoji-1F5B1 { background-position: -380px -680px; }
-.emoji-1F5B2 { background-position: -400px -680px; }
-.emoji-1F5B3 { background-position: -420px -680px; }
-.emoji-1F5B4 { background-position: -440px -680px; }
-.emoji-1F5B8 { background-position: -460px -680px; }
-.emoji-1F5B9 { background-position: -480px -680px; }
-.emoji-1F5BC { background-position: -500px -680px; }
-.emoji-1F5BD { background-position: -520px -680px; }
-.emoji-1F5BE { background-position: -540px -680px; }
-.emoji-1F5C0 { background-position: -560px -680px; }
-.emoji-1F5C1 { background-position: -580px -680px; }
-.emoji-1F5C2 { background-position: -600px -680px; }
-.emoji-1F5C3 { background-position: -620px -680px; }
-.emoji-1F5C4 { background-position: -640px -680px; }
-.emoji-1F5C6 { background-position: -660px -680px; }
-.emoji-1F5C7 { background-position: -680px -680px; }
-.emoji-1F5C9 { background-position: -700px 0; }
-.emoji-1F5CA { background-position: -700px -20px; }
-.emoji-1F5CE { background-position: -700px -40px; }
-.emoji-1F5CF { background-position: -700px -60px; }
-.emoji-1F5D0 { background-position: -700px -80px; }
-.emoji-1F5D1 { background-position: -700px -100px; }
-.emoji-1F5D2 { background-position: -700px -120px; }
-.emoji-1F5D3 { background-position: -700px -140px; }
-.emoji-1F5D4 { background-position: -700px -160px; }
-.emoji-1F5D8 { background-position: -700px -180px; }
-.emoji-1F5D9 { background-position: -700px -200px; }
-.emoji-1F5DC { background-position: -700px -220px; }
-.emoji-1F5DD { background-position: -700px -240px; }
-.emoji-1F5DE { background-position: -700px -260px; }
-.emoji-1F5E0 { background-position: -700px -280px; }
-.emoji-1F5E1 { background-position: -700px -300px; }
-.emoji-1F5E2 { background-position: -700px -320px; }
-.emoji-1F5E3 { background-position: -700px -340px; }
-.emoji-1F5E8 { background-position: -700px -360px; }
-.emoji-1F5E9 { background-position: -700px -380px; }
-.emoji-1F5EA { background-position: -700px -400px; }
-.emoji-1F5EB { background-position: -700px -420px; }
-.emoji-1F5EC { background-position: -700px -440px; }
-.emoji-1F5ED { background-position: -700px -460px; }
-.emoji-1F5EE { background-position: -700px -480px; }
-.emoji-1F5EF { background-position: -700px -500px; }
-.emoji-1F5F0 { background-position: -700px -520px; }
-.emoji-1F5F1 { background-position: -700px -540px; }
-.emoji-1F5F2 { background-position: -700px -560px; }
-.emoji-1F5F3 { background-position: -700px -580px; }
-.emoji-1F5F4 { background-position: -700px -600px; }
-.emoji-1F5F5 { background-position: -700px -620px; }
-.emoji-1F5F8 { background-position: -700px -640px; }
-.emoji-1F5F9 { background-position: -700px -660px; }
-.emoji-1F5FA { background-position: -700px -680px; }
-.emoji-1F5FB { background-position: 0 -700px; }
-.emoji-1F5FC { background-position: -20px -700px; }
-.emoji-1F5FD { background-position: -40px -700px; }
-.emoji-1F5FE { background-position: -60px -700px; }
-.emoji-1F5FF { background-position: -80px -700px; }
-.emoji-1F600 { background-position: -100px -700px; }
-.emoji-1F601 { background-position: -120px -700px; }
-.emoji-1F602 { background-position: -140px -700px; }
-.emoji-1F603 { background-position: -160px -700px; }
-.emoji-1F604 { background-position: -180px -700px; }
-.emoji-1F605 { background-position: -200px -700px; }
-.emoji-1F606 { background-position: -220px -700px; }
-.emoji-1F607 { background-position: -240px -700px; }
-.emoji-1F608 { background-position: -260px -700px; }
-.emoji-1F609 { background-position: -280px -700px; }
-.emoji-1F60A { background-position: -300px -700px; }
-.emoji-1F60B { background-position: -320px -700px; }
-.emoji-1F60C { background-position: -340px -700px; }
-.emoji-1F60D { background-position: -360px -700px; }
-.emoji-1F60E { background-position: -380px -700px; }
-.emoji-1F60F { background-position: -400px -700px; }
-.emoji-1F610 { background-position: -420px -700px; }
-.emoji-1F611 { background-position: -440px -700px; }
-.emoji-1F612 { background-position: -460px -700px; }
-.emoji-1F613 { background-position: -480px -700px; }
-.emoji-1F614 { background-position: -500px -700px; }
-.emoji-1F615 { background-position: -520px -700px; }
-.emoji-1F616 { background-position: -540px -700px; }
-.emoji-1F617 { background-position: -560px -700px; }
-.emoji-1F618 { background-position: -580px -700px; }
-.emoji-1F619 { background-position: -600px -700px; }
-.emoji-1F61A { background-position: -620px -700px; }
-.emoji-1F61B { background-position: -640px -700px; }
-.emoji-1F61C { background-position: -660px -700px; }
-.emoji-1F61D { background-position: -680px -700px; }
-.emoji-1F61E { background-position: -700px -700px; }
-.emoji-1F61F { background-position: -720px 0; }
-.emoji-1F620 { background-position: -720px -20px; }
-.emoji-1F621 { background-position: -720px -40px; }
-.emoji-1F622 { background-position: -720px -60px; }
-.emoji-1F623 { background-position: -720px -80px; }
-.emoji-1F624 { background-position: -720px -100px; }
-.emoji-1F625 { background-position: -720px -120px; }
-.emoji-1F626 { background-position: -720px -140px; }
-.emoji-1F627 { background-position: -720px -160px; }
-.emoji-1F628 { background-position: -720px -180px; }
-.emoji-1F629 { background-position: -720px -200px; }
-.emoji-1F62A { background-position: -720px -220px; }
-.emoji-1F62B { background-position: -720px -240px; }
-.emoji-1F62C { background-position: -720px -260px; }
-.emoji-1F62D { background-position: -720px -280px; }
-.emoji-1F62E { background-position: -720px -300px; }
-.emoji-1F62F { background-position: -720px -320px; }
-.emoji-1F630 { background-position: -720px -340px; }
-.emoji-1F631 { background-position: -720px -360px; }
-.emoji-1F632 { background-position: -720px -380px; }
-.emoji-1F633 { background-position: -720px -400px; }
-.emoji-1F634 { background-position: -720px -420px; }
-.emoji-1F635 { background-position: -720px -440px; }
-.emoji-1F636 { background-position: -720px -460px; }
-.emoji-1F637 { background-position: -720px -480px; }
-.emoji-1F638 { background-position: -720px -500px; }
-.emoji-1F639 { background-position: -720px -520px; }
-.emoji-1F63A { background-position: -720px -540px; }
-.emoji-1F63B { background-position: -720px -560px; }
-.emoji-1F63C { background-position: -720px -580px; }
-.emoji-1F63D { background-position: -720px -600px; }
-.emoji-1F63E { background-position: -720px -620px; }
-.emoji-1F63F { background-position: -720px -640px; }
-.emoji-1F640 { background-position: -720px -660px; }
-.emoji-1F641 { background-position: -720px -680px; }
-.emoji-1F642 { background-position: -720px -700px; }
-.emoji-1F643 { background-position: 0 -720px; }
-.emoji-1F644 { background-position: -20px -720px; }
-.emoji-1F645 { background-position: -40px -720px; }
-.emoji-1F645-1F3FB { background-position: -60px -720px; }
-.emoji-1F645-1F3FC { background-position: -80px -720px; }
-.emoji-1F645-1F3FD { background-position: -100px -720px; }
-.emoji-1F645-1F3FE { background-position: -120px -720px; }
-.emoji-1F645-1F3FF { background-position: -140px -720px; }
-.emoji-1F646 { background-position: -160px -720px; }
-.emoji-1F646-1F3FB { background-position: -180px -720px; }
-.emoji-1F646-1F3FC { background-position: -200px -720px; }
-.emoji-1F646-1F3FD { background-position: -220px -720px; }
-.emoji-1F646-1F3FE { background-position: -240px -720px; }
-.emoji-1F646-1F3FF { background-position: -260px -720px; }
-.emoji-1F647 { background-position: -280px -720px; }
-.emoji-1F647-1F3FB { background-position: -300px -720px; }
-.emoji-1F647-1F3FC { background-position: -320px -720px; }
-.emoji-1F647-1F3FD { background-position: -340px -720px; }
-.emoji-1F647-1F3FE { background-position: -360px -720px; }
-.emoji-1F647-1F3FF { background-position: -380px -720px; }
-.emoji-1F648 { background-position: -400px -720px; }
-.emoji-1F649 { background-position: -420px -720px; }
-.emoji-1F64A { background-position: -440px -720px; }
-.emoji-1F64B { background-position: -460px -720px; }
-.emoji-1F64B-1F3FB { background-position: -480px -720px; }
-.emoji-1F64B-1F3FC { background-position: -500px -720px; }
-.emoji-1F64B-1F3FD { background-position: -520px -720px; }
-.emoji-1F64B-1F3FE { background-position: -540px -720px; }
-.emoji-1F64B-1F3FF { background-position: -560px -720px; }
-.emoji-1F64C { background-position: -580px -720px; }
-.emoji-1F64C-1F3FB { background-position: -600px -720px; }
-.emoji-1F64C-1F3FC { background-position: -620px -720px; }
-.emoji-1F64C-1F3FD { background-position: -640px -720px; }
-.emoji-1F64C-1F3FE { background-position: -660px -720px; }
-.emoji-1F64C-1F3FF { background-position: -680px -720px; }
-.emoji-1F64D { background-position: -700px -720px; }
-.emoji-1F64D-1F3FB { background-position: -720px -720px; }
-.emoji-1F64D-1F3FC { background-position: -740px 0; }
-.emoji-1F64D-1F3FD { background-position: -740px -20px; }
-.emoji-1F64D-1F3FE { background-position: -740px -40px; }
-.emoji-1F64D-1F3FF { background-position: -740px -60px; }
-.emoji-1F64E { background-position: -740px -80px; }
-.emoji-1F64E-1F3FB { background-position: -740px -100px; }
-.emoji-1F64E-1F3FC { background-position: -740px -120px; }
-.emoji-1F64E-1F3FD { background-position: -740px -140px; }
-.emoji-1F64E-1F3FE { background-position: -740px -160px; }
-.emoji-1F64E-1F3FF { background-position: -740px -180px; }
-.emoji-1F64F { background-position: -740px -200px; }
-.emoji-1F64F-1F3FB { background-position: -740px -220px; }
-.emoji-1F64F-1F3FC { background-position: -740px -240px; }
-.emoji-1F64F-1F3FD { background-position: -740px -260px; }
-.emoji-1F64F-1F3FE { background-position: -740px -280px; }
-.emoji-1F64F-1F3FF { background-position: -740px -300px; }
-.emoji-1F680 { background-position: -740px -320px; }
-.emoji-1F681 { background-position: -740px -340px; }
-.emoji-1F682 { background-position: -740px -360px; }
-.emoji-1F683 { background-position: -740px -380px; }
-.emoji-1F684 { background-position: -740px -400px; }
-.emoji-1F685 { background-position: -740px -420px; }
-.emoji-1F686 { background-position: -740px -440px; }
-.emoji-1F687 { background-position: -740px -460px; }
-.emoji-1F688 { background-position: -740px -480px; }
-.emoji-1F689 { background-position: -740px -500px; }
-.emoji-1F68A { background-position: -740px -520px; }
-.emoji-1F68B { background-position: -740px -540px; }
-.emoji-1F68C { background-position: -740px -560px; }
-.emoji-1F68D { background-position: -740px -580px; }
-.emoji-1F68E { background-position: -740px -600px; }
-.emoji-1F68F { background-position: -740px -620px; }
-.emoji-1F690 { background-position: -740px -640px; }
-.emoji-1F691 { background-position: -740px -660px; }
-.emoji-1F692 { background-position: -740px -680px; }
-.emoji-1F693 { background-position: -740px -700px; }
-.emoji-1F694 { background-position: -740px -720px; }
-.emoji-1F695 { background-position: 0 -740px; }
-.emoji-1F696 { background-position: -20px -740px; }
-.emoji-1F697 { background-position: -40px -740px; }
-.emoji-1F698 { background-position: -60px -740px; }
-.emoji-1F699 { background-position: -80px -740px; }
-.emoji-1F69A { background-position: -100px -740px; }
-.emoji-1F69B { background-position: -120px -740px; }
-.emoji-1F69C { background-position: -140px -740px; }
-.emoji-1F69D { background-position: -160px -740px; }
-.emoji-1F69E { background-position: -180px -740px; }
-.emoji-1F69F { background-position: -200px -740px; }
-.emoji-1F6A0 { background-position: -220px -740px; }
-.emoji-1F6A1 { background-position: -240px -740px; }
-.emoji-1F6A2 { background-position: -260px -740px; }
-.emoji-1F6A3 { background-position: -280px -740px; }
-.emoji-1F6A3-1F3FB { background-position: -300px -740px; }
-.emoji-1F6A3-1F3FC { background-position: -320px -740px; }
-.emoji-1F6A3-1F3FD { background-position: -340px -740px; }
-.emoji-1F6A3-1F3FE { background-position: -360px -740px; }
-.emoji-1F6A3-1F3FF { background-position: -380px -740px; }
-.emoji-1F6A4 { background-position: -400px -740px; }
-.emoji-1F6A5 { background-position: -420px -740px; }
-.emoji-1F6A6 { background-position: -440px -740px; }
-.emoji-1F6A7 { background-position: -460px -740px; }
-.emoji-1F6A8 { background-position: -480px -740px; }
-.emoji-1F6A9 { background-position: -500px -740px; }
-.emoji-1F6AA { background-position: -520px -740px; }
-.emoji-1F6AB { background-position: -540px -740px; }
-.emoji-1F6AC { background-position: -560px -740px; }
-.emoji-1F6AD { background-position: -580px -740px; }
-.emoji-1F6AE { background-position: -600px -740px; }
-.emoji-1F6AF { background-position: -620px -740px; }
-.emoji-1F6B0 { background-position: -640px -740px; }
-.emoji-1F6B1 { background-position: -660px -740px; }
-.emoji-1F6B2 { background-position: -680px -740px; }
-.emoji-1F6B3 { background-position: -700px -740px; }
-.emoji-1F6B4 { background-position: -720px -740px; }
-.emoji-1F6B4-1F3FB { background-position: -740px -740px; }
-.emoji-1F6B4-1F3FC { background-position: -760px 0; }
-.emoji-1F6B4-1F3FD { background-position: -760px -20px; }
-.emoji-1F6B4-1F3FE { background-position: -760px -40px; }
-.emoji-1F6B4-1F3FF { background-position: -760px -60px; }
-.emoji-1F6B5 { background-position: -760px -80px; }
-.emoji-1F6B5-1F3FB { background-position: -760px -100px; }
-.emoji-1F6B5-1F3FC { background-position: -760px -120px; }
-.emoji-1F6B5-1F3FD { background-position: -760px -140px; }
-.emoji-1F6B5-1F3FE { background-position: -760px -160px; }
-.emoji-1F6B5-1F3FF { background-position: -760px -180px; }
-.emoji-1F6B6 { background-position: -760px -200px; }
-.emoji-1F6B6-1F3FB { background-position: -760px -220px; }
-.emoji-1F6B6-1F3FC { background-position: -760px -240px; }
-.emoji-1F6B6-1F3FD { background-position: -760px -260px; }
-.emoji-1F6B6-1F3FE { background-position: -760px -280px; }
-.emoji-1F6B6-1F3FF { background-position: -760px -300px; }
-.emoji-1F6B7 { background-position: -760px -320px; }
-.emoji-1F6B8 { background-position: -760px -340px; }
-.emoji-1F6B9 { background-position: -760px -360px; }
-.emoji-1F6BA { background-position: -760px -380px; }
-.emoji-1F6BB { background-position: -760px -400px; }
-.emoji-1F6BC { background-position: -760px -420px; }
-.emoji-1F6BD { background-position: -760px -440px; }
-.emoji-1F6BE { background-position: -760px -460px; }
-.emoji-1F6BF { background-position: -760px -480px; }
-.emoji-1F6C0 { background-position: -760px -500px; }
-.emoji-1F6C0-1F3FB { background-position: -760px -520px; }
-.emoji-1F6C0-1F3FC { background-position: -760px -540px; }
-.emoji-1F6C0-1F3FD { background-position: -760px -560px; }
-.emoji-1F6C0-1F3FE { background-position: -760px -580px; }
-.emoji-1F6C0-1F3FF { background-position: -760px -600px; }
-.emoji-1F6C1 { background-position: -760px -620px; }
-.emoji-1F6C2 { background-position: -760px -640px; }
-.emoji-1F6C3 { background-position: -760px -660px; }
-.emoji-1F6C4 { background-position: -760px -680px; }
-.emoji-1F6C5 { background-position: -760px -700px; }
-.emoji-1F6C6 { background-position: -760px -720px; }
-.emoji-1F6C7 { background-position: -760px -740px; }
-.emoji-1F6C8 { background-position: 0 -760px; }
-.emoji-1F6C9 { background-position: -20px -760px; }
-.emoji-1F6CA { background-position: -40px -760px; }
-.emoji-1F6CB { background-position: -60px -760px; }
-.emoji-1F6CC { background-position: -80px -760px; }
-.emoji-1F6CD { background-position: -100px -760px; }
-.emoji-1F6CE { background-position: -120px -760px; }
-.emoji-1F6CF { background-position: -140px -760px; }
-.emoji-1F6D0 { background-position: -160px -760px; }
-.emoji-1F6E0 { background-position: -180px -760px; }
-.emoji-1F6E1 { background-position: -200px -760px; }
-.emoji-1F6E2 { background-position: -220px -760px; }
-.emoji-1F6E3 { background-position: -240px -760px; }
-.emoji-1F6E4 { background-position: -260px -760px; }
-.emoji-1F6E5 { background-position: -280px -760px; }
-.emoji-1F6E6 { background-position: -300px -760px; }
-.emoji-1F6E7 { background-position: -320px -760px; }
-.emoji-1F6E8 { background-position: -340px -760px; }
-.emoji-1F6E9 { background-position: -360px -760px; }
-.emoji-1F6EA { background-position: -380px -760px; }
-.emoji-1F6EB { background-position: -400px -760px; }
-.emoji-1F6EC { background-position: -420px -760px; }
-.emoji-1F6F0 { background-position: -440px -760px; }
-.emoji-1F6F1 { background-position: -460px -760px; }
-.emoji-1F6F2 { background-position: -480px -760px; }
-.emoji-1F6F3 { background-position: -500px -760px; }
-.emoji-1F910 { background-position: -520px -760px; }
-.emoji-1F911 { background-position: -540px -760px; }
-.emoji-1F912 { background-position: -560px -760px; }
-.emoji-1F913 { background-position: -580px -760px; }
-.emoji-1F914 { background-position: -600px -760px; }
-.emoji-1F915 { background-position: -620px -760px; }
-.emoji-1F916 { background-position: -640px -760px; }
-.emoji-1F917 { background-position: -660px -760px; }
-.emoji-1F918 { background-position: -680px -760px; }
-.emoji-1F918-1F3FB { background-position: -700px -760px; }
-.emoji-1F918-1F3FC { background-position: -720px -760px; }
-.emoji-1F918-1F3FD { background-position: -740px -760px; }
-.emoji-1F918-1F3FE { background-position: -760px -760px; }
-.emoji-1F918-1F3FF { background-position: -780px 0; }
-.emoji-1F980 { background-position: -780px -20px; }
-.emoji-1F981 { background-position: -780px -40px; }
-.emoji-1F982 { background-position: -780px -60px; }
-.emoji-1F983 { background-position: -780px -80px; }
-.emoji-1F984 { background-position: -780px -100px; }
-.emoji-1F9C0 { background-position: -780px -120px; }
-.emoji-203C { background-position: -780px -140px; }
-.emoji-2049 { background-position: -780px -160px; }
-.emoji-2122 { background-position: -780px -180px; }
-.emoji-2139 { background-position: -780px -200px; }
-.emoji-2194 { background-position: -780px -220px; }
-.emoji-2195 { background-position: -780px -240px; }
-.emoji-2196 { background-position: -780px -260px; }
-.emoji-2197 { background-position: -780px -280px; }
-.emoji-2198 { background-position: -780px -300px; }
-.emoji-2199 { background-position: -780px -320px; }
-.emoji-21A9 { background-position: -780px -340px; }
-.emoji-21AA { background-position: -780px -360px; }
-.emoji-231A { background-position: -780px -380px; }
-.emoji-231B { background-position: -780px -400px; }
-.emoji-2328 { background-position: -780px -420px; }
-.emoji-23E9 { background-position: -780px -440px; }
-.emoji-23EA { background-position: -780px -460px; }
-.emoji-23EB { background-position: -780px -480px; }
-.emoji-23EC { background-position: -780px -500px; }
-.emoji-23ED { background-position: -780px -520px; }
-.emoji-23EE { background-position: -780px -540px; }
-.emoji-23EF { background-position: -780px -560px; }
-.emoji-23F0 { background-position: -780px -580px; }
-.emoji-23F1 { background-position: -780px -600px; }
-.emoji-23F2 { background-position: -780px -620px; }
-.emoji-23F3 { background-position: -780px -640px; }
-.emoji-23F8 { background-position: -780px -660px; }
-.emoji-23F9 { background-position: -780px -680px; }
-.emoji-23FA { background-position: -780px -700px; }
-.emoji-24C2 { background-position: -780px -720px; }
-.emoji-25AA { background-position: -780px -740px; }
-.emoji-25AB { background-position: -780px -760px; }
-.emoji-25B6 { background-position: 0 -780px; }
-.emoji-25C0 { background-position: -20px -780px; }
-.emoji-25FB { background-position: -40px -780px; }
-.emoji-25FC { background-position: -60px -780px; }
-.emoji-25FD { background-position: -80px -780px; }
-.emoji-25FE { background-position: -100px -780px; }
-.emoji-2600 { background-position: -120px -780px; }
-.emoji-2601 { background-position: -140px -780px; }
-.emoji-2602 { background-position: -160px -780px; }
-.emoji-2603 { background-position: -180px -780px; }
-.emoji-2604 { background-position: -200px -780px; }
-.emoji-260E { background-position: -220px -780px; }
-.emoji-2611 { background-position: -240px -780px; }
-.emoji-2614 { background-position: -260px -780px; }
-.emoji-2615 { background-position: -280px -780px; }
-.emoji-2618 { background-position: -300px -780px; }
-.emoji-261D { background-position: -320px -780px; }
-.emoji-261D-1F3FB { background-position: -340px -780px; }
-.emoji-261D-1F3FC { background-position: -360px -780px; }
-.emoji-261D-1F3FD { background-position: -380px -780px; }
-.emoji-261D-1F3FE { background-position: -400px -780px; }
-.emoji-261D-1F3FF { background-position: -420px -780px; }
-.emoji-2620 { background-position: -440px -780px; }
-.emoji-2622 { background-position: -460px -780px; }
-.emoji-2623 { background-position: -480px -780px; }
-.emoji-2626 { background-position: -500px -780px; }
-.emoji-262A { background-position: -520px -780px; }
-.emoji-262E { background-position: -540px -780px; }
-.emoji-262F { background-position: -560px -780px; }
-.emoji-2638 { background-position: -580px -780px; }
-.emoji-2639 { background-position: -600px -780px; }
-.emoji-263A { background-position: -620px -780px; }
-.emoji-2648 { background-position: -640px -780px; }
-.emoji-2649 { background-position: -660px -780px; }
-.emoji-264A { background-position: -680px -780px; }
-.emoji-264B { background-position: -700px -780px; }
-.emoji-264C { background-position: -720px -780px; }
-.emoji-264D { background-position: -740px -780px; }
-.emoji-264E { background-position: -760px -780px; }
-.emoji-264F { background-position: -780px -780px; }
-.emoji-2650 { background-position: -800px 0; }
-.emoji-2651 { background-position: -800px -20px; }
-.emoji-2652 { background-position: -800px -40px; }
-.emoji-2653 { background-position: -800px -60px; }
-.emoji-2660 { background-position: -800px -80px; }
-.emoji-2663 { background-position: -800px -100px; }
-.emoji-2665 { background-position: -800px -120px; }
-.emoji-2666 { background-position: -800px -140px; }
-.emoji-2668 { background-position: -800px -160px; }
-.emoji-267B { background-position: -800px -180px; }
-.emoji-267F { background-position: -800px -200px; }
-.emoji-2692 { background-position: -800px -220px; }
-.emoji-2693 { background-position: -800px -240px; }
-.emoji-2694 { background-position: -800px -260px; }
-.emoji-2696 { background-position: -800px -280px; }
-.emoji-2697 { background-position: -800px -300px; }
-.emoji-2699 { background-position: -800px -320px; }
-.emoji-269B { background-position: -800px -340px; }
-.emoji-269C { background-position: -800px -360px; }
-.emoji-26A0 { background-position: -800px -380px; }
-.emoji-26A1 { background-position: -800px -400px; }
-.emoji-26AA { background-position: -800px -420px; }
-.emoji-26AB { background-position: -800px -440px; }
-.emoji-26B0 { background-position: -800px -460px; }
-.emoji-26B1 { background-position: -800px -480px; }
-.emoji-26BD { background-position: -800px -500px; }
-.emoji-26BE { background-position: -800px -520px; }
-.emoji-26C4 { background-position: -800px -540px; }
-.emoji-26C5 { background-position: -800px -560px; }
-.emoji-26C8 { background-position: -800px -580px; }
-.emoji-26CE { background-position: -800px -600px; }
-.emoji-26CF { background-position: -800px -620px; }
-.emoji-26D1 { background-position: -800px -640px; }
-.emoji-26D3 { background-position: -800px -660px; }
-.emoji-26D4 { background-position: -800px -680px; }
-.emoji-26E9 { background-position: -800px -700px; }
-.emoji-26EA { background-position: -800px -720px; }
-.emoji-26F0 { background-position: -800px -740px; }
-.emoji-26F1 { background-position: -800px -760px; }
-.emoji-26F2 { background-position: -800px -780px; }
-.emoji-26F3 { background-position: 0 -800px; }
-.emoji-26F4 { background-position: -20px -800px; }
-.emoji-26F5 { background-position: -40px -800px; }
-.emoji-26F7 { background-position: -60px -800px; }
-.emoji-26F8 { background-position: -80px -800px; }
-.emoji-26F9 { background-position: -100px -800px; }
-.emoji-26F9-1F3FB { background-position: -120px -800px; }
-.emoji-26F9-1F3FC { background-position: -140px -800px; }
-.emoji-26F9-1F3FD { background-position: -160px -800px; }
-.emoji-26F9-1F3FE { background-position: -180px -800px; }
-.emoji-26F9-1F3FF { background-position: -200px -800px; }
-.emoji-26FA { background-position: -220px -800px; }
-.emoji-26FD { background-position: -240px -800px; }
-.emoji-2702 { background-position: -260px -800px; }
-.emoji-2705 { background-position: -280px -800px; }
-.emoji-2708 { background-position: -300px -800px; }
-.emoji-2709 { background-position: -320px -800px; }
-.emoji-270A { background-position: -340px -800px; }
-.emoji-270A-1F3FB { background-position: -360px -800px; }
-.emoji-270A-1F3FC { background-position: -380px -800px; }
-.emoji-270A-1F3FD { background-position: -400px -800px; }
-.emoji-270A-1F3FE { background-position: -420px -800px; }
-.emoji-270A-1F3FF { background-position: -440px -800px; }
-.emoji-270B { background-position: -460px -800px; }
-.emoji-270B-1F3FB { background-position: -480px -800px; }
-.emoji-270B-1F3FC { background-position: -500px -800px; }
-.emoji-270B-1F3FD { background-position: -520px -800px; }
-.emoji-270B-1F3FE { background-position: -540px -800px; }
-.emoji-270B-1F3FF { background-position: -560px -800px; }
-.emoji-270C { background-position: -580px -800px; }
-.emoji-270C-1F3FB { background-position: -600px -800px; }
-.emoji-270C-1F3FC { background-position: -620px -800px; }
-.emoji-270C-1F3FD { background-position: -640px -800px; }
-.emoji-270C-1F3FE { background-position: -660px -800px; }
-.emoji-270C-1F3FF { background-position: -680px -800px; }
-.emoji-270D { background-position: -700px -800px; }
-.emoji-270D-1F3FB { background-position: -720px -800px; }
-.emoji-270D-1F3FC { background-position: -740px -800px; }
-.emoji-270D-1F3FD { background-position: -760px -800px; }
-.emoji-270D-1F3FE { background-position: -780px -800px; }
-.emoji-270D-1F3FF { background-position: -800px -800px; }
-.emoji-270F { background-position: -820px 0; }
-.emoji-2712 { background-position: -820px -20px; }
-.emoji-2714 { background-position: -820px -40px; }
-.emoji-2716 { background-position: -820px -60px; }
-.emoji-271D { background-position: -820px -80px; }
-.emoji-2721 { background-position: -820px -100px; }
-.emoji-2728 { background-position: -820px -120px; }
-.emoji-2733 { background-position: -820px -140px; }
-.emoji-2734 { background-position: -820px -160px; }
-.emoji-2744 { background-position: -820px -180px; }
-.emoji-2747 { background-position: -820px -200px; }
-.emoji-274C { background-position: -820px -220px; }
-.emoji-274E { background-position: -820px -240px; }
-.emoji-2753 { background-position: -820px -260px; }
-.emoji-2754 { background-position: -820px -280px; }
-.emoji-2755 { background-position: -820px -300px; }
-.emoji-2757 { background-position: -820px -320px; }
-.emoji-2763 { background-position: -820px -340px; }
-.emoji-2764 { background-position: -820px -360px; }
-.emoji-2795 { background-position: -820px -380px; }
-.emoji-2796 { background-position: -820px -400px; }
-.emoji-2797 { background-position: -820px -420px; }
-.emoji-27A1 { background-position: -820px -440px; }
-.emoji-27B0 { background-position: -820px -460px; }
-.emoji-27BF { background-position: -820px -480px; }
-.emoji-2934 { background-position: -820px -500px; }
-.emoji-2935 { background-position: -820px -520px; }
-.emoji-2B05 { background-position: -820px -540px; }
-.emoji-2B06 { background-position: -820px -560px; }
-.emoji-2B07 { background-position: -820px -580px; }
-.emoji-2B1B { background-position: -820px -600px; }
-.emoji-2B1C { background-position: -820px -620px; }
-.emoji-2B50 { background-position: -820px -640px; }
-.emoji-2B55 { background-position: -820px -660px; }
-.emoji-3030 { background-position: -820px -680px; }
-.emoji-303D { background-position: -820px -700px; }
-.emoji-3297 { background-position: -820px -720px; }
-.emoji-3299 { background-position: -820px -740px; }
+.emoji-1F396 { background-position: -420px -260px; }
+.emoji-1F397 { background-position: -420px -280px; }
+.emoji-1F399 { background-position: -420px -300px; }
+.emoji-1F39A { background-position: -420px -320px; }
+.emoji-1F39B { background-position: -420px -340px; }
+.emoji-1F39E { background-position: -420px -360px; }
+.emoji-1F39F { background-position: -420px -380px; }
+.emoji-1F3A0 { background-position: -420px -400px; }
+.emoji-1F3A1 { background-position: 0 -420px; }
+.emoji-1F3A2 { background-position: -20px -420px; }
+.emoji-1F3A3 { background-position: -40px -420px; }
+.emoji-1F3A4 { background-position: -60px -420px; }
+.emoji-1F3A5 { background-position: -80px -420px; }
+.emoji-1F3A6 { background-position: -100px -420px; }
+.emoji-1F3A7 { background-position: -120px -420px; }
+.emoji-1F3A8 { background-position: -140px -420px; }
+.emoji-1F3A9 { background-position: -160px -420px; }
+.emoji-1F3AA { background-position: -180px -420px; }
+.emoji-1F3AB { background-position: -200px -420px; }
+.emoji-1F3AC { background-position: -220px -420px; }
+.emoji-1F3AD { background-position: -240px -420px; }
+.emoji-1F3AE { background-position: -260px -420px; }
+.emoji-1F3AF { background-position: -280px -420px; }
+.emoji-1F3B0 { background-position: -300px -420px; }
+.emoji-1F3B1 { background-position: -320px -420px; }
+.emoji-1F3B2 { background-position: -340px -420px; }
+.emoji-1F3B3 { background-position: -360px -420px; }
+.emoji-1F3B4 { background-position: -380px -420px; }
+.emoji-1F3B5 { background-position: -400px -420px; }
+.emoji-1F3B6 { background-position: -420px -420px; }
+.emoji-1F3B7 { background-position: -440px 0; }
+.emoji-1F3B8 { background-position: -440px -20px; }
+.emoji-1F3B9 { background-position: -440px -40px; }
+.emoji-1F3BA { background-position: -440px -60px; }
+.emoji-1F3BB { background-position: -440px -80px; }
+.emoji-1F3BC { background-position: -440px -100px; }
+.emoji-1F3BD { background-position: -440px -120px; }
+.emoji-1F3BE { background-position: -440px -140px; }
+.emoji-1F3BF { background-position: -440px -160px; }
+.emoji-1F3C0 { background-position: -440px -180px; }
+.emoji-1F3C1 { background-position: -440px -200px; }
+.emoji-1F3C2 { background-position: -440px -220px; }
+.emoji-1F3C3 { background-position: -440px -240px; }
+.emoji-1F3C3-1F3FB { background-position: -440px -260px; }
+.emoji-1F3C3-1F3FC { background-position: -440px -280px; }
+.emoji-1F3C3-1F3FD { background-position: -440px -300px; }
+.emoji-1F3C3-1F3FE { background-position: -440px -320px; }
+.emoji-1F3C3-1F3FF { background-position: -440px -340px; }
+.emoji-1F3C4 { background-position: -440px -360px; }
+.emoji-1F3C4-1F3FB { background-position: -440px -380px; }
+.emoji-1F3C4-1F3FC { background-position: -440px -400px; }
+.emoji-1F3C4-1F3FD { background-position: -440px -420px; }
+.emoji-1F3C4-1F3FE { background-position: 0 -440px; }
+.emoji-1F3C4-1F3FF { background-position: -20px -440px; }
+.emoji-1F3C5 { background-position: -40px -440px; }
+.emoji-1F3C6 { background-position: -60px -440px; }
+.emoji-1F3C7 { background-position: -80px -440px; }
+.emoji-1F3C7-1F3FB { background-position: -100px -440px; }
+.emoji-1F3C7-1F3FC { background-position: -120px -440px; }
+.emoji-1F3C7-1F3FD { background-position: -140px -440px; }
+.emoji-1F3C7-1F3FE { background-position: -160px -440px; }
+.emoji-1F3C7-1F3FF { background-position: -180px -440px; }
+.emoji-1F3C8 { background-position: -200px -440px; }
+.emoji-1F3C9 { background-position: -220px -440px; }
+.emoji-1F3CA { background-position: -240px -440px; }
+.emoji-1F3CA-1F3FB { background-position: -260px -440px; }
+.emoji-1F3CA-1F3FC { background-position: -280px -440px; }
+.emoji-1F3CA-1F3FD { background-position: -300px -440px; }
+.emoji-1F3CA-1F3FE { background-position: -320px -440px; }
+.emoji-1F3CA-1F3FF { background-position: -340px -440px; }
+.emoji-1F3CB { background-position: -360px -440px; }
+.emoji-1F3CB-1F3FB { background-position: -380px -440px; }
+.emoji-1F3CB-1F3FC { background-position: -400px -440px; }
+.emoji-1F3CB-1F3FD { background-position: -420px -440px; }
+.emoji-1F3CB-1F3FE { background-position: -440px -440px; }
+.emoji-1F3CB-1F3FF { background-position: -460px 0; }
+.emoji-1F3CC { background-position: -460px -20px; }
+.emoji-1F3CD { background-position: -460px -40px; }
+.emoji-1F3CE { background-position: -460px -60px; }
+.emoji-1F3CF { background-position: -460px -80px; }
+.emoji-1F3D0 { background-position: -460px -100px; }
+.emoji-1F3D1 { background-position: -460px -120px; }
+.emoji-1F3D2 { background-position: -460px -140px; }
+.emoji-1F3D3 { background-position: -460px -160px; }
+.emoji-1F3D4 { background-position: -460px -180px; }
+.emoji-1F3D5 { background-position: -460px -200px; }
+.emoji-1F3D6 { background-position: -460px -220px; }
+.emoji-1F3D7 { background-position: -460px -240px; }
+.emoji-1F3D8 { background-position: -460px -260px; }
+.emoji-1F3D9 { background-position: -460px -280px; }
+.emoji-1F3DA { background-position: -460px -300px; }
+.emoji-1F3DB { background-position: -460px -320px; }
+.emoji-1F3DC { background-position: -460px -340px; }
+.emoji-1F3DD { background-position: -460px -360px; }
+.emoji-1F3DE { background-position: -460px -380px; }
+.emoji-1F3DF { background-position: -460px -400px; }
+.emoji-1F3E0 { background-position: -460px -420px; }
+.emoji-1F3E1 { background-position: -460px -440px; }
+.emoji-1F3E2 { background-position: 0 -460px; }
+.emoji-1F3E3 { background-position: -20px -460px; }
+.emoji-1F3E4 { background-position: -40px -460px; }
+.emoji-1F3E5 { background-position: -60px -460px; }
+.emoji-1F3E6 { background-position: -80px -460px; }
+.emoji-1F3E7 { background-position: -100px -460px; }
+.emoji-1F3E8 { background-position: -120px -460px; }
+.emoji-1F3E9 { background-position: -140px -460px; }
+.emoji-1F3EA { background-position: -160px -460px; }
+.emoji-1F3EB { background-position: -180px -460px; }
+.emoji-1F3EC { background-position: -200px -460px; }
+.emoji-1F3ED { background-position: -220px -460px; }
+.emoji-1F3EE { background-position: -240px -460px; }
+.emoji-1F3EF { background-position: -260px -460px; }
+.emoji-1F3F0 { background-position: -280px -460px; }
+.emoji-1F3F3 { background-position: -300px -460px; }
+.emoji-1F3F4 { background-position: -320px -460px; }
+.emoji-1F3F5 { background-position: -340px -460px; }
+.emoji-1F3F7 { background-position: -360px -460px; }
+.emoji-1F3F8 { background-position: -380px -460px; }
+.emoji-1F3F9 { background-position: -400px -460px; }
+.emoji-1F3FA { background-position: -420px -460px; }
+.emoji-1F3FB { background-position: -440px -460px; }
+.emoji-1F3FC { background-position: -460px -460px; }
+.emoji-1F3FD { background-position: -480px 0; }
+.emoji-1F3FE { background-position: -480px -20px; }
+.emoji-1F3FF { background-position: -480px -40px; }
+.emoji-1F400 { background-position: -480px -60px; }
+.emoji-1F401 { background-position: -480px -80px; }
+.emoji-1F402 { background-position: -480px -100px; }
+.emoji-1F403 { background-position: -480px -120px; }
+.emoji-1F404 { background-position: -480px -140px; }
+.emoji-1F405 { background-position: -480px -160px; }
+.emoji-1F406 { background-position: -480px -180px; }
+.emoji-1F407 { background-position: -480px -200px; }
+.emoji-1F408 { background-position: -480px -220px; }
+.emoji-1F409 { background-position: -480px -240px; }
+.emoji-1F40A { background-position: -480px -260px; }
+.emoji-1F40B { background-position: -480px -280px; }
+.emoji-1F40C { background-position: -480px -300px; }
+.emoji-1F40D { background-position: -480px -320px; }
+.emoji-1F40E { background-position: -480px -340px; }
+.emoji-1F40F { background-position: -480px -360px; }
+.emoji-1F410 { background-position: -480px -380px; }
+.emoji-1F411 { background-position: -480px -400px; }
+.emoji-1F412 { background-position: -480px -420px; }
+.emoji-1F413 { background-position: -480px -440px; }
+.emoji-1F414 { background-position: -480px -460px; }
+.emoji-1F415 { background-position: 0 -480px; }
+.emoji-1F416 { background-position: -20px -480px; }
+.emoji-1F417 { background-position: -40px -480px; }
+.emoji-1F418 { background-position: -60px -480px; }
+.emoji-1F419 { background-position: -80px -480px; }
+.emoji-1F41A { background-position: -100px -480px; }
+.emoji-1F41B { background-position: -120px -480px; }
+.emoji-1F41C { background-position: -140px -480px; }
+.emoji-1F41D { background-position: -160px -480px; }
+.emoji-1F41E { background-position: -180px -480px; }
+.emoji-1F41F { background-position: -200px -480px; }
+.emoji-1F420 { background-position: -220px -480px; }
+.emoji-1F421 { background-position: -240px -480px; }
+.emoji-1F422 { background-position: -260px -480px; }
+.emoji-1F423 { background-position: -280px -480px; }
+.emoji-1F424 { background-position: -300px -480px; }
+.emoji-1F425 { background-position: -320px -480px; }
+.emoji-1F426 { background-position: -340px -480px; }
+.emoji-1F427 { background-position: -360px -480px; }
+.emoji-1F428 { background-position: -380px -480px; }
+.emoji-1F429 { background-position: -400px -480px; }
+.emoji-1F42A { background-position: -420px -480px; }
+.emoji-1F42B { background-position: -440px -480px; }
+.emoji-1F42C { background-position: -460px -480px; }
+.emoji-1F42D { background-position: -480px -480px; }
+.emoji-1F42E { background-position: -500px 0; }
+.emoji-1F42F { background-position: -500px -20px; }
+.emoji-1F430 { background-position: -500px -40px; }
+.emoji-1F431 { background-position: -500px -60px; }
+.emoji-1F432 { background-position: -500px -80px; }
+.emoji-1F433 { background-position: -500px -100px; }
+.emoji-1F434 { background-position: -500px -120px; }
+.emoji-1F435 { background-position: -500px -140px; }
+.emoji-1F436 { background-position: -500px -160px; }
+.emoji-1F437 { background-position: -500px -180px; }
+.emoji-1F438 { background-position: -500px -200px; }
+.emoji-1F439 { background-position: -500px -220px; }
+.emoji-1F43A { background-position: -500px -240px; }
+.emoji-1F43B { background-position: -500px -260px; }
+.emoji-1F43C { background-position: -500px -280px; }
+.emoji-1F43D { background-position: -500px -300px; }
+.emoji-1F43E { background-position: -500px -320px; }
+.emoji-1F43F { background-position: -500px -340px; }
+.emoji-1F440 { background-position: -500px -360px; }
+.emoji-1F441 { background-position: -500px -380px; }
+.emoji-1F441-1F5E8 { background-position: -500px -400px; }
+.emoji-1F442 { background-position: -500px -420px; }
+.emoji-1F442-1F3FB { background-position: -500px -440px; }
+.emoji-1F442-1F3FC { background-position: -500px -460px; }
+.emoji-1F442-1F3FD { background-position: -500px -480px; }
+.emoji-1F442-1F3FE { background-position: 0 -500px; }
+.emoji-1F442-1F3FF { background-position: -20px -500px; }
+.emoji-1F443 { background-position: -40px -500px; }
+.emoji-1F443-1F3FB { background-position: -60px -500px; }
+.emoji-1F443-1F3FC { background-position: -80px -500px; }
+.emoji-1F443-1F3FD { background-position: -100px -500px; }
+.emoji-1F443-1F3FE { background-position: -120px -500px; }
+.emoji-1F443-1F3FF { background-position: -140px -500px; }
+.emoji-1F444 { background-position: -160px -500px; }
+.emoji-1F445 { background-position: -180px -500px; }
+.emoji-1F446 { background-position: -200px -500px; }
+.emoji-1F446-1F3FB { background-position: -220px -500px; }
+.emoji-1F446-1F3FC { background-position: -240px -500px; }
+.emoji-1F446-1F3FD { background-position: -260px -500px; }
+.emoji-1F446-1F3FE { background-position: -280px -500px; }
+.emoji-1F446-1F3FF { background-position: -300px -500px; }
+.emoji-1F447 { background-position: -320px -500px; }
+.emoji-1F447-1F3FB { background-position: -340px -500px; }
+.emoji-1F447-1F3FC { background-position: -360px -500px; }
+.emoji-1F447-1F3FD { background-position: -380px -500px; }
+.emoji-1F447-1F3FE { background-position: -400px -500px; }
+.emoji-1F447-1F3FF { background-position: -420px -500px; }
+.emoji-1F448 { background-position: -440px -500px; }
+.emoji-1F448-1F3FB { background-position: -460px -500px; }
+.emoji-1F448-1F3FC { background-position: -480px -500px; }
+.emoji-1F448-1F3FD { background-position: -500px -500px; }
+.emoji-1F448-1F3FE { background-position: -520px 0; }
+.emoji-1F448-1F3FF { background-position: -520px -20px; }
+.emoji-1F449 { background-position: -520px -40px; }
+.emoji-1F449-1F3FB { background-position: -520px -60px; }
+.emoji-1F449-1F3FC { background-position: -520px -80px; }
+.emoji-1F449-1F3FD { background-position: -520px -100px; }
+.emoji-1F449-1F3FE { background-position: -520px -120px; }
+.emoji-1F449-1F3FF { background-position: -520px -140px; }
+.emoji-1F44A { background-position: -520px -160px; }
+.emoji-1F44A-1F3FB { background-position: -520px -180px; }
+.emoji-1F44A-1F3FC { background-position: -520px -200px; }
+.emoji-1F44A-1F3FD { background-position: -520px -220px; }
+.emoji-1F44A-1F3FE { background-position: -520px -240px; }
+.emoji-1F44A-1F3FF { background-position: -520px -260px; }
+.emoji-1F44B { background-position: -520px -280px; }
+.emoji-1F44B-1F3FB { background-position: -520px -300px; }
+.emoji-1F44B-1F3FC { background-position: -520px -320px; }
+.emoji-1F44B-1F3FD { background-position: -520px -340px; }
+.emoji-1F44B-1F3FE { background-position: -520px -360px; }
+.emoji-1F44B-1F3FF { background-position: -520px -380px; }
+.emoji-1F44C { background-position: -520px -400px; }
+.emoji-1F44C-1F3FB { background-position: -520px -420px; }
+.emoji-1F44C-1F3FC { background-position: -520px -440px; }
+.emoji-1F44C-1F3FD { background-position: -520px -460px; }
+.emoji-1F44C-1F3FE { background-position: -520px -480px; }
+.emoji-1F44C-1F3FF { background-position: -520px -500px; }
+.emoji-1F44D { background-position: 0 -520px; }
+.emoji-1F44D-1F3FB { background-position: -20px -520px; }
+.emoji-1F44D-1F3FC { background-position: -40px -520px; }
+.emoji-1F44D-1F3FD { background-position: -60px -520px; }
+.emoji-1F44D-1F3FE { background-position: -80px -520px; }
+.emoji-1F44D-1F3FF { background-position: -100px -520px; }
+.emoji-1F44E { background-position: -120px -520px; }
+.emoji-1F44E-1F3FB { background-position: -140px -520px; }
+.emoji-1F44E-1F3FC { background-position: -160px -520px; }
+.emoji-1F44E-1F3FD { background-position: -180px -520px; }
+.emoji-1F44E-1F3FE { background-position: -200px -520px; }
+.emoji-1F44E-1F3FF { background-position: -220px -520px; }
+.emoji-1F44F { background-position: -240px -520px; }
+.emoji-1F44F-1F3FB { background-position: -260px -520px; }
+.emoji-1F44F-1F3FC { background-position: -280px -520px; }
+.emoji-1F44F-1F3FD { background-position: -300px -520px; }
+.emoji-1F44F-1F3FE { background-position: -320px -520px; }
+.emoji-1F44F-1F3FF { background-position: -340px -520px; }
+.emoji-1F450 { background-position: -360px -520px; }
+.emoji-1F450-1F3FB { background-position: -380px -520px; }
+.emoji-1F450-1F3FC { background-position: -400px -520px; }
+.emoji-1F450-1F3FD { background-position: -420px -520px; }
+.emoji-1F450-1F3FE { background-position: -440px -520px; }
+.emoji-1F450-1F3FF { background-position: -460px -520px; }
+.emoji-1F451 { background-position: -480px -520px; }
+.emoji-1F452 { background-position: -500px -520px; }
+.emoji-1F453 { background-position: -520px -520px; }
+.emoji-1F454 { background-position: -540px 0; }
+.emoji-1F455 { background-position: -540px -20px; }
+.emoji-1F456 { background-position: -540px -40px; }
+.emoji-1F457 { background-position: -540px -60px; }
+.emoji-1F458 { background-position: -540px -80px; }
+.emoji-1F459 { background-position: -540px -100px; }
+.emoji-1F45A { background-position: -540px -120px; }
+.emoji-1F45B { background-position: -540px -140px; }
+.emoji-1F45C { background-position: -540px -160px; }
+.emoji-1F45D { background-position: -540px -180px; }
+.emoji-1F45E { background-position: -540px -200px; }
+.emoji-1F45F { background-position: -540px -220px; }
+.emoji-1F460 { background-position: -540px -240px; }
+.emoji-1F461 { background-position: -540px -260px; }
+.emoji-1F462 { background-position: -540px -280px; }
+.emoji-1F463 { background-position: -540px -300px; }
+.emoji-1F464 { background-position: -540px -320px; }
+.emoji-1F465 { background-position: -540px -340px; }
+.emoji-1F466 { background-position: -540px -360px; }
+.emoji-1F466-1F3FB { background-position: -540px -380px; }
+.emoji-1F466-1F3FC { background-position: -540px -400px; }
+.emoji-1F466-1F3FD { background-position: -540px -420px; }
+.emoji-1F466-1F3FE { background-position: -540px -440px; }
+.emoji-1F466-1F3FF { background-position: -540px -460px; }
+.emoji-1F467 { background-position: -540px -480px; }
+.emoji-1F467-1F3FB { background-position: -540px -500px; }
+.emoji-1F467-1F3FC { background-position: -540px -520px; }
+.emoji-1F467-1F3FD { background-position: 0 -540px; }
+.emoji-1F467-1F3FE { background-position: -20px -540px; }
+.emoji-1F467-1F3FF { background-position: -40px -540px; }
+.emoji-1F468 { background-position: -60px -540px; }
+.emoji-1F468-1F3FB { background-position: -80px -540px; }
+.emoji-1F468-1F3FC { background-position: -100px -540px; }
+.emoji-1F468-1F3FD { background-position: -120px -540px; }
+.emoji-1F468-1F3FE { background-position: -140px -540px; }
+.emoji-1F468-1F3FF { background-position: -160px -540px; }
+.emoji-1F468-1F468-1F466 { background-position: -180px -540px; }
+.emoji-1F468-1F468-1F466-1F466 { background-position: -200px -540px; }
+.emoji-1F468-1F468-1F467 { background-position: -220px -540px; }
+.emoji-1F468-1F468-1F467-1F466 { background-position: -240px -540px; }
+.emoji-1F468-1F468-1F467-1F467 { background-position: -260px -540px; }
+.emoji-1F468-1F469-1F466-1F466 { background-position: -280px -540px; }
+.emoji-1F468-1F469-1F467 { background-position: -300px -540px; }
+.emoji-1F468-1F469-1F467-1F466 { background-position: -320px -540px; }
+.emoji-1F468-1F469-1F467-1F467 { background-position: -340px -540px; }
+.emoji-1F468-2764-1F468 { background-position: -360px -540px; }
+.emoji-1F468-2764-1F48B-1F468 { background-position: -380px -540px; }
+.emoji-1F469 { background-position: -400px -540px; }
+.emoji-1F469-1F3FB { background-position: -420px -540px; }
+.emoji-1F469-1F3FC { background-position: -440px -540px; }
+.emoji-1F469-1F3FD { background-position: -460px -540px; }
+.emoji-1F469-1F3FE { background-position: -480px -540px; }
+.emoji-1F469-1F3FF { background-position: -500px -540px; }
+.emoji-1F469-1F469-1F466 { background-position: -520px -540px; }
+.emoji-1F469-1F469-1F466-1F466 { background-position: -540px -540px; }
+.emoji-1F469-1F469-1F467 { background-position: -560px 0; }
+.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -20px; }
+.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -40px; }
+.emoji-1F469-2764-1F469 { background-position: -560px -60px; }
+.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -80px; }
+.emoji-1F46A { background-position: -560px -100px; }
+.emoji-1F46B { background-position: -560px -120px; }
+.emoji-1F46C { background-position: -560px -140px; }
+.emoji-1F46D { background-position: -560px -160px; }
+.emoji-1F46E { background-position: -560px -180px; }
+.emoji-1F46E-1F3FB { background-position: -560px -200px; }
+.emoji-1F46E-1F3FC { background-position: -560px -220px; }
+.emoji-1F46E-1F3FD { background-position: -560px -240px; }
+.emoji-1F46E-1F3FE { background-position: -560px -260px; }
+.emoji-1F46E-1F3FF { background-position: -560px -280px; }
+.emoji-1F46F { background-position: -560px -300px; }
+.emoji-1F470 { background-position: -560px -320px; }
+.emoji-1F470-1F3FB { background-position: -560px -340px; }
+.emoji-1F470-1F3FC { background-position: -560px -360px; }
+.emoji-1F470-1F3FD { background-position: -560px -380px; }
+.emoji-1F470-1F3FE { background-position: -560px -400px; }
+.emoji-1F470-1F3FF { background-position: -560px -420px; }
+.emoji-1F471 { background-position: -560px -440px; }
+.emoji-1F471-1F3FB { background-position: -560px -460px; }
+.emoji-1F471-1F3FC { background-position: -560px -480px; }
+.emoji-1F471-1F3FD { background-position: -560px -500px; }
+.emoji-1F471-1F3FE { background-position: -560px -520px; }
+.emoji-1F471-1F3FF { background-position: -560px -540px; }
+.emoji-1F472 { background-position: 0 -560px; }
+.emoji-1F472-1F3FB { background-position: -20px -560px; }
+.emoji-1F472-1F3FC { background-position: -40px -560px; }
+.emoji-1F472-1F3FD { background-position: -60px -560px; }
+.emoji-1F472-1F3FE { background-position: -80px -560px; }
+.emoji-1F472-1F3FF { background-position: -100px -560px; }
+.emoji-1F473 { background-position: -120px -560px; }
+.emoji-1F473-1F3FB { background-position: -140px -560px; }
+.emoji-1F473-1F3FC { background-position: -160px -560px; }
+.emoji-1F473-1F3FD { background-position: -180px -560px; }
+.emoji-1F473-1F3FE { background-position: -200px -560px; }
+.emoji-1F473-1F3FF { background-position: -220px -560px; }
+.emoji-1F474 { background-position: -240px -560px; }
+.emoji-1F474-1F3FB { background-position: -260px -560px; }
+.emoji-1F474-1F3FC { background-position: -280px -560px; }
+.emoji-1F474-1F3FD { background-position: -300px -560px; }
+.emoji-1F474-1F3FE { background-position: -320px -560px; }
+.emoji-1F474-1F3FF { background-position: -340px -560px; }
+.emoji-1F475 { background-position: -360px -560px; }
+.emoji-1F475-1F3FB { background-position: -380px -560px; }
+.emoji-1F475-1F3FC { background-position: -400px -560px; }
+.emoji-1F475-1F3FD { background-position: -420px -560px; }
+.emoji-1F475-1F3FE { background-position: -440px -560px; }
+.emoji-1F475-1F3FF { background-position: -460px -560px; }
+.emoji-1F476 { background-position: -480px -560px; }
+.emoji-1F476-1F3FB { background-position: -500px -560px; }
+.emoji-1F476-1F3FC { background-position: -520px -560px; }
+.emoji-1F476-1F3FD { background-position: -540px -560px; }
+.emoji-1F476-1F3FE { background-position: -560px -560px; }
+.emoji-1F476-1F3FF { background-position: -580px 0; }
+.emoji-1F477 { background-position: -580px -20px; }
+.emoji-1F477-1F3FB { background-position: -580px -40px; }
+.emoji-1F477-1F3FC { background-position: -580px -60px; }
+.emoji-1F477-1F3FD { background-position: -580px -80px; }
+.emoji-1F477-1F3FE { background-position: -580px -100px; }
+.emoji-1F477-1F3FF { background-position: -580px -120px; }
+.emoji-1F478 { background-position: -580px -140px; }
+.emoji-1F478-1F3FB { background-position: -580px -160px; }
+.emoji-1F478-1F3FC { background-position: -580px -180px; }
+.emoji-1F478-1F3FD { background-position: -580px -200px; }
+.emoji-1F478-1F3FE { background-position: -580px -220px; }
+.emoji-1F478-1F3FF { background-position: -580px -240px; }
+.emoji-1F479 { background-position: -580px -260px; }
+.emoji-1F47A { background-position: -580px -280px; }
+.emoji-1F47B { background-position: -580px -300px; }
+.emoji-1F47C { background-position: -580px -320px; }
+.emoji-1F47C-1F3FB { background-position: -580px -340px; }
+.emoji-1F47C-1F3FC { background-position: -580px -360px; }
+.emoji-1F47C-1F3FD { background-position: -580px -380px; }
+.emoji-1F47C-1F3FE { background-position: -580px -400px; }
+.emoji-1F47C-1F3FF { background-position: -580px -420px; }
+.emoji-1F47D { background-position: -580px -440px; }
+.emoji-1F47E { background-position: -580px -460px; }
+.emoji-1F47F { background-position: -580px -480px; }
+.emoji-1F480 { background-position: -580px -500px; }
+.emoji-1F481 { background-position: -580px -520px; }
+.emoji-1F481-1F3FB { background-position: -580px -540px; }
+.emoji-1F481-1F3FC { background-position: -580px -560px; }
+.emoji-1F481-1F3FD { background-position: 0 -580px; }
+.emoji-1F481-1F3FE { background-position: -20px -580px; }
+.emoji-1F481-1F3FF { background-position: -40px -580px; }
+.emoji-1F482 { background-position: -60px -580px; }
+.emoji-1F482-1F3FB { background-position: -80px -580px; }
+.emoji-1F482-1F3FC { background-position: -100px -580px; }
+.emoji-1F482-1F3FD { background-position: -120px -580px; }
+.emoji-1F482-1F3FE { background-position: -140px -580px; }
+.emoji-1F482-1F3FF { background-position: -160px -580px; }
+.emoji-1F483 { background-position: -180px -580px; }
+.emoji-1F483-1F3FB { background-position: -200px -580px; }
+.emoji-1F483-1F3FC { background-position: -220px -580px; }
+.emoji-1F483-1F3FD { background-position: -240px -580px; }
+.emoji-1F483-1F3FE { background-position: -260px -580px; }
+.emoji-1F483-1F3FF { background-position: -280px -580px; }
+.emoji-1F484 { background-position: -300px -580px; }
+.emoji-1F485 { background-position: -320px -580px; }
+.emoji-1F485-1F3FB { background-position: -340px -580px; }
+.emoji-1F485-1F3FC { background-position: -360px -580px; }
+.emoji-1F485-1F3FD { background-position: -380px -580px; }
+.emoji-1F485-1F3FE { background-position: -400px -580px; }
+.emoji-1F485-1F3FF { background-position: -420px -580px; }
+.emoji-1F486 { background-position: -440px -580px; }
+.emoji-1F486-1F3FB { background-position: -460px -580px; }
+.emoji-1F486-1F3FC { background-position: -480px -580px; }
+.emoji-1F486-1F3FD { background-position: -500px -580px; }
+.emoji-1F486-1F3FE { background-position: -520px -580px; }
+.emoji-1F486-1F3FF { background-position: -540px -580px; }
+.emoji-1F487 { background-position: -560px -580px; }
+.emoji-1F487-1F3FB { background-position: -580px -580px; }
+.emoji-1F487-1F3FC { background-position: -600px 0; }
+.emoji-1F487-1F3FD { background-position: -600px -20px; }
+.emoji-1F487-1F3FE { background-position: -600px -40px; }
+.emoji-1F487-1F3FF { background-position: -600px -60px; }
+.emoji-1F488 { background-position: -600px -80px; }
+.emoji-1F489 { background-position: -600px -100px; }
+.emoji-1F48A { background-position: -600px -120px; }
+.emoji-1F48B { background-position: -600px -140px; }
+.emoji-1F48C { background-position: -600px -160px; }
+.emoji-1F48D { background-position: -600px -180px; }
+.emoji-1F48E { background-position: -600px -200px; }
+.emoji-1F48F { background-position: -600px -220px; }
+.emoji-1F490 { background-position: -600px -240px; }
+.emoji-1F491 { background-position: -600px -260px; }
+.emoji-1F492 { background-position: -600px -280px; }
+.emoji-1F493 { background-position: -600px -300px; }
+.emoji-1F494 { background-position: -600px -320px; }
+.emoji-1F495 { background-position: -600px -340px; }
+.emoji-1F496 { background-position: -600px -360px; }
+.emoji-1F497 { background-position: -600px -380px; }
+.emoji-1F498 { background-position: -600px -400px; }
+.emoji-1F499 { background-position: -600px -420px; }
+.emoji-1F49A { background-position: -600px -440px; }
+.emoji-1F49B { background-position: -600px -460px; }
+.emoji-1F49C { background-position: -600px -480px; }
+.emoji-1F49D { background-position: -600px -500px; }
+.emoji-1F49E { background-position: -600px -520px; }
+.emoji-1F49F { background-position: -600px -540px; }
+.emoji-1F4A0 { background-position: -600px -560px; }
+.emoji-1F4A1 { background-position: -600px -580px; }
+.emoji-1F4A2 { background-position: 0 -600px; }
+.emoji-1F4A3 { background-position: -20px -600px; }
+.emoji-1F4A4 { background-position: -40px -600px; }
+.emoji-1F4A5 { background-position: -60px -600px; }
+.emoji-1F4A6 { background-position: -80px -600px; }
+.emoji-1F4A7 { background-position: -100px -600px; }
+.emoji-1F4A8 { background-position: -120px -600px; }
+.emoji-1F4A9 { background-position: -140px -600px; }
+.emoji-1F4AA { background-position: -160px -600px; }
+.emoji-1F4AA-1F3FB { background-position: -180px -600px; }
+.emoji-1F4AA-1F3FC { background-position: -200px -600px; }
+.emoji-1F4AA-1F3FD { background-position: -220px -600px; }
+.emoji-1F4AA-1F3FE { background-position: -240px -600px; }
+.emoji-1F4AA-1F3FF { background-position: -260px -600px; }
+.emoji-1F4AB { background-position: -280px -600px; }
+.emoji-1F4AC { background-position: -300px -600px; }
+.emoji-1F4AD { background-position: -320px -600px; }
+.emoji-1F4AE { background-position: -340px -600px; }
+.emoji-1F4AF { background-position: -360px -600px; }
+.emoji-1F4B0 { background-position: -380px -600px; }
+.emoji-1F4B1 { background-position: -400px -600px; }
+.emoji-1F4B2 { background-position: -420px -600px; }
+.emoji-1F4B3 { background-position: -440px -600px; }
+.emoji-1F4B4 { background-position: -460px -600px; }
+.emoji-1F4B5 { background-position: -480px -600px; }
+.emoji-1F4B6 { background-position: -500px -600px; }
+.emoji-1F4B7 { background-position: -520px -600px; }
+.emoji-1F4B8 { background-position: -540px -600px; }
+.emoji-1F4B9 { background-position: -560px -600px; }
+.emoji-1F4BA { background-position: -580px -600px; }
+.emoji-1F4BB { background-position: -600px -600px; }
+.emoji-1F4BC { background-position: -620px 0; }
+.emoji-1F4BD { background-position: -620px -20px; }
+.emoji-1F4BE { background-position: -620px -40px; }
+.emoji-1F4BF { background-position: -620px -60px; }
+.emoji-1F4C0 { background-position: -620px -80px; }
+.emoji-1F4C1 { background-position: -620px -100px; }
+.emoji-1F4C2 { background-position: -620px -120px; }
+.emoji-1F4C3 { background-position: -620px -140px; }
+.emoji-1F4C4 { background-position: -620px -160px; }
+.emoji-1F4C5 { background-position: -620px -180px; }
+.emoji-1F4C6 { background-position: -620px -200px; }
+.emoji-1F4C7 { background-position: -620px -220px; }
+.emoji-1F4C8 { background-position: -620px -240px; }
+.emoji-1F4C9 { background-position: -620px -260px; }
+.emoji-1F4CA { background-position: -620px -280px; }
+.emoji-1F4CB { background-position: -620px -300px; }
+.emoji-1F4CC { background-position: -620px -320px; }
+.emoji-1F4CD { background-position: -620px -340px; }
+.emoji-1F4CE { background-position: -620px -360px; }
+.emoji-1F4CF { background-position: -620px -380px; }
+.emoji-1F4D0 { background-position: -620px -400px; }
+.emoji-1F4D1 { background-position: -620px -420px; }
+.emoji-1F4D2 { background-position: -620px -440px; }
+.emoji-1F4D3 { background-position: -620px -460px; }
+.emoji-1F4D4 { background-position: -620px -480px; }
+.emoji-1F4D5 { background-position: -620px -500px; }
+.emoji-1F4D6 { background-position: -620px -520px; }
+.emoji-1F4D7 { background-position: -620px -540px; }
+.emoji-1F4D8 { background-position: -620px -560px; }
+.emoji-1F4D9 { background-position: -620px -580px; }
+.emoji-1F4DA { background-position: -620px -600px; }
+.emoji-1F4DB { background-position: 0 -620px; }
+.emoji-1F4DC { background-position: -20px -620px; }
+.emoji-1F4DD { background-position: -40px -620px; }
+.emoji-1F4DE { background-position: -60px -620px; }
+.emoji-1F4DF { background-position: -80px -620px; }
+.emoji-1F4E0 { background-position: -100px -620px; }
+.emoji-1F4E1 { background-position: -120px -620px; }
+.emoji-1F4E2 { background-position: -140px -620px; }
+.emoji-1F4E3 { background-position: -160px -620px; }
+.emoji-1F4E4 { background-position: -180px -620px; }
+.emoji-1F4E5 { background-position: -200px -620px; }
+.emoji-1F4E6 { background-position: -220px -620px; }
+.emoji-1F4E7 { background-position: -240px -620px; }
+.emoji-1F4E8 { background-position: -260px -620px; }
+.emoji-1F4E9 { background-position: -280px -620px; }
+.emoji-1F4EA { background-position: -300px -620px; }
+.emoji-1F4EB { background-position: -320px -620px; }
+.emoji-1F4EC { background-position: -340px -620px; }
+.emoji-1F4ED { background-position: -360px -620px; }
+.emoji-1F4EE { background-position: -380px -620px; }
+.emoji-1F4EF { background-position: -400px -620px; }
+.emoji-1F4F0 { background-position: -420px -620px; }
+.emoji-1F4F1 { background-position: -440px -620px; }
+.emoji-1F4F2 { background-position: -460px -620px; }
+.emoji-1F4F3 { background-position: -480px -620px; }
+.emoji-1F4F4 { background-position: -500px -620px; }
+.emoji-1F4F5 { background-position: -520px -620px; }
+.emoji-1F4F6 { background-position: -540px -620px; }
+.emoji-1F4F7 { background-position: -560px -620px; }
+.emoji-1F4F8 { background-position: -580px -620px; }
+.emoji-1F4F9 { background-position: -600px -620px; }
+.emoji-1F4FA { background-position: -620px -620px; }
+.emoji-1F4FB { background-position: -640px 0; }
+.emoji-1F4FC { background-position: -640px -20px; }
+.emoji-1F4FD { background-position: -640px -40px; }
+.emoji-1F4FF { background-position: -640px -60px; }
+.emoji-1F500 { background-position: -640px -80px; }
+.emoji-1F501 { background-position: -640px -100px; }
+.emoji-1F502 { background-position: -640px -120px; }
+.emoji-1F503 { background-position: -640px -140px; }
+.emoji-1F504 { background-position: -640px -160px; }
+.emoji-1F505 { background-position: -640px -180px; }
+.emoji-1F506 { background-position: -640px -200px; }
+.emoji-1F507 { background-position: -640px -220px; }
+.emoji-1F508 { background-position: -640px -240px; }
+.emoji-1F509 { background-position: -640px -260px; }
+.emoji-1F50A { background-position: -640px -280px; }
+.emoji-1F50B { background-position: -640px -300px; }
+.emoji-1F50C { background-position: -640px -320px; }
+.emoji-1F50D { background-position: -640px -340px; }
+.emoji-1F50E { background-position: -640px -360px; }
+.emoji-1F50F { background-position: -640px -380px; }
+.emoji-1F510 { background-position: -640px -400px; }
+.emoji-1F511 { background-position: -640px -420px; }
+.emoji-1F512 { background-position: -640px -440px; }
+.emoji-1F513 { background-position: -640px -460px; }
+.emoji-1F514 { background-position: -640px -480px; }
+.emoji-1F515 { background-position: -640px -500px; }
+.emoji-1F516 { background-position: -640px -520px; }
+.emoji-1F517 { background-position: -640px -540px; }
+.emoji-1F518 { background-position: -640px -560px; }
+.emoji-1F519 { background-position: -640px -580px; }
+.emoji-1F51A { background-position: -640px -600px; }
+.emoji-1F51B { background-position: -640px -620px; }
+.emoji-1F51C { background-position: 0 -640px; }
+.emoji-1F51D { background-position: -20px -640px; }
+.emoji-1F51E { background-position: -40px -640px; }
+.emoji-1F51F { background-position: -60px -640px; }
+.emoji-1F520 { background-position: -80px -640px; }
+.emoji-1F521 { background-position: -100px -640px; }
+.emoji-1F522 { background-position: -120px -640px; }
+.emoji-1F523 { background-position: -140px -640px; }
+.emoji-1F524 { background-position: -160px -640px; }
+.emoji-1F525 { background-position: -180px -640px; }
+.emoji-1F526 { background-position: -200px -640px; }
+.emoji-1F527 { background-position: -220px -640px; }
+.emoji-1F528 { background-position: -240px -640px; }
+.emoji-1F529 { background-position: -260px -640px; }
+.emoji-1F52A { background-position: -280px -640px; }
+.emoji-1F52B { background-position: -300px -640px; }
+.emoji-1F52C { background-position: -320px -640px; }
+.emoji-1F52D { background-position: -340px -640px; }
+.emoji-1F52E { background-position: -360px -640px; }
+.emoji-1F52F { background-position: -380px -640px; }
+.emoji-1F530 { background-position: -400px -640px; }
+.emoji-1F531 { background-position: -420px -640px; }
+.emoji-1F532 { background-position: -440px -640px; }
+.emoji-1F533 { background-position: -460px -640px; }
+.emoji-1F534 { background-position: -480px -640px; }
+.emoji-1F535 { background-position: -500px -640px; }
+.emoji-1F536 { background-position: -520px -640px; }
+.emoji-1F537 { background-position: -540px -640px; }
+.emoji-1F538 { background-position: -560px -640px; }
+.emoji-1F539 { background-position: -580px -640px; }
+.emoji-1F53A { background-position: -600px -640px; }
+.emoji-1F53B { background-position: -620px -640px; }
+.emoji-1F53C { background-position: -640px -640px; }
+.emoji-1F53D { background-position: -660px 0; }
+.emoji-1F549 { background-position: -660px -20px; }
+.emoji-1F54A { background-position: -660px -40px; }
+.emoji-1F54B { background-position: -660px -60px; }
+.emoji-1F54C { background-position: -660px -80px; }
+.emoji-1F54D { background-position: -660px -100px; }
+.emoji-1F54E { background-position: -660px -120px; }
+.emoji-1F550 { background-position: -660px -140px; }
+.emoji-1F551 { background-position: -660px -160px; }
+.emoji-1F552 { background-position: -660px -180px; }
+.emoji-1F553 { background-position: -660px -200px; }
+.emoji-1F554 { background-position: -660px -220px; }
+.emoji-1F555 { background-position: -660px -240px; }
+.emoji-1F556 { background-position: -660px -260px; }
+.emoji-1F557 { background-position: -660px -280px; }
+.emoji-1F558 { background-position: -660px -300px; }
+.emoji-1F559 { background-position: -660px -320px; }
+.emoji-1F55A { background-position: -660px -340px; }
+.emoji-1F55B { background-position: -660px -360px; }
+.emoji-1F55C { background-position: -660px -380px; }
+.emoji-1F55D { background-position: -660px -400px; }
+.emoji-1F55E { background-position: -660px -420px; }
+.emoji-1F55F { background-position: -660px -440px; }
+.emoji-1F560 { background-position: -660px -460px; }
+.emoji-1F561 { background-position: -660px -480px; }
+.emoji-1F562 { background-position: -660px -500px; }
+.emoji-1F563 { background-position: -660px -520px; }
+.emoji-1F564 { background-position: -660px -540px; }
+.emoji-1F565 { background-position: -660px -560px; }
+.emoji-1F566 { background-position: -660px -580px; }
+.emoji-1F567 { background-position: -660px -600px; }
+.emoji-1F56F { background-position: -660px -620px; }
+.emoji-1F570 { background-position: -660px -640px; }
+.emoji-1F573 { background-position: 0 -660px; }
+.emoji-1F574 { background-position: -20px -660px; }
+.emoji-1F575 { background-position: -40px -660px; }
+.emoji-1F575-1F3FB { background-position: -60px -660px; }
+.emoji-1F575-1F3FC { background-position: -80px -660px; }
+.emoji-1F575-1F3FD { background-position: -100px -660px; }
+.emoji-1F575-1F3FE { background-position: -120px -660px; }
+.emoji-1F575-1F3FF { background-position: -140px -660px; }
+.emoji-1F576 { background-position: -160px -660px; }
+.emoji-1F577 { background-position: -180px -660px; }
+.emoji-1F578 { background-position: -200px -660px; }
+.emoji-1F579 { background-position: -220px -660px; }
+.emoji-1F57A { background-position: -240px -660px; }
+.emoji-1F57A-1F3FB { background-position: -260px -660px; }
+.emoji-1F57A-1F3FC { background-position: -280px -660px; }
+.emoji-1F57A-1F3FD { background-position: -300px -660px; }
+.emoji-1F57A-1F3FE { background-position: -320px -660px; }
+.emoji-1F57A-1F3FF { background-position: -340px -660px; }
+.emoji-1F587 { background-position: -360px -660px; }
+.emoji-1F58A { background-position: -380px -660px; }
+.emoji-1F58B { background-position: -400px -660px; }
+.emoji-1F58C { background-position: -420px -660px; }
+.emoji-1F58D { background-position: -440px -660px; }
+.emoji-1F590 { background-position: -460px -660px; }
+.emoji-1F590-1F3FB { background-position: -480px -660px; }
+.emoji-1F590-1F3FC { background-position: -500px -660px; }
+.emoji-1F590-1F3FD { background-position: -520px -660px; }
+.emoji-1F590-1F3FE { background-position: -540px -660px; }
+.emoji-1F590-1F3FF { background-position: -560px -660px; }
+.emoji-1F595 { background-position: -580px -660px; }
+.emoji-1F595-1F3FB { background-position: -600px -660px; }
+.emoji-1F595-1F3FC { background-position: -620px -660px; }
+.emoji-1F595-1F3FD { background-position: -640px -660px; }
+.emoji-1F595-1F3FE { background-position: -660px -660px; }
+.emoji-1F595-1F3FF { background-position: -680px 0; }
+.emoji-1F596 { background-position: -680px -20px; }
+.emoji-1F596-1F3FB { background-position: -680px -40px; }
+.emoji-1F596-1F3FC { background-position: -680px -60px; }
+.emoji-1F596-1F3FD { background-position: -680px -80px; }
+.emoji-1F596-1F3FE { background-position: -680px -100px; }
+.emoji-1F596-1F3FF { background-position: -680px -120px; }
+.emoji-1F5A4 { background-position: -680px -140px; }
+.emoji-1F5A5 { background-position: -680px -160px; }
+.emoji-1F5A8 { background-position: -680px -180px; }
+.emoji-1F5B1 { background-position: -680px -200px; }
+.emoji-1F5B2 { background-position: -680px -220px; }
+.emoji-1F5BC { background-position: -680px -240px; }
+.emoji-1F5C2 { background-position: -680px -260px; }
+.emoji-1F5C3 { background-position: -680px -280px; }
+.emoji-1F5C4 { background-position: -680px -300px; }
+.emoji-1F5D1 { background-position: -680px -320px; }
+.emoji-1F5D2 { background-position: -680px -340px; }
+.emoji-1F5D3 { background-position: -680px -360px; }
+.emoji-1F5DC { background-position: -680px -380px; }
+.emoji-1F5DD { background-position: -680px -400px; }
+.emoji-1F5DE { background-position: -680px -420px; }
+.emoji-1F5E1 { background-position: -680px -440px; }
+.emoji-1F5E3 { background-position: -680px -460px; }
+.emoji-1F5EF { background-position: -680px -480px; }
+.emoji-1F5F3 { background-position: -680px -500px; }
+.emoji-1F5FA { background-position: -680px -520px; }
+.emoji-1F5FB { background-position: -680px -540px; }
+.emoji-1F5FC { background-position: -680px -560px; }
+.emoji-1F5FD { background-position: -680px -580px; }
+.emoji-1F5FE { background-position: -680px -600px; }
+.emoji-1F5FF { background-position: -680px -620px; }
+.emoji-1F600 { background-position: -680px -640px; }
+.emoji-1F601 { background-position: -680px -660px; }
+.emoji-1F602 { background-position: 0 -680px; }
+.emoji-1F603 { background-position: -20px -680px; }
+.emoji-1F604 { background-position: -40px -680px; }
+.emoji-1F605 { background-position: -60px -680px; }
+.emoji-1F606 { background-position: -80px -680px; }
+.emoji-1F607 { background-position: -100px -680px; }
+.emoji-1F608 { background-position: -120px -680px; }
+.emoji-1F609 { background-position: -140px -680px; }
+.emoji-1F60A { background-position: -160px -680px; }
+.emoji-1F60B { background-position: -180px -680px; }
+.emoji-1F60C { background-position: -200px -680px; }
+.emoji-1F60D { background-position: -220px -680px; }
+.emoji-1F60E { background-position: -240px -680px; }
+.emoji-1F60F { background-position: -260px -680px; }
+.emoji-1F610 { background-position: -280px -680px; }
+.emoji-1F611 { background-position: -300px -680px; }
+.emoji-1F612 { background-position: -320px -680px; }
+.emoji-1F613 { background-position: -340px -680px; }
+.emoji-1F614 { background-position: -360px -680px; }
+.emoji-1F615 { background-position: -380px -680px; }
+.emoji-1F616 { background-position: -400px -680px; }
+.emoji-1F617 { background-position: -420px -680px; }
+.emoji-1F618 { background-position: -440px -680px; }
+.emoji-1F619 { background-position: -460px -680px; }
+.emoji-1F61A { background-position: -480px -680px; }
+.emoji-1F61B { background-position: -500px -680px; }
+.emoji-1F61C { background-position: -520px -680px; }
+.emoji-1F61D { background-position: -540px -680px; }
+.emoji-1F61E { background-position: -560px -680px; }
+.emoji-1F61F { background-position: -580px -680px; }
+.emoji-1F620 { background-position: -600px -680px; }
+.emoji-1F621 { background-position: -620px -680px; }
+.emoji-1F622 { background-position: -640px -680px; }
+.emoji-1F623 { background-position: -660px -680px; }
+.emoji-1F624 { background-position: -680px -680px; }
+.emoji-1F625 { background-position: -700px 0; }
+.emoji-1F626 { background-position: -700px -20px; }
+.emoji-1F627 { background-position: -700px -40px; }
+.emoji-1F628 { background-position: -700px -60px; }
+.emoji-1F629 { background-position: -700px -80px; }
+.emoji-1F62A { background-position: -700px -100px; }
+.emoji-1F62B { background-position: -700px -120px; }
+.emoji-1F62C { background-position: -700px -140px; }
+.emoji-1F62D { background-position: -700px -160px; }
+.emoji-1F62E { background-position: -700px -180px; }
+.emoji-1F62F { background-position: -700px -200px; }
+.emoji-1F630 { background-position: -700px -220px; }
+.emoji-1F631 { background-position: -700px -240px; }
+.emoji-1F632 { background-position: -700px -260px; }
+.emoji-1F633 { background-position: -700px -280px; }
+.emoji-1F634 { background-position: -700px -300px; }
+.emoji-1F635 { background-position: -700px -320px; }
+.emoji-1F636 { background-position: -700px -340px; }
+.emoji-1F637 { background-position: -700px -360px; }
+.emoji-1F638 { background-position: -700px -380px; }
+.emoji-1F639 { background-position: -700px -400px; }
+.emoji-1F63A { background-position: -700px -420px; }
+.emoji-1F63B { background-position: -700px -440px; }
+.emoji-1F63C { background-position: -700px -460px; }
+.emoji-1F63D { background-position: -700px -480px; }
+.emoji-1F63E { background-position: -700px -500px; }
+.emoji-1F63F { background-position: -700px -520px; }
+.emoji-1F640 { background-position: -700px -540px; }
+.emoji-1F641 { background-position: -700px -560px; }
+.emoji-1F642 { background-position: -700px -580px; }
+.emoji-1F643 { background-position: -700px -600px; }
+.emoji-1F644 { background-position: -700px -620px; }
+.emoji-1F645 { background-position: -700px -640px; }
+.emoji-1F645-1F3FB { background-position: -700px -660px; }
+.emoji-1F645-1F3FC { background-position: -700px -680px; }
+.emoji-1F645-1F3FD { background-position: 0 -700px; }
+.emoji-1F645-1F3FE { background-position: -20px -700px; }
+.emoji-1F645-1F3FF { background-position: -40px -700px; }
+.emoji-1F646 { background-position: -60px -700px; }
+.emoji-1F646-1F3FB { background-position: -80px -700px; }
+.emoji-1F646-1F3FC { background-position: -100px -700px; }
+.emoji-1F646-1F3FD { background-position: -120px -700px; }
+.emoji-1F646-1F3FE { background-position: -140px -700px; }
+.emoji-1F646-1F3FF { background-position: -160px -700px; }
+.emoji-1F647 { background-position: -180px -700px; }
+.emoji-1F647-1F3FB { background-position: -200px -700px; }
+.emoji-1F647-1F3FC { background-position: -220px -700px; }
+.emoji-1F647-1F3FD { background-position: -240px -700px; }
+.emoji-1F647-1F3FE { background-position: -260px -700px; }
+.emoji-1F647-1F3FF { background-position: -280px -700px; }
+.emoji-1F648 { background-position: -300px -700px; }
+.emoji-1F649 { background-position: -320px -700px; }
+.emoji-1F64A { background-position: -340px -700px; }
+.emoji-1F64B { background-position: -360px -700px; }
+.emoji-1F64B-1F3FB { background-position: -380px -700px; }
+.emoji-1F64B-1F3FC { background-position: -400px -700px; }
+.emoji-1F64B-1F3FD { background-position: -420px -700px; }
+.emoji-1F64B-1F3FE { background-position: -440px -700px; }
+.emoji-1F64B-1F3FF { background-position: -460px -700px; }
+.emoji-1F64C { background-position: -480px -700px; }
+.emoji-1F64C-1F3FB { background-position: -500px -700px; }
+.emoji-1F64C-1F3FC { background-position: -520px -700px; }
+.emoji-1F64C-1F3FD { background-position: -540px -700px; }
+.emoji-1F64C-1F3FE { background-position: -560px -700px; }
+.emoji-1F64C-1F3FF { background-position: -580px -700px; }
+.emoji-1F64D { background-position: -600px -700px; }
+.emoji-1F64D-1F3FB { background-position: -620px -700px; }
+.emoji-1F64D-1F3FC { background-position: -640px -700px; }
+.emoji-1F64D-1F3FD { background-position: -660px -700px; }
+.emoji-1F64D-1F3FE { background-position: -680px -700px; }
+.emoji-1F64D-1F3FF { background-position: -700px -700px; }
+.emoji-1F64E { background-position: -720px 0; }
+.emoji-1F64E-1F3FB { background-position: -720px -20px; }
+.emoji-1F64E-1F3FC { background-position: -720px -40px; }
+.emoji-1F64E-1F3FD { background-position: -720px -60px; }
+.emoji-1F64E-1F3FE { background-position: -720px -80px; }
+.emoji-1F64E-1F3FF { background-position: -720px -100px; }
+.emoji-1F64F { background-position: -720px -120px; }
+.emoji-1F64F-1F3FB { background-position: -720px -140px; }
+.emoji-1F64F-1F3FC { background-position: -720px -160px; }
+.emoji-1F64F-1F3FD { background-position: -720px -180px; }
+.emoji-1F64F-1F3FE { background-position: -720px -200px; }
+.emoji-1F64F-1F3FF { background-position: -720px -220px; }
+.emoji-1F680 { background-position: -720px -240px; }
+.emoji-1F681 { background-position: -720px -260px; }
+.emoji-1F682 { background-position: -720px -280px; }
+.emoji-1F683 { background-position: -720px -300px; }
+.emoji-1F684 { background-position: -720px -320px; }
+.emoji-1F685 { background-position: -720px -340px; }
+.emoji-1F686 { background-position: -720px -360px; }
+.emoji-1F687 { background-position: -720px -380px; }
+.emoji-1F688 { background-position: -720px -400px; }
+.emoji-1F689 { background-position: -720px -420px; }
+.emoji-1F68A { background-position: -720px -440px; }
+.emoji-1F68B { background-position: -720px -460px; }
+.emoji-1F68C { background-position: -720px -480px; }
+.emoji-1F68D { background-position: -720px -500px; }
+.emoji-1F68E { background-position: -720px -520px; }
+.emoji-1F68F { background-position: -720px -540px; }
+.emoji-1F690 { background-position: -720px -560px; }
+.emoji-1F691 { background-position: -720px -580px; }
+.emoji-1F692 { background-position: -720px -600px; }
+.emoji-1F693 { background-position: -720px -620px; }
+.emoji-1F694 { background-position: -720px -640px; }
+.emoji-1F695 { background-position: -720px -660px; }
+.emoji-1F696 { background-position: -720px -680px; }
+.emoji-1F697 { background-position: -720px -700px; }
+.emoji-1F698 { background-position: 0 -720px; }
+.emoji-1F699 { background-position: -20px -720px; }
+.emoji-1F69A { background-position: -40px -720px; }
+.emoji-1F69B { background-position: -60px -720px; }
+.emoji-1F69C { background-position: -80px -720px; }
+.emoji-1F69D { background-position: -100px -720px; }
+.emoji-1F69E { background-position: -120px -720px; }
+.emoji-1F69F { background-position: -140px -720px; }
+.emoji-1F6A0 { background-position: -160px -720px; }
+.emoji-1F6A1 { background-position: -180px -720px; }
+.emoji-1F6A2 { background-position: -200px -720px; }
+.emoji-1F6A3 { background-position: -220px -720px; }
+.emoji-1F6A3-1F3FB { background-position: -240px -720px; }
+.emoji-1F6A3-1F3FC { background-position: -260px -720px; }
+.emoji-1F6A3-1F3FD { background-position: -280px -720px; }
+.emoji-1F6A3-1F3FE { background-position: -300px -720px; }
+.emoji-1F6A3-1F3FF { background-position: -320px -720px; }
+.emoji-1F6A4 { background-position: -340px -720px; }
+.emoji-1F6A5 { background-position: -360px -720px; }
+.emoji-1F6A6 { background-position: -380px -720px; }
+.emoji-1F6A7 { background-position: -400px -720px; }
+.emoji-1F6A8 { background-position: -420px -720px; }
+.emoji-1F6A9 { background-position: -440px -720px; }
+.emoji-1F6AA { background-position: -460px -720px; }
+.emoji-1F6AB { background-position: -480px -720px; }
+.emoji-1F6AC { background-position: -500px -720px; }
+.emoji-1F6AD { background-position: -520px -720px; }
+.emoji-1F6AE { background-position: -540px -720px; }
+.emoji-1F6AF { background-position: -560px -720px; }
+.emoji-1F6B0 { background-position: -580px -720px; }
+.emoji-1F6B1 { background-position: -600px -720px; }
+.emoji-1F6B2 { background-position: -620px -720px; }
+.emoji-1F6B3 { background-position: -640px -720px; }
+.emoji-1F6B4 { background-position: -660px -720px; }
+.emoji-1F6B4-1F3FB { background-position: -680px -720px; }
+.emoji-1F6B4-1F3FC { background-position: -700px -720px; }
+.emoji-1F6B4-1F3FD { background-position: -720px -720px; }
+.emoji-1F6B4-1F3FE { background-position: -740px 0; }
+.emoji-1F6B4-1F3FF { background-position: -740px -20px; }
+.emoji-1F6B5 { background-position: -740px -40px; }
+.emoji-1F6B5-1F3FB { background-position: -740px -60px; }
+.emoji-1F6B5-1F3FC { background-position: -740px -80px; }
+.emoji-1F6B5-1F3FD { background-position: -740px -100px; }
+.emoji-1F6B5-1F3FE { background-position: -740px -120px; }
+.emoji-1F6B5-1F3FF { background-position: -740px -140px; }
+.emoji-1F6B6 { background-position: -740px -160px; }
+.emoji-1F6B6-1F3FB { background-position: -740px -180px; }
+.emoji-1F6B6-1F3FC { background-position: -740px -200px; }
+.emoji-1F6B6-1F3FD { background-position: -740px -220px; }
+.emoji-1F6B6-1F3FE { background-position: -740px -240px; }
+.emoji-1F6B6-1F3FF { background-position: -740px -260px; }
+.emoji-1F6B7 { background-position: -740px -280px; }
+.emoji-1F6B8 { background-position: -740px -300px; }
+.emoji-1F6B9 { background-position: -740px -320px; }
+.emoji-1F6BA { background-position: -740px -340px; }
+.emoji-1F6BB { background-position: -740px -360px; }
+.emoji-1F6BC { background-position: -740px -380px; }
+.emoji-1F6BD { background-position: -740px -400px; }
+.emoji-1F6BE { background-position: -740px -420px; }
+.emoji-1F6BF { background-position: -740px -440px; }
+.emoji-1F6C0 { background-position: -740px -460px; }
+.emoji-1F6C0-1F3FB { background-position: -740px -480px; }
+.emoji-1F6C0-1F3FC { background-position: -740px -500px; }
+.emoji-1F6C0-1F3FD { background-position: -740px -520px; }
+.emoji-1F6C0-1F3FE { background-position: -740px -540px; }
+.emoji-1F6C0-1F3FF { background-position: -740px -560px; }
+.emoji-1F6C1 { background-position: -740px -580px; }
+.emoji-1F6C2 { background-position: -740px -600px; }
+.emoji-1F6C3 { background-position: -740px -620px; }
+.emoji-1F6C4 { background-position: -740px -640px; }
+.emoji-1F6C5 { background-position: -740px -660px; }
+.emoji-1F6CB { background-position: -740px -680px; }
+.emoji-1F6CC { background-position: -740px -700px; }
+.emoji-1F6CD { background-position: -740px -720px; }
+.emoji-1F6CE { background-position: 0 -740px; }
+.emoji-1F6CF { background-position: -20px -740px; }
+.emoji-1F6D0 { background-position: -40px -740px; }
+.emoji-1F6D1 { background-position: -60px -740px; }
+.emoji-1F6D2 { background-position: -80px -740px; }
+.emoji-1F6E0 { background-position: -100px -740px; }
+.emoji-1F6E1 { background-position: -120px -740px; }
+.emoji-1F6E2 { background-position: -140px -740px; }
+.emoji-1F6E3 { background-position: -160px -740px; }
+.emoji-1F6E4 { background-position: -180px -740px; }
+.emoji-1F6E5 { background-position: -200px -740px; }
+.emoji-1F6E9 { background-position: -220px -740px; }
+.emoji-1F6EB { background-position: -240px -740px; }
+.emoji-1F6EC { background-position: -260px -740px; }
+.emoji-1F6F0 { background-position: -280px -740px; }
+.emoji-1F6F3 { background-position: -300px -740px; }
+.emoji-1F6F4 { background-position: -320px -740px; }
+.emoji-1F6F5 { background-position: -340px -740px; }
+.emoji-1F6F6 { background-position: -360px -740px; }
+.emoji-1F910 { background-position: -380px -740px; }
+.emoji-1F911 { background-position: -400px -740px; }
+.emoji-1F912 { background-position: -420px -740px; }
+.emoji-1F913 { background-position: -440px -740px; }
+.emoji-1F914 { background-position: -460px -740px; }
+.emoji-1F915 { background-position: -480px -740px; }
+.emoji-1F916 { background-position: -500px -740px; }
+.emoji-1F917 { background-position: -520px -740px; }
+.emoji-1F918 { background-position: -540px -740px; }
+.emoji-1F918-1F3FB { background-position: -560px -740px; }
+.emoji-1F918-1F3FC { background-position: -580px -740px; }
+.emoji-1F918-1F3FD { background-position: -600px -740px; }
+.emoji-1F918-1F3FE { background-position: -620px -740px; }
+.emoji-1F918-1F3FF { background-position: -640px -740px; }
+.emoji-1F919 { background-position: -660px -740px; }
+.emoji-1F919-1F3FB { background-position: -680px -740px; }
+.emoji-1F919-1F3FC { background-position: -700px -740px; }
+.emoji-1F919-1F3FD { background-position: -720px -740px; }
+.emoji-1F919-1F3FE { background-position: -740px -740px; }
+.emoji-1F919-1F3FF { background-position: -760px 0; }
+.emoji-1F91A { background-position: -760px -20px; }
+.emoji-1F91A-1F3FB { background-position: -760px -40px; }
+.emoji-1F91A-1F3FC { background-position: -760px -60px; }
+.emoji-1F91A-1F3FD { background-position: -760px -80px; }
+.emoji-1F91A-1F3FE { background-position: -760px -100px; }
+.emoji-1F91A-1F3FF { background-position: -760px -120px; }
+.emoji-1F91B { background-position: -760px -140px; }
+.emoji-1F91B-1F3FB { background-position: -760px -160px; }
+.emoji-1F91B-1F3FC { background-position: -760px -180px; }
+.emoji-1F91B-1F3FD { background-position: -760px -200px; }
+.emoji-1F91B-1F3FE { background-position: -760px -220px; }
+.emoji-1F91B-1F3FF { background-position: -760px -240px; }
+.emoji-1F91C { background-position: -760px -260px; }
+.emoji-1F91C-1F3FB { background-position: -760px -280px; }
+.emoji-1F91C-1F3FC { background-position: -760px -300px; }
+.emoji-1F91C-1F3FD { background-position: -760px -320px; }
+.emoji-1F91C-1F3FE { background-position: -760px -340px; }
+.emoji-1F91C-1F3FF { background-position: -760px -360px; }
+.emoji-1F91D { background-position: -760px -380px; }
+.emoji-1F91D-1F3FB { background-position: -760px -400px; }
+.emoji-1F91D-1F3FC { background-position: -760px -420px; }
+.emoji-1F91D-1F3FD { background-position: -760px -440px; }
+.emoji-1F91D-1F3FE { background-position: -760px -460px; }
+.emoji-1F91D-1F3FF { background-position: -760px -480px; }
+.emoji-1F91E { background-position: -760px -500px; }
+.emoji-1F91E-1F3FB { background-position: -760px -520px; }
+.emoji-1F91E-1F3FC { background-position: -760px -540px; }
+.emoji-1F91E-1F3FD { background-position: -760px -560px; }
+.emoji-1F91E-1F3FE { background-position: -760px -580px; }
+.emoji-1F91E-1F3FF { background-position: -760px -600px; }
+.emoji-1F920 { background-position: -760px -620px; }
+.emoji-1F921 { background-position: -760px -640px; }
+.emoji-1F922 { background-position: -760px -660px; }
+.emoji-1F923 { background-position: -760px -680px; }
+.emoji-1F924 { background-position: -760px -700px; }
+.emoji-1F925 { background-position: -760px -720px; }
+.emoji-1F926 { background-position: -760px -740px; }
+.emoji-1F926-1F3FB { background-position: 0 -760px; }
+.emoji-1F926-1F3FC { background-position: -20px -760px; }
+.emoji-1F926-1F3FD { background-position: -40px -760px; }
+.emoji-1F926-1F3FE { background-position: -60px -760px; }
+.emoji-1F926-1F3FF { background-position: -80px -760px; }
+.emoji-1F927 { background-position: -100px -760px; }
+.emoji-1F930 { background-position: -120px -760px; }
+.emoji-1F930-1F3FB { background-position: -140px -760px; }
+.emoji-1F930-1F3FC { background-position: -160px -760px; }
+.emoji-1F930-1F3FD { background-position: -180px -760px; }
+.emoji-1F930-1F3FE { background-position: -200px -760px; }
+.emoji-1F930-1F3FF { background-position: -220px -760px; }
+.emoji-1F933 { background-position: -240px -760px; }
+.emoji-1F933-1F3FB { background-position: -260px -760px; }
+.emoji-1F933-1F3FC { background-position: -280px -760px; }
+.emoji-1F933-1F3FD { background-position: -300px -760px; }
+.emoji-1F933-1F3FE { background-position: -320px -760px; }
+.emoji-1F933-1F3FF { background-position: -340px -760px; }
+.emoji-1F934 { background-position: -360px -760px; }
+.emoji-1F934-1F3FB { background-position: -380px -760px; }
+.emoji-1F934-1F3FC { background-position: -400px -760px; }
+.emoji-1F934-1F3FD { background-position: -420px -760px; }
+.emoji-1F934-1F3FE { background-position: -440px -760px; }
+.emoji-1F934-1F3FF { background-position: -460px -760px; }
+.emoji-1F935 { background-position: -480px -760px; }
+.emoji-1F935-1F3FB { background-position: -500px -760px; }
+.emoji-1F935-1F3FC { background-position: -520px -760px; }
+.emoji-1F935-1F3FD { background-position: -540px -760px; }
+.emoji-1F935-1F3FE { background-position: -560px -760px; }
+.emoji-1F935-1F3FF { background-position: -580px -760px; }
+.emoji-1F936 { background-position: -600px -760px; }
+.emoji-1F936-1F3FB { background-position: -620px -760px; }
+.emoji-1F936-1F3FC { background-position: -640px -760px; }
+.emoji-1F936-1F3FD { background-position: -660px -760px; }
+.emoji-1F936-1F3FE { background-position: -680px -760px; }
+.emoji-1F936-1F3FF { background-position: -700px -760px; }
+.emoji-1F937 { background-position: -720px -760px; }
+.emoji-1F937-1F3FB { background-position: -740px -760px; }
+.emoji-1F937-1F3FC { background-position: -760px -760px; }
+.emoji-1F937-1F3FD { background-position: -780px 0; }
+.emoji-1F937-1F3FE { background-position: -780px -20px; }
+.emoji-1F937-1F3FF { background-position: -780px -40px; }
+.emoji-1F938 { background-position: -780px -60px; }
+.emoji-1F938-1F3FB { background-position: -780px -80px; }
+.emoji-1F938-1F3FC { background-position: -780px -100px; }
+.emoji-1F938-1F3FD { background-position: -780px -120px; }
+.emoji-1F938-1F3FE { background-position: -780px -140px; }
+.emoji-1F938-1F3FF { background-position: -780px -160px; }
+.emoji-1F939 { background-position: -780px -180px; }
+.emoji-1F939-1F3FB { background-position: -780px -200px; }
+.emoji-1F939-1F3FC { background-position: -780px -220px; }
+.emoji-1F939-1F3FD { background-position: -780px -240px; }
+.emoji-1F939-1F3FE { background-position: -780px -260px; }
+.emoji-1F939-1F3FF { background-position: -780px -280px; }
+.emoji-1F93A { background-position: -780px -300px; }
+.emoji-1F93C { background-position: -780px -320px; }
+.emoji-1F93C-1F3FB { background-position: -780px -340px; }
+.emoji-1F93C-1F3FC { background-position: -780px -360px; }
+.emoji-1F93C-1F3FD { background-position: -780px -380px; }
+.emoji-1F93C-1F3FE { background-position: -780px -400px; }
+.emoji-1F93C-1F3FF { background-position: -780px -420px; }
+.emoji-1F93D { background-position: -780px -440px; }
+.emoji-1F93D-1F3FB { background-position: -780px -460px; }
+.emoji-1F93D-1F3FC { background-position: -780px -480px; }
+.emoji-1F93D-1F3FD { background-position: -780px -500px; }
+.emoji-1F93D-1F3FE { background-position: -780px -520px; }
+.emoji-1F93D-1F3FF { background-position: -780px -540px; }
+.emoji-1F93E { background-position: -780px -560px; }
+.emoji-1F93E-1F3FB { background-position: -780px -580px; }
+.emoji-1F93E-1F3FC { background-position: -780px -600px; }
+.emoji-1F93E-1F3FD { background-position: -780px -620px; }
+.emoji-1F93E-1F3FE { background-position: -780px -640px; }
+.emoji-1F93E-1F3FF { background-position: -780px -660px; }
+.emoji-1F940 { background-position: -780px -680px; }
+.emoji-1F941 { background-position: -780px -700px; }
+.emoji-1F942 { background-position: -780px -720px; }
+.emoji-1F943 { background-position: -780px -740px; }
+.emoji-1F944 { background-position: -780px -760px; }
+.emoji-1F945 { background-position: 0 -780px; }
+.emoji-1F947 { background-position: -20px -780px; }
+.emoji-1F948 { background-position: -40px -780px; }
+.emoji-1F949 { background-position: -60px -780px; }
+.emoji-1F94A { background-position: -80px -780px; }
+.emoji-1F94B { background-position: -100px -780px; }
+.emoji-1F950 { background-position: -120px -780px; }
+.emoji-1F951 { background-position: -140px -780px; }
+.emoji-1F952 { background-position: -160px -780px; }
+.emoji-1F953 { background-position: -180px -780px; }
+.emoji-1F954 { background-position: -200px -780px; }
+.emoji-1F955 { background-position: -220px -780px; }
+.emoji-1F956 { background-position: -240px -780px; }
+.emoji-1F957 { background-position: -260px -780px; }
+.emoji-1F958 { background-position: -280px -780px; }
+.emoji-1F959 { background-position: -300px -780px; }
+.emoji-1F95A { background-position: -320px -780px; }
+.emoji-1F95B { background-position: -340px -780px; }
+.emoji-1F95C { background-position: -360px -780px; }
+.emoji-1F95D { background-position: -380px -780px; }
+.emoji-1F95E { background-position: -400px -780px; }
+.emoji-1F980 { background-position: -420px -780px; }
+.emoji-1F981 { background-position: -440px -780px; }
+.emoji-1F982 { background-position: -460px -780px; }
+.emoji-1F983 { background-position: -480px -780px; }
+.emoji-1F984 { background-position: -500px -780px; }
+.emoji-1F985 { background-position: -520px -780px; }
+.emoji-1F986 { background-position: -540px -780px; }
+.emoji-1F987 { background-position: -560px -780px; }
+.emoji-1F988 { background-position: -580px -780px; }
+.emoji-1F989 { background-position: -600px -780px; }
+.emoji-1F98A { background-position: -620px -780px; }
+.emoji-1F98B { background-position: -640px -780px; }
+.emoji-1F98C { background-position: -660px -780px; }
+.emoji-1F98D { background-position: -680px -780px; }
+.emoji-1F98E { background-position: -700px -780px; }
+.emoji-1F98F { background-position: -720px -780px; }
+.emoji-1F990 { background-position: -740px -780px; }
+.emoji-1F991 { background-position: -760px -780px; }
+.emoji-1F9C0 { background-position: -780px -780px; }
+.emoji-203C { background-position: -800px 0; }
+.emoji-2049 { background-position: -800px -20px; }
+.emoji-2122 { background-position: -800px -40px; }
+.emoji-2139 { background-position: -800px -60px; }
+.emoji-2194 { background-position: -800px -80px; }
+.emoji-2195 { background-position: -800px -100px; }
+.emoji-2196 { background-position: -800px -120px; }
+.emoji-2197 { background-position: -800px -140px; }
+.emoji-2198 { background-position: -800px -160px; }
+.emoji-2199 { background-position: -800px -180px; }
+.emoji-21A9 { background-position: -800px -200px; }
+.emoji-21AA { background-position: -800px -220px; }
+.emoji-231A { background-position: -800px -240px; }
+.emoji-231B { background-position: -800px -260px; }
+.emoji-2328 { background-position: -800px -280px; }
+.emoji-23CF { background-position: -800px -300px; }
+.emoji-23E9 { background-position: -800px -320px; }
+.emoji-23EA { background-position: -800px -340px; }
+.emoji-23EB { background-position: -800px -360px; }
+.emoji-23EC { background-position: -800px -380px; }
+.emoji-23ED { background-position: -800px -400px; }
+.emoji-23EE { background-position: -800px -420px; }
+.emoji-23EF { background-position: -800px -440px; }
+.emoji-23F0 { background-position: -800px -460px; }
+.emoji-23F1 { background-position: -800px -480px; }
+.emoji-23F2 { background-position: -800px -500px; }
+.emoji-23F3 { background-position: -800px -520px; }
+.emoji-23F8 { background-position: -800px -540px; }
+.emoji-23F9 { background-position: -800px -560px; }
+.emoji-23FA { background-position: -800px -580px; }
+.emoji-24C2 { background-position: -800px -600px; }
+.emoji-25AA { background-position: -800px -620px; }
+.emoji-25AB { background-position: -800px -640px; }
+.emoji-25B6 { background-position: -800px -660px; }
+.emoji-25C0 { background-position: -800px -680px; }
+.emoji-25FB { background-position: -800px -700px; }
+.emoji-25FC { background-position: -800px -720px; }
+.emoji-25FD { background-position: -800px -740px; }
+.emoji-25FE { background-position: -800px -760px; }
+.emoji-2600 { background-position: -800px -780px; }
+.emoji-2601 { background-position: 0 -800px; }
+.emoji-2602 { background-position: -20px -800px; }
+.emoji-2603 { background-position: -40px -800px; }
+.emoji-2604 { background-position: -60px -800px; }
+.emoji-260E { background-position: -80px -800px; }
+.emoji-2611 { background-position: -100px -800px; }
+.emoji-2614 { background-position: -120px -800px; }
+.emoji-2615 { background-position: -140px -800px; }
+.emoji-2618 { background-position: -160px -800px; }
+.emoji-261D { background-position: -180px -800px; }
+.emoji-261D-1F3FB { background-position: -200px -800px; }
+.emoji-261D-1F3FC { background-position: -220px -800px; }
+.emoji-261D-1F3FD { background-position: -240px -800px; }
+.emoji-261D-1F3FE { background-position: -260px -800px; }
+.emoji-261D-1F3FF { background-position: -280px -800px; }
+.emoji-2620 { background-position: -300px -800px; }
+.emoji-2622 { background-position: -320px -800px; }
+.emoji-2623 { background-position: -340px -800px; }
+.emoji-2626 { background-position: -360px -800px; }
+.emoji-262A { background-position: -380px -800px; }
+.emoji-262E { background-position: -400px -800px; }
+.emoji-262F { background-position: -420px -800px; }
+.emoji-2638 { background-position: -440px -800px; }
+.emoji-2639 { background-position: -460px -800px; }
+.emoji-263A { background-position: -480px -800px; }
+.emoji-2648 { background-position: -500px -800px; }
+.emoji-2649 { background-position: -520px -800px; }
+.emoji-264A { background-position: -540px -800px; }
+.emoji-264B { background-position: -560px -800px; }
+.emoji-264C { background-position: -580px -800px; }
+.emoji-264D { background-position: -600px -800px; }
+.emoji-264E { background-position: -620px -800px; }
+.emoji-264F { background-position: -640px -800px; }
+.emoji-2650 { background-position: -660px -800px; }
+.emoji-2651 { background-position: -680px -800px; }
+.emoji-2652 { background-position: -700px -800px; }
+.emoji-2653 { background-position: -720px -800px; }
+.emoji-2660 { background-position: -740px -800px; }
+.emoji-2663 { background-position: -760px -800px; }
+.emoji-2665 { background-position: -780px -800px; }
+.emoji-2666 { background-position: -800px -800px; }
+.emoji-2668 { background-position: -820px 0; }
+.emoji-267B { background-position: -820px -20px; }
+.emoji-267F { background-position: -820px -40px; }
+.emoji-2692 { background-position: -820px -60px; }
+.emoji-2693 { background-position: -820px -80px; }
+.emoji-2694 { background-position: -820px -100px; }
+.emoji-2696 { background-position: -820px -120px; }
+.emoji-2697 { background-position: -820px -140px; }
+.emoji-2699 { background-position: -820px -160px; }
+.emoji-269B { background-position: -820px -180px; }
+.emoji-269C { background-position: -820px -200px; }
+.emoji-26A0 { background-position: -820px -220px; }
+.emoji-26A1 { background-position: -820px -240px; }
+.emoji-26AA { background-position: -820px -260px; }
+.emoji-26AB { background-position: -820px -280px; }
+.emoji-26B0 { background-position: -820px -300px; }
+.emoji-26B1 { background-position: -820px -320px; }
+.emoji-26BD { background-position: -820px -340px; }
+.emoji-26BE { background-position: -820px -360px; }
+.emoji-26C4 { background-position: -820px -380px; }
+.emoji-26C5 { background-position: -820px -400px; }
+.emoji-26C8 { background-position: -820px -420px; }
+.emoji-26CE { background-position: -820px -440px; }
+.emoji-26CF { background-position: -820px -460px; }
+.emoji-26D1 { background-position: -820px -480px; }
+.emoji-26D3 { background-position: -820px -500px; }
+.emoji-26D4 { background-position: -820px -520px; }
+.emoji-26E9 { background-position: -820px -540px; }
+.emoji-26EA { background-position: -820px -560px; }
+.emoji-26F0 { background-position: -820px -580px; }
+.emoji-26F1 { background-position: -820px -600px; }
+.emoji-26F2 { background-position: -820px -620px; }
+.emoji-26F3 { background-position: -820px -640px; }
+.emoji-26F4 { background-position: -820px -660px; }
+.emoji-26F5 { background-position: -820px -680px; }
+.emoji-26F7 { background-position: -820px -700px; }
+.emoji-26F8 { background-position: -820px -720px; }
+.emoji-26F9 { background-position: -820px -740px; }
+.emoji-26F9-1F3FB { background-position: -820px -760px; }
+.emoji-26F9-1F3FC { background-position: -820px -780px; }
+.emoji-26F9-1F3FD { background-position: -820px -800px; }
+.emoji-26F9-1F3FE { background-position: 0 -820px; }
+.emoji-26F9-1F3FF { background-position: -20px -820px; }
+.emoji-26FA { background-position: -40px -820px; }
+.emoji-26FD { background-position: -60px -820px; }
+.emoji-2702 { background-position: -80px -820px; }
+.emoji-2705 { background-position: -100px -820px; }
+.emoji-2708 { background-position: -120px -820px; }
+.emoji-2709 { background-position: -140px -820px; }
+.emoji-270A { background-position: -160px -820px; }
+.emoji-270A-1F3FB { background-position: -180px -820px; }
+.emoji-270A-1F3FC { background-position: -200px -820px; }
+.emoji-270A-1F3FD { background-position: -220px -820px; }
+.emoji-270A-1F3FE { background-position: -240px -820px; }
+.emoji-270A-1F3FF { background-position: -260px -820px; }
+.emoji-270B { background-position: -280px -820px; }
+.emoji-270B-1F3FB { background-position: -300px -820px; }
+.emoji-270B-1F3FC { background-position: -320px -820px; }
+.emoji-270B-1F3FD { background-position: -340px -820px; }
+.emoji-270B-1F3FE { background-position: -360px -820px; }
+.emoji-270B-1F3FF { background-position: -380px -820px; }
+.emoji-270C { background-position: -400px -820px; }
+.emoji-270C-1F3FB { background-position: -420px -820px; }
+.emoji-270C-1F3FC { background-position: -440px -820px; }
+.emoji-270C-1F3FD { background-position: -460px -820px; }
+.emoji-270C-1F3FE { background-position: -480px -820px; }
+.emoji-270C-1F3FF { background-position: -500px -820px; }
+.emoji-270D { background-position: -520px -820px; }
+.emoji-270D-1F3FB { background-position: -540px -820px; }
+.emoji-270D-1F3FC { background-position: -560px -820px; }
+.emoji-270D-1F3FD { background-position: -580px -820px; }
+.emoji-270D-1F3FE { background-position: -600px -820px; }
+.emoji-270D-1F3FF { background-position: -620px -820px; }
+.emoji-270F { background-position: -640px -820px; }
+.emoji-2712 { background-position: -660px -820px; }
+.emoji-2714 { background-position: -680px -820px; }
+.emoji-2716 { background-position: -700px -820px; }
+.emoji-271D { background-position: -720px -820px; }
+.emoji-2721 { background-position: -740px -820px; }
+.emoji-2728 { background-position: -760px -820px; }
+.emoji-2733 { background-position: -780px -820px; }
+.emoji-2734 { background-position: -800px -820px; }
+.emoji-2744 { background-position: -820px -820px; }
+.emoji-2747 { background-position: -840px 0; }
+.emoji-274C { background-position: -840px -20px; }
+.emoji-274E { background-position: -840px -40px; }
+.emoji-2753 { background-position: -840px -60px; }
+.emoji-2754 { background-position: -840px -80px; }
+.emoji-2755 { background-position: -840px -100px; }
+.emoji-2757 { background-position: -840px -120px; }
+.emoji-2763 { background-position: -840px -140px; }
+.emoji-2764 { background-position: -840px -160px; }
+.emoji-2795 { background-position: -840px -180px; }
+.emoji-2796 { background-position: -840px -200px; }
+.emoji-2797 { background-position: -840px -220px; }
+.emoji-27A1 { background-position: -840px -240px; }
+.emoji-27B0 { background-position: -840px -260px; }
+.emoji-27BF { background-position: -840px -280px; }
+.emoji-2934 { background-position: -840px -300px; }
+.emoji-2935 { background-position: -840px -320px; }
+.emoji-2B05 { background-position: -840px -340px; }
+.emoji-2B06 { background-position: -840px -360px; }
+.emoji-2B07 { background-position: -840px -380px; }
+.emoji-2B1B { background-position: -840px -400px; }
+.emoji-2B1C { background-position: -840px -420px; }
+.emoji-2B50 { background-position: -840px -440px; }
+.emoji-2B55 { background-position: -840px -460px; }
+.emoji-3030 { background-position: -840px -480px; }
+.emoji-303D { background-position: -840px -500px; }
+.emoji-3297 { background-position: -840px -520px; }
+.emoji-3299 { background-position: -840px -540px; }
 
 .emoji-icon {
   background-image: image-url('emoji.png');
@@ -1731,6 +1804,6 @@
          only screen and (min-resolution: 192dpi),
          only screen and (min-resolution: 2dppx) {
     background-image: image-url('emoji@2x.png');
-    background-size: 840px 820px;
+    background-size: 860px 840px;
   }
 }
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index a2145956eb58abd39d89dc7a2a74a23045f20669..5c336bb1c7e1b6403f0bee866d94365006b8d08a 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -176,3 +176,11 @@
     }
   }
 }
+
+// hide event scope (namespace + project) where it is not necessary
+.project-activity {
+  .event-scope {
+    display: none;
+  }
+}
+
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 542fa244689ac63dda5428540aac9667c3892a22..7a50bc9c8320f369f47da2728a1ff4d8f515e601 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -122,7 +122,8 @@
 
     button {
       float: right;
-      padding: 3px 5px;
+      padding: 1px 5px;
+      background-color: $gray-light;
     }
   }
 
@@ -268,7 +269,7 @@
   .issuable-header-btn {
     background: $gray-normal;
     border: 1px solid $border-gray-normal;
-    
+
     &:hover {
       background: $gray-dark;
       border: 1px solid $border-gray-dark;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0e4d8c140aa2d7a6cdc77d58022ca6e88f9af32f..dfe1e3075dadd45b2404d2db6f0bdbee4d006445 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -63,7 +63,7 @@ form.edit-issue {
 .merge-request,
 .issue {
   &.today {
-    background: #f8feef;
+    background: #f3fff2;
     border-color: #e1e8d5;
   }
 
@@ -78,6 +78,14 @@ form.edit-issue {
   }
 }
 
+.merge-request-ci-status {
+  svg {
+    margin-right: 4px;
+    position: relative;
+    top: 1px;
+  }
+}
+
 @media (max-width: $screen-xs-max) {
   .issue-btn-group {
     width: 100%;
@@ -91,3 +99,33 @@ form.edit-issue {
 .issue-form .select2-container {
   width: 250px !important;
 }
+
+.issues-footer {
+  padding-top: $gl-padding;
+  padding-bottom: 37px;
+}
+
+.issue-email-modal-btn {
+  padding: 0;
+  color: $gl-link-color;
+  background-color: transparent;
+  border: 0;
+  outline: 0;
+
+  &:hover {
+    text-decoration: underline;
+  }
+}
+
+.email-modal-input-group {
+  margin-bottom: 10px;
+
+  .form-control {
+    background-color: $white-light;
+  }
+
+  .btn {
+    background-color: $background-color;
+    border: 1px solid $border-gray-light;
+  }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 15c6c9f231aa06cecce6b713ac1186e722a8a749..0a661e529f0c96e364b64a0fcccbc364c9adda1e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -60,14 +60,25 @@
   .ci_widget {
     border-bottom: 1px solid #eef0f2;
 
-    i {
+    svg {
       margin-right: 4px;
+      position: relative;
+      top: 1px;
+      overflow: visible;
     }
 
     &.ci-success {
       color: $gl-success;
     }
 
+    &.ci-success_with_warnings {
+      color: $gl-success;
+
+      i {
+        color: $gl-warning;
+      }
+    }
+
     &.ci-skipped {
       background-color: #eee;
       color: #888;
@@ -196,6 +207,21 @@
 
     .merge-request-title {
       margin-bottom: 2px;
+
+      .ci-status-link {
+
+        svg {
+          height: 16px;
+          width: 16px;
+          position: relative;
+          top: 3px;
+        }
+
+        &:hover,
+        &:focus {
+          text-decoration: none;
+        }
+      }
     }
   }
 
@@ -270,7 +296,7 @@
 
     .item-title {
       @media (min-width: $screen-sm-min) {
-        width: 49%;
+        width: 45%;
       }
     }
 
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index ac8c02b59dcbe7a10085d50ec4617ac2f8c69a8b..a2b5437e5031080ed00835470d1967fd7d17bf70 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -91,34 +91,11 @@ ul.notes {
         // Reset ul style types since we're nested inside a ul already
         @include bulleted-list;
 
-        // On diffs code should wrap nicely and not overflow
-        code {
-          white-space: pre-wrap;
-        }
-
         ul.task-list {
           ul:not(.task-list) {
             padding-left: 1.3em;
           }
         }
-
-        hr {
-          // Darken 'whitesmoke' a bit to make it more visible in note bodies
-          border-color: darken(#f5f5f5, 8%);
-          margin: 10px 0;
-        }
-
-        code {
-          word-break: keep-all;
-        }
-
-        // Border around images in issue and MR comments.
-        img:not(.emoji) {
-          border: 1px solid $table-border-gray;
-          padding: 5px;
-          margin: 5px 0;
-          max-height: calc(100vh - 100px);
-        }
       }
     }
 
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a0334207c683fe4f933c8b3a454321c13070c749..21919fe4d73ff3d28d9c65c5dcf5e307cf7cbef3 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -1,7 +1,7 @@
 .pipelines {
   .stage {
-    max-width: 80px;
-    width: 80px;
+    max-width: 90px;
+    width: 90px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
@@ -18,6 +18,10 @@
   .btn {
     margin: 4px;
   }
+
+  .table.builds {
+    min-width: 1200px;
+  }
 }
 
 .content-list {
@@ -29,14 +33,27 @@
   }
 }
 
+.pipeline-holder {
+  width: 100%;
+  overflow: auto;
+}
+
 .table.builds {
-  min-width: 1100px;
+  min-width: 900px;
+
+  &.pipeline {
+    min-width: 650px;
+  }
 
   tr {
     th {
-      padding: 16px;
+      padding: 16px 8px;
       border: none;
     }
+
+    td {
+      padding: 10px 8px;
+    }
   }
 
   tbody {
@@ -45,6 +62,14 @@
 
   .commit-link {
 
+    .ci-status {
+
+      svg {
+        top: 1px;
+        margin-right: 0;
+      }
+    }
+
     a:hover {
       text-decoration: none;
     }
@@ -53,9 +78,8 @@
   .branch-commit {
 
     .branch-name {
-      margin-left: 8px;
       font-weight: bold;
-      max-width: 180px;
+      max-width: 150px;
       overflow: hidden;
       display: inline-block;
       white-space: nowrap;
@@ -64,10 +88,15 @@
     }
 
     svg {
-      margin: 0 6px;
       height: 14px;
-      width: auto;
+      width: 14px;
       vertical-align: middle;
+      fill: $table-text-gray;
+    }
+
+    .fa {
+      font-size: 12px;
+      color: $table-text-gray;
     }
 
     .commit-id {
@@ -77,7 +106,7 @@
 
     .commit-title {
       margin-top: 4px;
-      max-width: 320px;
+      max-width: 300px;
       overflow: hidden;
       white-space: nowrap;
       text-overflow: ellipsis;
@@ -100,6 +129,36 @@
     }
   }
 
+  .icon-container {
+    display: inline-block;
+    text-align: right;
+    width: 15px;
+
+    .fa {
+      position: relative;
+      right: 3px;
+    }
+
+    svg {
+      position: relative;
+      right: 1px;
+    }
+  }
+
+  .stage-cell {
+
+    svg {
+      height: 18px;
+      width: 18px;
+      vertical-align: middle;
+      overflow: visible;
+    }
+
+    .light {
+      width: 3px;
+    }
+  }
+
   .duration,
   .finished-at {
     color: $table-text-gray;
@@ -107,21 +166,19 @@
 
     .fa {
       font-size: 12px;
+      margin-right: 4px;
     }
 
     svg {
+      width: 12px;
       height: 12px;
-      width: auto;
       vertical-align: middle;
-    }
-
-    .fa,
-    svg {
-      margin-right: 5px;
+      margin-right: 4px;
     }
   }
 
   .pipeline-actions {
+    min-width: 140px;
 
     .btn {
       margin: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index ea9f7cf054050abea65ec987b99fc9d0bb891818..4409477916f42799e9d827aeefa914224661e994 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -129,6 +129,17 @@
       color: $layout-link-gray;
     }
 
+    svg {
+
+      path {
+        fill: $layout-link-gray;
+      }
+
+      use {
+        stroke: $layout-link-gray;
+      }
+    }
+
     .fa-caret-down {
       margin-left: 3px;
     }
@@ -322,18 +333,53 @@ a.deploy-project-label {
 }
 
 .fork-namespaces {
-  .fork-thumbnail {
-    text-align: center;
-    margin-bottom: $gl-padding;
-
-    .caption {
-      padding: $gl-padding 0;
-      min-height: 30px;
-    }
+  .row {
+    -webkit-flex-wrap: wrap;
+    display: -webkit-flex;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+
+    .fork-thumbnail {
+      @include border-radius($border-radius-base);
+      background-color: $white-light;
+      border: 1px solid $border-white-light;
+      height: 202px;
+      margin: $gl-padding;
+      text-align: center;
+      width: 169px;
+      &:hover, &.forked {
+        background-color: $row-hover;
+        border-color: $row-hover-border;
+      }
+      .no-avatar {
+        width: 100px;
+        height: 100px;
+        background-color: $gray-light;
+        border: 1px solid $gray-dark;
+        margin: 0 auto;
+        @include border-radius(50%);
+        i {
+          font-size: 100px;
+          color: $gray-dark;
+        }
+      }
+      a {
+        display: block;
+        width: 100%;
+        height: 100%;
+        padding-top: $gl-padding;
+        color: $gl-gray;
+        .caption {
+          min-height: 30px;
+          padding: $gl-padding 0;
+        }
+      }
 
-    img {
-      @include border-radius(50%);
-      max-width: 100px;
+      img {
+        @include border-radius(50%);
+        max-width: 100px;
+      }
     }
   }
 }
@@ -486,6 +532,11 @@ pre.light-well {
       > span {
         margin-left: 10px;
       }
+
+      svg {
+        position: relative;
+        top: 2px;
+      }
     }
   }
 
@@ -610,14 +661,28 @@ pre.light-well {
   }
 }
 
+.new_protected_branch {
+  .dropdown {
+    display: inline;
+    margin-left: 15px;
+  }
+
+  label {
+    min-width: 120px;
+  }
+}
+
 .protected-branches-list {
   a {
     color: $gl-gray;
-    font-weight: 600;
 
     &:hover {
       color: $gl-link-color;
     }
+
+    &.is-active {
+      font-weight: 600;
+    }
   }
 }
 
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index c6b053150be4cc3c92ee33ba241349816b6bb26a..587f2d9f3c13735d1692b23561b0b0e507403f05 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -15,7 +15,8 @@
       border-color: $gl-danger;
     }
 
-    &.ci-success {
+    &.ci-success,
+    &.ci-success_with_warnings {
       color: $gl-success;
       border-color: $gl-success;
     }
@@ -41,6 +42,15 @@
       color: $blue-normal;
       border-color: $blue-normal;
     }
+
+    svg {
+      height: 13px;
+      width: 13px;
+      position: relative;
+      top: 1px;
+      margin: 0 3px;
+      overflow: visible;
+    }
   }
 
   .ci-status-icon-success {
@@ -49,9 +59,12 @@
   .ci-status-icon-failed {
     color: $gl-danger;
   }
-  .ci-status-icon-pending {
+
+  .ci-status-icon-pending,
+  .ci-status-icon-success_with_warning {
     color: $gl-warning;
   }
+  
   .ci-status-icon-running {
     color: $blue-normal;
   }
@@ -62,3 +75,11 @@
     color: $gl-gray;
   }
 }
+
+.visible-xs-inline {
+  .ci-status-link {
+    position: relative;
+    top: 2px;
+    left: 5px;
+  }
+}
diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss
new file mode 100644
index 0000000000000000000000000000000000000000..24ebd3e7cfa940722d2c4254389489883cd8fca7
--- /dev/null
+++ b/app/assets/stylesheets/pages/tags.scss
@@ -0,0 +1,7 @@
+.tag-buttons {
+  line-height: 40px;
+
+  .btn:not(.dropdown-toggle) {
+    margin-left: 10px;
+  }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 42a20e9775fc045699d5729727ca6459083d5367..9da40fe2b09d5a6fac7b167defc56af175ee18ea 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -19,7 +19,7 @@
       border-top: 1px solid $table-border-gray;
 
       td, th {
-        line-height: 23px;
+        line-height: 21px;
       }
 
       &:hover {
@@ -58,6 +58,10 @@
 
     .tree_commit {
       max-width: 320px;
+
+      .str-truncated {
+        max-width: 100%;
+      }
     }
 
     .tree_time_ago {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 23ba83aba0e7c942745eb9d524f18c93503d13bb..9e1dc15de849c53075870720aab4668a9a5c1d56 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -64,6 +64,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
     params[:application_setting][:disabled_oauth_sign_in_sources] =
       AuthHelper.button_based_providers.map(&:to_s) -
       Array(enabled_oauth_sign_in_sources)
+    params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
 
     params.require(:application_setting).permit(
       :default_projects_limit,
@@ -83,7 +84,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
       :default_project_visibility,
       :default_snippet_visibility,
       :default_group_visibility,
-      :restricted_signup_domains_raw,
+      :domain_whitelist_raw,
+      :domain_blacklist_enabled,
+      :domain_blacklist_raw,
+      :domain_blacklist_file,
       :version_check_enabled,
       :admin_notification_email,
       :user_oauth_applications,
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 94b5aaa71d076b6f440357a96ec7290c987976d2..f3a88a8e6c8cd47e8b1143c2fbf14140b9996764 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -60,6 +60,6 @@ class Admin::GroupsController < Admin::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level)
+    params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled)
   end
 end
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a478176e138860b7d14e7b6b7aeae02304ac0dc6
--- /dev/null
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -0,0 +1,17 @@
+class Admin::RequestsProfilesController < Admin::ApplicationController
+  def index
+    @profile_token = Gitlab::RequestProfiler.profile_token
+    @profiles      = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
+  end
+
+  def show
+    clean_name = Rack::Utils.clean_path_info(params[:name])
+    profile    = Gitlab::RequestProfiler::Profile.find(clean_name)
+
+    if profile
+      render text: profile.content
+    else
+      redirect_to admin_requests_profiles_path, alert: 'Profile not found'
+    end
+  end
+end
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 461335883325e2c7b33b29c83114ce6ff880e5db..7c37f3155dac8f0435bd02ac297a505f0b1d15da 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -1,4 +1,6 @@
 class Admin::ServicesController < Admin::ApplicationController
+  include ServiceParams
+
   before_action :service, only: [:edit, :update]
 
   def index
@@ -13,7 +15,7 @@ class Admin::ServicesController < Admin::ApplicationController
   end
 
   def update
-    if service.update_attributes(application_services_params[:service])
+    if service.update_attributes(service_params[:service])
       redirect_to admin_application_settings_services_path,
         notice: 'Application settings saved successfully'
     else
@@ -37,15 +39,4 @@ class Admin::ServicesController < Admin::ApplicationController
   def service
     @service ||= Service.where(id: params[:id], template: true).first
   end
-
-  def application_services_params
-    application_services_params = params.permit(:id,
-      service: Projects::ServicesController::ALLOWED_PARAMS)
-    if application_services_params[:service].is_a?(Hash)
-      Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
-        application_services_params[:service].delete(param) if application_services_params[:service][param].blank? 
-      end
-    end
-    application_services_params
-  end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a1004d9bcea658d97d694bf2cde951c971fa6bf0..634d36a44671ed450597061fcbb3f6bd9f9903fa 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -243,42 +243,6 @@ class ApplicationController < ActionController::Base
     end
   end
 
-  def set_filters_params
-    set_default_sort
-
-    params[:scope] = 'all' if params[:scope].blank?
-    params[:state] = 'opened' if params[:state].blank?
-
-    @sort = params[:sort]
-    @filter_params = params.dup
-
-    if @project
-      @filter_params[:project_id] = @project.id
-    elsif @group
-      @filter_params[:group_id] = @group.id
-    else
-      # TODO: this filter ignore issues/mr created in public or
-      # internal repos where you are not a member. Enable this filter
-      # or improve current implementation to filter only issues you
-      # created or assigned or mentioned
-      # @filter_params[:authorized_only] = true
-    end
-
-    @filter_params
-  end
-
-  def get_issues_collection
-    set_filters_params
-    @issuable_finder = IssuesFinder.new(current_user, @filter_params)
-    @issuable_finder.execute
-  end
-
-  def get_merge_requests_collection
-    set_filters_params
-    @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params)
-    @issuable_finder.execute
-  end
-
   def import_sources_enabled?
     !current_application_settings.import_sources.empty?
   end
@@ -363,24 +327,4 @@ class ApplicationController < ActionController::Base
   def u2f_app_id
     request.base_url
   end
-
-  private
-
-  def set_default_sort
-    key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
-            'issuable_sort'
-          end
-
-    cookies[key]  = params[:sort] if key && params[:sort].present?
-    params[:sort] = cookies[key] if key
-    params[:sort] ||= 'id_desc'
-  end
-
-  def is_a_listing_page_for?(page_type)
-    controller_name, action_name = params.values_at(:controller, :action)
-
-    (controller_name == "projects/#{page_type}" && action_name == 'index') ||
-    (controller_name == 'groups' && action_name == page_type) ||
-    (controller_name == 'dashboard' && action_name == page_type)
-  end
 end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index c89678cf2d8350ddf3b12cd2fe994e5bd3e5c864..d828d163c280e1e40d7257a12594ab96b2300828 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController
   def users
     @users ||= User.none
     @users = @users.search(params[:search]) if params[:search].present?
+    @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
     @users = @users.active
     @users = @users.reorder(:name)
     @users = @users.page(params[:page])
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index dacb5679dd300fde4a034ba762960ee5a41610e7..f2b8f297bc27886a536478ba77aeb82304cfaa56 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -7,7 +7,8 @@ module CreatesCommit
     commit_params = @commit_params.merge(
       source_project: @project,
       source_branch: @ref,
-      target_branch: @target_branch
+      target_branch: @target_branch,
+      previous_path: @previous_path
     )
 
     result = service.new(@tree_edit_project, current_user, commit_params).execute
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c802922e0af83c1b80728fac462e7111861ffd2c
--- /dev/null
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -0,0 +1,79 @@
+module IssuableCollections
+  extend ActiveSupport::Concern
+  include SortingHelper
+
+  included do
+    helper_method :issues_finder
+    helper_method :merge_requests_finder
+  end
+
+  private
+
+  def issues_collection
+    issues_finder.execute
+  end
+
+  def merge_requests_collection
+    merge_requests_finder.execute
+  end
+
+  def issues_finder
+    @issues_finder ||= issuable_finder_for(IssuesFinder)
+  end
+
+  def merge_requests_finder
+    @merge_requests_finder ||= issuable_finder_for(MergeRequestsFinder)
+  end
+
+  def issuable_finder_for(finder_class)
+    finder_class.new(current_user, filter_params)
+  end
+
+  def filter_params
+    set_sort_order_from_cookie
+    set_default_scope
+    set_default_state
+
+    @filter_params = params.dup
+    @filter_params[:sort] ||= default_sort_order
+
+    @sort = @filter_params[:sort]
+
+    if @project
+      @filter_params[:project_id] = @project.id
+    elsif @group
+      @filter_params[:group_id] = @group.id
+    else
+      # TODO: this filter ignore issues/mr created in public or
+      # internal repos where you are not a member. Enable this filter
+      # or improve current implementation to filter only issues you
+      # created or assigned or mentioned
+      # @filter_params[:authorized_only] = true
+    end
+
+    @filter_params
+  end
+
+  def set_default_scope
+    params[:scope] = 'all' if params[:scope].blank?
+  end
+
+  def set_default_state
+    params[:state] = 'opened' if params[:state].blank?
+  end
+
+  def set_sort_order_from_cookie
+    key = 'issuable_sort'
+
+    cookies[key] = params[:sort] if params[:sort].present?
+    params[:sort] = cookies[key]
+  end
+
+  def default_sort_order
+    case params[:state]
+    when 'opened', 'all' then sort_value_recently_created
+    when 'merged', 'closed' then sort_value_recently_updated
+    else sort_value_recently_created
+    end
+  end
+end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index 4feabc32b1cd89520c11ba5df5a772fe24883c6d..b89fb94be6ea7f6df2185d72573d4ea333192e4d 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -1,12 +1,14 @@
 module IssuesAction
   extend ActiveSupport::Concern
+  include IssuableCollections
 
   def issues
-    @issues = get_issues_collection.non_archived
-    @issues = @issues.page(params[:page])
-    @issues = @issues.preload(:author, :project)
+    @label = issues_finder.labels.first
 
-    @label = @issuable_finder.labels.first
+    @issues = issues_collection
+              .non_archived
+              .preload(:author, :project)
+              .page(params[:page])
 
     respond_to do |format|
       format.html
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index 06a6b065e7e984eac743f9545d3701a288564397..a1b0eee37f91a5131e8002045c09d1f21c5ffff3 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -1,11 +1,13 @@
 module MergeRequestsAction
   extend ActiveSupport::Concern
+  include IssuableCollections
 
   def merge_requests
-    @merge_requests = get_merge_requests_collection.non_archived
-    @merge_requests = @merge_requests.page(params[:page])
-    @merge_requests = @merge_requests.preload(:author, :target_project)
+    @label = merge_requests_finder.labels.first
 
-    @label = @issuable_finder.labels.first
+    @merge_requests = merge_requests_collection
+                      .non_archived
+                      .preload(:author, :target_project)
+                      .page(params[:page])
   end
 end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
new file mode 100644
index 0000000000000000000000000000000000000000..471d15af9130aa1e2962730053b7113cbaf021a3
--- /dev/null
+++ b/app/controllers/concerns/service_params.rb
@@ -0,0 +1,35 @@
+module ServiceParams
+  extend ActiveSupport::Concern
+
+  ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
+                    :room, :recipients, :project_url, :webhook,
+                    :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+                    :build_key, :server, :teamcity_url, :drone_url, :build_type,
+                    :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
+                    :colorize_messages, :channels,
+                    :push_events, :issues_events, :merge_requests_events, :tag_push_events,
+                    :note_events, :build_events, :wiki_page_events,
+                    :notify_only_broken_builds, :add_pusher,
+                    :send_from_committer_email, :disable_diffs, :external_wiki_url,
+                    :notify, :color,
+                    :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
+                    :jira_issue_transition_id]
+
+  # Parameters to ignore if no value is specified
+  FILTER_BLANK_PARAMS = [:password]
+
+  def service_params
+    dynamic_params = []
+    dynamic_params.concat(@service.event_channel_names)
+
+    service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
+
+    if service_params[:service].is_a?(Hash)
+      FILTER_BLANK_PARAMS.each do |param|
+        service_params[:service].delete(param) if service_params[:service][param].blank?
+      end
+    end
+
+    service_params
+  end
+end
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index 461fc059a3c1ac62db1cb575e23c9607a7e8d95c..a1ab8b99048fc4d5979b1a7064a999d0d2a301fa 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,5 +1,5 @@
 class Explore::ApplicationController < ApplicationController
-  skip_before_action :authenticate_user!, :reject_blocked
+  skip_before_action :authenticate_user!, :reject_blocked!
 
   layout 'explore'
 end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index a04bf7df722ae30525f7f34505fadd94674b8555..6780a6d4d8731b23cdae74365aea3493a245a866 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -121,7 +121,7 @@ class GroupsController < Groups::ApplicationController
   end
 
   def group_params
-    params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock)
+    params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled)
   end
 
   def load_events
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index d3dd98c8a4e44167dbcb471076e2ba12bed6f08a..4eca278599fc370e01df9423b22dc6a898095c73 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,5 +1,5 @@
 class HelpController < ApplicationController
-  skip_before_action :authenticate_user!, :reject_blocked
+  skip_before_action :authenticate_user!, :reject_blocked!
 
   layout 'help'
 
@@ -30,7 +30,7 @@ class HelpController < ApplicationController
       end
 
       # Allow access to images in the doc folder
-      format.any(:png, :gif, :jpeg) do
+      format.any(:png, :gif, :jpeg, :mp4) do
         # Note: We are purposefully NOT using `Rails.root.join`
         path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}")
 
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 25e587248606ccde522f0b0f86fe2c4d35875107..944c73d139ae0be6dd26f0e0111f3c301c796ac7 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -82,8 +82,6 @@ class Import::BitbucketController < Import::BaseController
     go_to_bitbucket_for_permissions
   end
 
-  private
-
   def access_params
     {
       bitbucket_access_token: session[:bitbucket_access_token],
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 23a396e8084f3dd2b32646f35b1d13f22a2bf0b3..08130ee81764dc22fbcfb079cb1f848053853ea7 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -61,8 +61,6 @@ class Import::GitlabController < Import::BaseController
     go_to_gitlab_for_permissions
   end
 
-  private
-
   def access_params
     { gitlab_access_token: session[:gitlab_access_token] }
   end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 824aa41db51c7145140ba00f50aabc6f5abca9cc..a9f482c8787b0edad09127fcaa472fd8133a86b7 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -3,11 +3,6 @@ class Projects::BadgesController < Projects::ApplicationController
   before_action :authorize_admin_project!, only: [:index]
   before_action :no_cache_headers, except: [:index]
 
-  def index
-    @ref = params[:ref] || @project.default_branch || 'master'
-    @build_badge = Gitlab::Badge::Build.new(@project, @ref)
-  end
-
   def build
     badge = Gitlab::Badge::Build.new(project, params[:ref])
 
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 5356fdf010df794cc86314602ecd9c21a31b809a..eda3727a28de2ebc788e41fbc12f86dd46831b79 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -38,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController
   end
 
   def update
+    if params[:file_path].present?
+      @previous_path = @path
+      @path = params[:file_path]
+      @commit_params[:file_path] = @path
+    end
+
     after_edit_path =
       if from_merge_request && @target_branch == @ref
         diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index dd9508da04912b9a154ef25976e9e9d026963ae9..e926043f3ebadbcf21721efa2430daa852f23409 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -6,8 +6,8 @@ class Projects::BranchesController < Projects::ApplicationController
   before_action :authorize_push_code!, only: [:new, :create, :destroy]
 
   def index
-    @sort = params[:sort] || 'name'
-    @branches = @repository.branches_sorted_by(@sort)
+    @sort = params[:sort].presence || 'name'
+    @branches = BranchesFinder.new(@repository, params).execute
     @branches = Kaminari.paginate_array(@branches).page(params[:page])
 
     @max_commits = @branches.reduce(0) do |memo, branch|
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index d7513d75f014491e537789b28df6fb385ae02090..553b62741a5de091ad536aa467686d2ac1794014 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,6 +1,6 @@
 class Projects::BuildsController < Projects::ApplicationController
   before_action :build, except: [:index, :cancel_all]
-  before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
+  before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play]
   before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
   layout 'project'
 
@@ -49,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
   end
 
   def retry
-    unless @build.retryable?
-      return render_404
-    end
+    return render_404 unless @build.retryable?
 
     build = Ci::Build.retry(@build, current_user)
     redirect_to build_path(build)
   end
 
+  def play
+    return render_404 unless @build.playable?
+
+    build = @build.play(current_user)
+    redirect_to build_path(build)
+  end
+
   def cancel
     @build.cancel
     redirect_to build_path(@build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 727e84b40a1ff025f7c81add065f907a95882934..7ae034f9398ce360f86fbe801031809189898f70 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -115,11 +115,11 @@ class Projects::CommitController < Projects::ApplicationController
   end
 
   def define_note_vars
-    @grouped_diff_notes = commit.notes.grouped_diff_notes
+    @grouped_diff_discussions = commit.notes.grouped_diff_discussions
     @notes = commit.notes.non_diff_notes.fresh
 
     Banzai::NoteRenderer.render(
-      @grouped_diff_notes.values.flatten + @notes,
+      @grouped_diff_discussions.values.flat_map(&:notes) + @notes,
       @project,
       current_user,
     )
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 5f3ee71444dd8f25e9026298ebc67b744fdbadde..8c004724f02561ed9260b2da53acfc17c3182dcd 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -15,6 +15,7 @@ class Projects::CompareController < Projects::ApplicationController
   end
 
   def show
+    apply_diff_view_cookie!
   end
 
   def diff_for_path
@@ -53,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController
       )
 
       @diff_notes_disabled = true
-      @grouped_diff_notes = {}
+      @grouped_diff_discussions = {}
     end
   end
 
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4b4337961612c0fd7df7d8c0dfc5e9a60c201d13..58678f96879b160a8cb6d5dac34d453ac648a1bf 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -2,8 +2,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
   layout 'project'
   before_action :authorize_read_environment!
   before_action :authorize_create_environment!, only: [:new, :create]
-  before_action :authorize_update_environment!, only: [:destroy]
-  before_action :environment, only: [:show, :destroy]
+  before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
+  before_action :environment, only: [:show, :edit, :update, :destroy]
 
   def index
     @environments = project.environments
@@ -17,13 +17,24 @@ class Projects::EnvironmentsController < Projects::ApplicationController
     @environment = project.environments.new
   end
 
+  def edit
+  end
+
   def create
-    @environment = project.environments.create(create_params)
+    @environment = project.environments.create(environment_params)
 
     if @environment.persisted?
       redirect_to namespace_project_environment_path(project.namespace, project, @environment)
     else
-      render 'new'
+      render :new
+    end
+  end
+
+  def update
+    if @environment.update(environment_params)
+      redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+    else
+      render :edit
     end
   end
 
@@ -39,8 +50,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
 
   private
 
-  def create_params
-    params.require(:environment).permit(:name)
+  def environment_params
+    params.require(:environment).permit(:name, :external_url)
   end
 
   def environment
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index fa663c9bda4f0e24ca5133bdbca33a1cbe7ae395..660e0eba06fa5b6bbca040ff189feb7a46baa4a5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,8 +1,11 @@
 class Projects::IssuesController < Projects::ApplicationController
+  include NotesHelper
   include ToggleSubscriptionAction
   include IssuableActions
   include ToggleAwardEmoji
+  include IssuableCollections
 
+  before_action :redirect_to_external_issue_tracker, only: [:index, :new]
   before_action :module_enabled
   before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
                                :related_branches, :can_create_branch]
@@ -23,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController
 
   def index
     terms = params['issue_search']
-    @issues = get_issues_collection
+    @issues = issues_collection
 
     if terms.present?
       if terms =~ /\A#(\d+)\z/
@@ -70,6 +73,8 @@ class Projects::IssuesController < Projects::ApplicationController
     @note     = @project.notes.new(noteable: @issue)
     @noteable = @issue
 
+    preload_max_access_for_authors(@notes, @project)
+
     respond_to do |format|
       format.html
       format.json do
@@ -79,7 +84,7 @@ class Projects::IssuesController < Projects::ApplicationController
   end
 
   def create
-    @issue = Issues::CreateService.new(project, current_user, issue_params).execute
+    @issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute
 
     respond_to do |format|
       format.html do
@@ -89,7 +94,7 @@ class Projects::IssuesController < Projects::ApplicationController
           render :new
         end
       end
-      format.js do |format|
+      format.js do
         @link = @issue.attachment.url.to_js
       end
     end
@@ -197,6 +202,18 @@ class Projects::IssuesController < Projects::ApplicationController
     return render_404 unless @project.issues_enabled && @project.default_issues_tracker?
   end
 
+  def redirect_to_external_issue_tracker
+    external = @project.external_issue_tracker
+
+    return unless external
+
+    if action_name == 'new'
+      redirect_to external.new_issue_path
+    else
+      redirect_to external.issues_url
+    end
+  end
+
   # Since iids are implemented only in 6.1
   # user may navigate to issue page using old global ids.
   #
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index df659bb8c3bbfa3cbd1d4e58a8e91b9dd411c9dd..116e7904a4e0eadaffe5cd2d43fa500609211ba2 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -3,7 +3,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
   include DiffForPath
   include DiffHelper
   include IssuableActions
+  include NotesHelper
   include ToggleAwardEmoji
+  include IssuableCollections
 
   before_action :module_enabled
   before_action :merge_request, only: [
@@ -28,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
 
   def index
     terms = params['issue_search']
-    @merge_requests = get_merge_requests_collection
+    @merge_requests = merge_requests_collection
 
     if terms.present?
       if terms =~ /\A[#!](\d+)\z/
@@ -97,7 +99,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     else
       build_merge_request
       @diff_notes_disabled = true
-      @grouped_diff_notes = {}
+      @grouped_diff_discussions = {}
     end
 
     define_commit_vars
@@ -286,6 +288,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       status = pipeline.status
       coverage = pipeline.try(:coverage)
 
+      status = "success_with_warnings" if pipeline.success? && pipeline.has_warnings?
+
       status ||= "preparing"
     else
       ci_service = @merge_request.source_project.ci_service
@@ -374,15 +378,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
       fresh.
       discussions
 
+    preload_noteable_for_regular_notes(@discussions.flat_map(&:notes))
+
     # This is not executed lazily
     @notes = Banzai::NoteRenderer.render(
-      @discussions.flatten,
+      @discussions.flat_map(&:notes),
       @project,
       current_user,
       @path,
       @project_wiki,
       @ref
     )
+
+    preload_max_access_for_authors(@notes, @project)
   end
 
   def define_widget_vars
@@ -402,10 +410,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
     }
 
     @use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
-    @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
+    @grouped_diff_discussions = @merge_request.notes.inc_author_project_award_emoji.grouped_diff_discussions
 
     Banzai::NoteRenderer.render(
-      @grouped_diff_notes.values.flatten,
+      @grouped_diff_discussions.values.flat_map(&:notes),
       @project,
       current_user,
       @path,
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 3eacdbbd0676e598346854fa0d1904021b871393..766b7e9cf2228e0c8c03027bc2c9df87a205dc2f 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -73,7 +73,7 @@ class Projects::NotesController < Projects::ApplicationController
   end
   alias_method :awardable, :note
 
-  def note_to_html(note)
+  def note_html(note)
     render_to_string(
       "projects/notes/_note",
       layout: false,
@@ -82,20 +82,20 @@ class Projects::NotesController < Projects::ApplicationController
     )
   end
 
-  def note_to_discussion_html(note)
-    return unless note.diff_note?
+  def diff_discussion_html(discussion)
+    return unless discussion.diff_discussion?
 
     if params[:view] == 'parallel'
-      template = "projects/notes/_diff_notes_with_reply_parallel"
+      template = "discussions/_parallel_diff_discussion"
       locals =
         if params[:line_type] == 'old'
-          { notes_left: [note], notes_right: [] }
+          { discussion_left: discussion, discussion_right: nil }
         else
-          { notes_left: [], notes_right: [note] }
+          { discussion_left: nil, discussion_right: discussion }
         end
     else
-      template = "projects/notes/_diff_notes_with_reply"
-      locals = { notes: [note] }
+      template = "discussions/_diff_discussion"
+      locals = { discussion: discussion }
     end
 
     render_to_string(
@@ -106,14 +106,14 @@ class Projects::NotesController < Projects::ApplicationController
     )
   end
 
-  def note_to_discussion_with_diff_html(note)
-    return unless note.diff_note?
+  def discussion_html(discussion)
+    return unless discussion.diff_discussion?
 
     render_to_string(
-      "projects/notes/_discussion",
+      "discussions/_discussion",
       layout: false,
       formats: [:html],
-      locals: { discussion_notes: [note] }
+      locals: { discussion: discussion }
     )
   end
 
@@ -132,26 +132,33 @@ class Projects::NotesController < Projects::ApplicationController
         valid: true,
         id: note.id,
         discussion_id: note.discussion_id,
-        html: note_to_html(note),
+        html: note_html(note),
         award: false,
-        note: note.note,
-        discussion_html: note_to_discussion_html(note),
-        discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+        note: note.note
       }
 
-      # The discussion_id is used to add the comment to the correct discussion
-      # element on the merge request page. Among other things, the discussion_id
-      # contains the sha of head commit of the merge request.
-      # When new commits are pushed into the merge request after the initial
-      # load of the merge request page, the discussion elements will still have
-      # the old discussion_ids, with the old head commit sha. The new comment,
-      # however, will have the new discussion_id with the new commit sha.
-      # To ensure that these new comments will still end up in the correct
-      # discussion element, we also send the original discussion_id, with the
-      # old commit sha, along, and fall back on this value when no discussion
-      # element with the new discussion_id could be found.
-      if note.new_diff_note? && note.position != note.original_position
-        attrs[:original_discussion_id] = note.original_discussion_id
+      if note.diff_note?
+        discussion = Discussion.new([note])
+
+        attrs.merge!(
+          diff_discussion_html: diff_discussion_html(discussion),
+          discussion_html: discussion_html(discussion)
+        )
+
+        # The discussion_id is used to add the comment to the correct discussion
+        # element on the merge request page. Among other things, the discussion_id
+        # contains the sha of head commit of the merge request.
+        # When new commits are pushed into the merge request after the initial
+        # load of the merge request page, the discussion elements will still have
+        # the old discussion_ids, with the old head commit sha. The new comment,
+        # however, will have the new discussion_id with the new commit sha.
+        # To ensure that these new comments will still end up in the correct
+        # discussion element, we also send the original discussion_id, with the
+        # old commit sha, along, and fall back on this value when no discussion
+        # element with the new discussion_id could be found.
+        if note.new_diff_note? && note.position != note.original_position
+          attrs[:original_discussion_id] = note.original_discussion_id
+        end
       end
 
       attrs
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85ba706e5cd050eeb19e074a8141611d344e765a
--- /dev/null
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -0,0 +1,30 @@
+class Projects::PipelinesSettingsController < Projects::ApplicationController
+  before_action :authorize_admin_pipeline!
+
+  def show
+    @ref = params[:ref] || @project.default_branch || 'master'
+    @build_badge = Gitlab::Badge::Build.new(@project, @ref)
+  end
+
+  def update
+    if @project.update_attributes(update_params)
+      flash[:notice] = "CI/CD Pipelines settings for '#{@project.name}' were successfully updated."
+      redirect_to namespace_project_pipelines_settings_path(@project.namespace, @project)
+    else
+      render 'index'
+    end
+  end
+
+  private
+
+  def create_params
+    params.require(:pipeline).permit(:ref)
+  end
+
+  def update_params
+    params.require(:project).permit(
+      :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
+      :public_builds
+    )
+  end
+end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 10dca47fdede1c49b774663d3a28c6f35899c48e..d28ec6e2eacc06d20f3d73fecf2bc8734743f5ca 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -3,19 +3,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
   before_action :require_non_empty_project
   before_action :authorize_admin_project!
   before_action :load_protected_branch, only: [:show, :update, :destroy]
+  before_action :load_protected_branches, only: [:index]
 
   layout "project_settings"
 
   def index
-    @protected_branches = @project.protected_branches.order(:name).page(params[:page])
     @protected_branch = @project.protected_branches.new
-    gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } })
+    load_protected_branches_gon_variables
   end
 
   def create
-    @project.protected_branches.create(protected_branch_params)
-    redirect_to namespace_project_protected_branches_path(@project.namespace,
-                                                          @project)
+    @protected_branch = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
+    if @protected_branch.persisted?
+      redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
+    else
+      load_protected_branches
+      load_protected_branches_gon_variables
+      render :index
+    end
   end
 
   def show
@@ -23,7 +28,9 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
   end
 
   def update
-    if @protected_branch && @protected_branch.update_attributes(protected_branch_params)
+    @protected_branch = ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch)
+
+    if @protected_branch.valid?
       respond_to do |format|
         format.json { render json: @protected_branch, status: :ok }
       end
@@ -50,6 +57,18 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
   end
 
   def protected_branch_params
-    params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge)
+    params.require(:protected_branch).permit(:name,
+                                             merge_access_level_attributes: [:access_level],
+                                             push_access_level_attributes: [:access_level])
+  end
+
+  def load_protected_branches
+    @protected_branches = @project.protected_branches.order(:name).page(params[:page])
+  end
+
+  def load_protected_branches_gon_variables
+    gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } },
+               push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } },
+               merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } })
   end
 end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index d79f16e6a5abe4624cfc19ca7f405e25b7d233d2..3602b3d5e58de3e07a2394e0c29ef02c9a194c2a 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
           when "graphs_commits"
             commits_namespace_project_graph_path(@project.namespace, @project, @id)
           when "badges"
-            namespace_project_badges_path(@project.namespace, @project, ref: @id)
+            namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
           else
             namespace_project_commits_path(@project.namespace, @project, @id)
           end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 1b91882048e3626313b6344abaf272ad66fc3857..6a227d85f6f5ce49eed364ba66104ded8942aeb9 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -1,20 +1,5 @@
 class Projects::ServicesController < Projects::ApplicationController
-  ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
-                    :room, :recipients, :project_url, :webhook,
-                    :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
-                    :build_key, :server, :teamcity_url, :drone_url, :build_type,
-                    :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
-                    :colorize_messages, :channels,
-                    :push_events, :issues_events, :merge_requests_events, :tag_push_events,
-                    :note_events, :build_events, :wiki_page_events,
-                    :notify_only_broken_builds, :add_pusher,
-                    :send_from_committer_email, :disable_diffs, :external_wiki_url,
-                    :notify, :color,
-                    :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
-                    :jira_issue_transition_id]
-
-  # Parameters to ignore if no value is specified
-  FILTER_BLANK_PARAMS = [:password]
+  include ServiceParams
 
   # Authorize
   before_action :authorize_admin_project!
@@ -33,7 +18,7 @@ class Projects::ServicesController < Projects::ApplicationController
   end
 
   def update
-    if @service.update_attributes(service_params)
+    if @service.update_attributes(service_params[:service])
       redirect_to(
         edit_namespace_project_service_path(@project.namespace, @project,
                                             @service.to_param, notice:
@@ -64,12 +49,4 @@ class Projects::ServicesController < Projects::ApplicationController
   def service
     @service ||= @project.services.find { |service| service.to_param == params[:id] }
   end
-
-  def service_params
-    service_params = params.require(:service).permit(ALLOWED_PARAMS)
-    FILTER_BLANK_PARAMS.each do |param|
-      service_params.delete(param) if service_params[param].blank?
-    end
-    service_params
-  end
 end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 6dc495247c8ca5acb1b29b1abfa4f991dc5c25e8..8592579abbd18a6174f6401e637fc6c135fabd23 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController
     @tags = @repository.tags_sorted_by(@sort)
     @tags = Kaminari.paginate_array(@tags).page(params[:page])
 
-    @releases = project.releases.where(tag: @tags)
+    @releases = project.releases.where(tag: @tags.map(&:name))
   end
 
   def show
     @tag = @repository.find_tag(params[:id])
+
     @release = @project.releases.find_or_initialize_by(tag: @tag.name)
     @commit = @repository.commit(@tag.target)
   end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index caed064dfbc11ec76715f21004b418b400e86cbc..e617be8f9fba15565564b63d98ded987b8b2e65c 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,6 +1,6 @@
 class Projects::UploadsController < Projects::ApplicationController
   skip_before_action :reject_blocked!, :project,
-    :repository, if: -> { action_name == 'show' && image? }
+    :repository, if: -> { action_name == 'show' && image_or_video? }
 
   before_action :authorize_upload_file!, only: [:create]
 
@@ -24,7 +24,7 @@ class Projects::UploadsController < Projects::ApplicationController
   def show
     return render_404 if uploader.nil? || !uploader.file.exists?
 
-    disposition = uploader.image? ? 'inline' : 'attachment'
+    disposition = uploader.image_or_video? ? 'inline' : 'attachment'
     send_file uploader.file.path, disposition: disposition
   end
 
@@ -49,7 +49,7 @@ class Projects::UploadsController < Projects::ApplicationController
     @uploader
   end
 
-  def image?
-    uploader && uploader.file.exists? && uploader.image?
+  def image_or_video?
+    uploader && uploader.file.exists? && uploader.image_or_video?
   end
 end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 4e5bcff9cf843eaf67752959a628997d28241ebe..a6e1aa5ccc1f69102ad853b309912c08186ca1f5 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -97,7 +97,7 @@ class ProjectsController < Projects::ApplicationController
     end
 
     if @project.pending_delete?
-      flash[:alert] = "Project queued for delete."
+      flash[:alert] = "Project #{@project.name} queued for deletion."
     end
 
     respond_to do |format|
@@ -296,7 +296,7 @@ class ProjectsController < Projects::ApplicationController
       :issues_tracker_id, :default_branch,
       :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
       :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
-      :public_builds, :only_allow_merge_if_build_succeeds
+      :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled
     )
   end
 
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 69c92d2bed2215b52f5e5a41303e8addd4576186..61517d21f9fb4f9fd57e95220f684dd9ea944a1e 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
 class SearchController < ApplicationController
-  skip_before_action :authenticate_user!, :reject_blocked
+  skip_before_action :authenticate_user!, :reject_blocked!
 
   include SearchHelper
 
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..533076585c05c0a9d97bc928e9bd4dbddeda1172
--- /dev/null
+++ b/app/finders/branches_finder.rb
@@ -0,0 +1,31 @@
+class BranchesFinder
+  def initialize(repository, params)
+    @repository = repository
+    @params = params
+  end
+
+  def execute
+    branches = @repository.branches_sorted_by(sort)
+    filter_by_name(branches)
+  end
+
+  private
+
+  attr_reader :repository, :params
+
+  def search
+    @params[:search].presence
+  end
+
+  def sort
+    @params[:sort].presence || 'name'
+  end
+
+  def filter_by_name(branches)
+    if search
+      branches.select { |branch| branch.name.include?(search) }
+    else
+      branches
+    end
+  end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index a0932712bd03506be1b22cb80b0fa2876b7cff92..33daac0399e29551b63af478c056d0a2e6d7d2b8 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -109,7 +109,7 @@ class IssuableFinder
 
         scope.where(title: params[:milestone_title])
       else
-        nil
+        Milestone.none
       end
   end
 
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 03495cf5ec48bd8fe588ec4b59cc3b8b8169ce72..50de93d4bdf9f260af2845782ff7a014ee769ef4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -245,7 +245,6 @@ module ApplicationHelper
       milestone_title: params[:milestone_title],
       assignee_id: params[:assignee_id],
       author_id: params[:author_id],
-      sort: params[:sort],
       issue_search: params[:issue_search],
       label_name: params[:label_name]
     }
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6ff40c6b461a10a99cf535f493947c40d3e5c1eb
--- /dev/null
+++ b/app/helpers/avatars_helper.rb
@@ -0,0 +1,30 @@
+module AvatarsHelper
+
+  def author_avatar(commit_or_event, options = {})
+    user_avatar(options.merge({
+      user: commit_or_event.author,
+      user_name: commit_or_event.author_name,
+      user_email: commit_or_event.author_email,
+    }))
+  end
+
+  private
+
+  def user_avatar(options = {})
+    avatar_size = options[:size] || 16
+    user_name = options[:user].try(:name) || options[:user_name]
+    avatar = image_tag(
+      avatar_icon(options[:user] || options[:user_email], avatar_size),
+      class: "avatar has-tooltip hidden-xs s#{avatar_size}",
+      alt: "#{user_name}'s avatar",
+      title: user_name
+    )
+
+    if options[:user]
+      link_to(avatar, user_path(options[:user]))
+    elsif options[:user_email]
+      mail_to(options[:user_email], avatar)
+    end
+  end
+
+end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index abe115d8c68be2b47ce43cf21bf958da62e8ab34..48c2782821905c3782534bfd75638f3332bccd50 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -13,7 +13,7 @@ module BlobHelper
 
     blob = project.repository.blob_at(ref, path) rescue nil
 
-    return unless blob && blob_text_viewable?(blob)
+    return unless blob
 
     from_mr = options[:from_merge_request_id]
     link_opts = {}
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index bfd23aa4e043f62f850ecb8d79c441d3fb6365bc..3fc85dc6b2bebd8fe0f0b2ed8f417dee8e3a629e 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -9,6 +9,17 @@ module BranchesHelper
     end
   end
 
+  def filter_branches_path(options = {})
+    exist_opts = {
+      search: params[:search],
+      sort: params[:sort]
+    }
+
+    options = exist_opts.merge(options)
+
+    namespace_project_branches_path(@project.namespace, @project, @id, options)
+  end
+
   def can_push_branch?(project, branch_name)
     return false unless project.repository.branch_exists?(branch_name)
 
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index e6c99c9959e159ed78089c022ce869246f9b397c..ea2f5f9281a84c4ae5be1faadb37e70645f5e6cf 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -15,8 +15,11 @@ module CiStatusHelper
   end
 
   def ci_label_for_status(status)
-    if status == 'success'
+    case status
+    when 'success'
       'passed'
+    when 'success_with_warnings'
+      'passed with warnings'
     else
       status
     end
@@ -26,24 +29,26 @@ module CiStatusHelper
     icon_name =
       case status
       when 'success'
-        'check'
+        'icon_status_success'
+      when 'success_with_warnings'
+        'icon_status_warning'
       when 'failed'
-        'close'
+        'icon_status_failed'
       when 'pending'
-        'clock-o'
+        'icon_status_pending'
       when 'running'
-        'spinner'
+        'icon_status_running'
       else
-        'circle'
+        'icon_status_cancel'
       end
 
-    icon(icon_name + ' fw')
+    custom_icon(icon_name)
   end
 
-  def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
+  def render_commit_status(commit, tooltip_placement: 'auto left')
     project = commit.project
     path = builds_namespace_project_commit_path(project.namespace, project, commit)
-    render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass)
+    render_status_with_link('commit', commit.status, path, tooltip_placement)
   end
 
   def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 474041eccbb60ca7ccc7f683acbb9e2e4234d040..f497626e21aa6425494c4723298ad7981b6ecbdb 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
 module CommitsHelper
   # Returns a link to the commit author. If the author has a matching user and
   # is a member of the current @project it will link to the team member page.
@@ -16,16 +15,6 @@ module CommitsHelper
     commit_person_link(commit, options.merge(source: :committer))
   end
 
-  def commit_author_avatar(commit, options = {})
-    options = options.merge(source: :author)
-    user = commit.send(options[:source])
-
-    source_email = clean(commit.send "#{options[:source]}_email".to_sym)
-    person_email = user.try(:email) || source_email
-
-    image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]} hidden-xs", width: options[:size], alt: "")
-  end
-
   def image_diff_class(diff)
     if diff.deleted_file
       "deleted"
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 75b029365f93a54c40c875ee72b6ebe652beae58..f35e2f6ddcdadff7f7d41219c06708a74d083a1d 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -54,18 +54,20 @@ module DiffHelper
     end
   end
 
-  def organize_comments(left, right)
-    notes_left = notes_right = nil
+  def parallel_diff_discussions(left, right, diff_file)
+    discussion_left = discussion_right = nil
 
-    unless left[:type].nil? && right[:type] == 'new'
-      notes_left = @grouped_diff_notes[left[:line_code]]
+    if left && (left.unchanged? || left.removed?)
+      line_code = diff_file.line_code(left)
+      discussion_left = @grouped_diff_discussions[line_code]
     end
 
-    unless left[:type].nil? && right[:type].nil?
-      notes_right = @grouped_diff_notes[right[:line_code]]
+    if right && right.added?
+      line_code = diff_file.line_code(right)
+      discussion_right = @grouped_diff_discussions[line_code]
     end
 
-    [notes_left, notes_right]
+    [discussion_left, discussion_right]
   end
 
   def inline_diff_btn
@@ -142,8 +144,6 @@ module DiffHelper
     toggle_whitespace_link(url, options)
   end
 
-  private
-
   def hide_whitespace?
     params[:w] == '1'
   end
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index 1f3401f290637587be4ee512738b864885220569..defd87d6bbe620991e9f7158058780f3149acf72 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -1,8 +1,7 @@
 module ExternalWikiHelper
   def get_project_wiki_path(project)
-    external_wiki_service = project.services.
-      find { |service| service.to_param == 'external_wiki' }
-    if external_wiki_service.present? && external_wiki_service.active?
+    external_wiki_service = project.external_wiki
+    if external_wiki_service
       external_wiki_service.properties['external_wiki_url']
     else
       namespace_project_wiki_path(project.namespace, project, :home)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2b0defd1dda0a6fc42c26378bc945857e013e07d..2e82b44437b2cf4b5e64cd648e2af4af37ea22ef 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -13,38 +13,6 @@ module IssuesHelper
     OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
   end
 
-  def url_for_project_issues(project = @project, options = {})
-    return '' if project.nil?
-
-    url =
-      if options[:only_path]
-        project.issues_tracker.project_path
-      else
-        project.issues_tracker.project_url
-      end
-
-    # Ensure we return a valid URL to prevent possible XSS.
-    URI.parse(url).to_s
-  rescue URI::InvalidURIError
-    ''
-  end
-
-  def url_for_new_issue(project = @project, options = {})
-    return '' if project.nil?
-
-    url =
-      if options[:only_path]
-        project.issues_tracker.new_issue_path
-      else
-        project.issues_tracker.new_issue_url
-      end
-
-    # Ensure we return a valid URL to prevent possible XSS.
-    URI.parse(url).to_s
-  rescue URI::InvalidURIError
-    ''
-  end
-
   def url_for_issue(issue_iid, project = @project, options = {})
     return '' if project.nil?
 
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 98143dcee9b6b8fe0d993fe8e7d1c8413b7bb7d4..26bde2230a95b10960d92d9913aa2ba2a5a83ad9 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,9 +1,4 @@
 module NotesHelper
-  # Helps to distinguish e.g. commit notes in mr notes list
-  def note_for_main_target?(note)
-    @noteable.class.name == note.noteable_type && !note.diff_note?
-  end
-
   def note_target_fields(note)
     if note.noteable
       hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
@@ -12,7 +7,7 @@ module NotesHelper
   end
 
   def note_editable?(note)
-    note.editable? && can?(current_user, :admin_note, note)
+    Ability.can_edit_note?(current_user, note)
   end
 
   def noteable_json(noteable)
@@ -44,8 +39,8 @@ module NotesHelper
     # If we didn't, diff notes that would show for the same line on the changes
     # tab, would show in different discussions on the discussion tab.
     use_legacy_diff_note ||= begin
-      line_diff_notes = @grouped_diff_notes[line_code]
-      line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?)
+      discussion = @grouped_diff_discussions[line_code]
+      discussion && discussion.legacy_diff_discussion?
     end
 
     data = {
@@ -81,22 +76,10 @@ module NotesHelper
     data
   end
 
-  def link_to_reply_discussion(note, line_type = nil)
+  def link_to_reply_discussion(discussion, line_type = nil)
     return unless current_user
 
-    data = {
-      noteable_type: note.noteable_type,
-      noteable_id:   note.noteable_id,
-      commit_id:     note.commit_id,
-      discussion_id: note.discussion_id,
-      line_type:     line_type
-    }
-
-    if note.diff_note?
-      data[:note_type] = note.type
-
-      data.merge!(note.diff_attributes)
-    end
+    data = discussion.reply_attributes.merge(line_type: line_type)
 
     content_tag(:div, class: "discussion-reply-holder") do
       button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
@@ -104,23 +87,26 @@ module NotesHelper
     end
   end
 
-  def note_max_access_for_user(note)
-    @max_access_by_user_id ||= Hash.new do |hash, key|
-      project = key[:project]
-      hash[key] = project.team.human_max_access(key[:user_id])
-    end
+  def preload_max_access_for_authors(notes, project)
+    user_ids = notes.map(&:author_id)
+    project.team.max_member_access_for_user_ids(user_ids)
+  end
 
-    full_key = { project: note.project, user_id: note.author_id }
-    @max_access_by_user_id[full_key]
+  def preload_noteable_for_regular_notes(notes)
+    ActiveRecord::Associations::Preloader.new.preload(notes.select { |note| !note.for_commit? }, :noteable)
+  end
+
+  def note_max_access_for_user(note)
+    note.project.team.human_max_access(note.author_id)
   end
 
-  def diff_note_path(note)
-    return unless note.diff_note?
+  def discussion_diff_path(discussion)
+    return unless discussion.diff_discussion?
 
-    if note.for_merge_request? && note.active?
-      diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
-    elsif note.for_commit?
-      namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
+    if discussion.for_merge_request? && discussion.active?
+      diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
+    elsif discussion.for_commit?
+      namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
     end
   end
 end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a733dff1579d2dd1a4028bc24261613ae8fc5f07..505545fbabb908d3e2fb686ab9d438f54fe2845c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -263,6 +263,10 @@ module ProjectsHelper
     filename_path(project, :version)
   end
 
+  def ci_configuration_path(project)
+    filename_path(project, :gitlab_ci_yml)
+  end
+
   def project_wiki_path_with_version(proj, page, version, is_newest)
     url_params = is_newest ? {} : { version_id: version }
     namespace_project_wiki_path(proj.namespace, proj, page, url_params)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index fcb2703e837a74a26af3f3a7298f8d7a1d87137b..a2bba139c170e21a16d43ad53810faa0073fbdeb 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -112,7 +112,8 @@ module SearchHelper
       search: params[:search],
       project_id: params[:project_id],
       group_id: params[:group_id],
-      scope: params[:scope]
+      scope: params[:scope],
+      repository_ref: params[:repository_ref]
     }
 
     options = exist_opts.merge(options)
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index bb395e378848ab093e85ccf85d840eddf80af1c1..5f27e33c6ad5aef23ac6925d9fbf808a7b908165 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -5,21 +5,9 @@ module SelectsHelper
     css_class << "skip_ldap " if opts[:skip_ldap]
     css_class << (opts[:class] || '')
     value = opts[:selected] || ''
-
-    first_user = opts[:first_user] && current_user ? current_user.username : false
-
     html = {
       class: css_class,
-      data: {
-        placeholder: opts[:placeholder]   || 'Search for a user',
-        null_user: opts[:null_user]       || false,
-        any_user: opts[:any_user]         || false,
-        email_user: opts[:email_user]     || false,
-        first_user: first_user,
-        current_user: opts[:current_user] || false,
-        "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
-        author_id: opts[:author_id] || ''
-      }
+      data: users_select_data_attributes(opts)
     }
 
     unless opts[:scope] == :all
@@ -68,4 +56,20 @@ module SelectsHelper
 
     hidden_field_tag(id, value, class: css_class)
   end
+
+  private
+
+  def users_select_data_attributes(opts)
+    {
+      placeholder: opts[:placeholder]   || 'Search for a user',
+      null_user: opts[:null_user]       || false,
+      any_user: opts[:any_user]         || false,
+      email_user: opts[:email_user]     || false,
+      first_user: opts[:first_user] && current_user ? current_user.username : false,
+      current_user: opts[:current_user] || false,
+      "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+      author_id: opts[:author_id] || '',
+      skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil,
+    }
+  end
 end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2dd0bf5d71e3f7205f2d846a954719f0b988fc2d
--- /dev/null
+++ b/app/helpers/services_helper.rb
@@ -0,0 +1,25 @@
+module ServicesHelper
+  def service_event_description(event)
+    case event
+    when "push"
+      "Event will be triggered by a push to the repository"
+    when "tag_push"
+      "Event will be triggered when a new tag is pushed to the repository"
+    when "note"
+      "Event will be triggered when someone adds a comment"
+    when "issue"
+      "Event will be triggered when an issue is created/updated/merged"
+    when "merge_request"
+      "Event will be triggered when a merge request is created/updated/merged"
+    when "build"
+      "Event will be triggered when a build status changes"
+    when "wiki_page"
+      "Event will be triggered when a wiki page is created/updated"
+    end
+  end
+
+  def service_event_field_name(event)
+    event = event.pluralize if %w[merge_request issue].include?(event)
+    "#{event}_events"
+  end
+end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index d86f1999f5cdc120139e498c00a34a7acf024ca2..e1c0b49755099054fef8cf210562a00a996be6b0 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -102,11 +102,11 @@ module SortingHelper
   end
 
   def sort_value_oldest_created
-    'id_asc'
+    'created_asc'
   end
 
   def sort_value_recently_created
-    'id_desc'
+    'created_desc'
   end
 
   def sort_value_milestone_soon
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 8cb82c2d5cccb0e3025e4668694b70ec42e019d4..790001222f12e92d47de0c72a201652647b08224 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -1,15 +1,6 @@
 module TimeHelper
-  def duration_in_words(finished_at, started_at)
-    if finished_at && started_at
-      interval_in_seconds = finished_at.to_i - started_at.to_i
-    elsif started_at
-      interval_in_seconds = Time.now.to_i - started_at.to_i
-    end
-
-    time_interval_in_words(interval_in_seconds)
-  end
-
   def time_interval_in_words(interval_in_seconds)
+    interval_in_seconds = interval_in_seconds.to_i
     minutes = interval_in_seconds / 60
     seconds = interval_in_seconds - minutes * 60
 
@@ -25,9 +16,19 @@ module TimeHelper
   end
 
   def duration_in_numbers(finished_at, started_at)
-    diff_in_seconds = finished_at.to_i - started_at.to_i
-    time_format = diff_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
+    interval = interval_in_seconds(started_at, finished_at)
+    time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
 
-    Time.at(diff_in_seconds).utc.strftime(time_format)
+    Time.at(interval).utc.strftime(time_format)
+  end
+
+  private
+
+  def interval_in_seconds(started_at, finished_at = nil)
+    if started_at && finished_at
+      finished_at.to_i - started_at.to_i
+    elsif started_at
+      Time.now.to_i - started_at.to_i
+    end
   end
 end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 6fd18f2ee24bd0dea3b42d0aff4c9317eeac8534..d95a250719977937a88c8a140b04b295e464a126 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -47,6 +47,16 @@ class Ability
       end
     end
 
+    # Returns an Array of Issues that can be read by the given user.
+    #
+    # issues - The issues to reduce down to those readable by the user.
+    # user - The User for which to check the issues
+    def issues_readable_by_user(issues, user = nil)
+      return issues if user && user.admin?
+
+      issues.select { |issue| issue.visible_to_user?(user) }
+    end
+
     # List of possible abilities for anonymous user
     def anonymous_abilities(user, subject)
       if subject.is_a?(PersonalSnippet)
@@ -172,7 +182,7 @@ class Ability
           rules << :read_build if project.public_builds?
 
           unless owner || project.team.member?(user) || project_group_member?(project, user)
-            rules << :request_access
+            rules << :request_access if project.request_access_enabled
           end
         end
 
@@ -373,7 +383,7 @@ class Ability
       end
 
       if group.public? || (group.internal? && !user.external?)
-        rules << :request_access unless group.users.include?(user)
+        rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
       end
 
       rules.flatten
@@ -388,6 +398,18 @@ class Ability
       GroupProjectsFinder.new(group).execute(user).any?
     end
 
+    def can_edit_note?(user, note)
+      return false if !note.editable? || !user.present?
+      return true if note.author == user || user.admin?
+
+      if note.project
+        max_access_level = note.project.team.max_member_access(user.id)
+        max_access_level >= Gitlab::Access::MASTER
+      else
+        false
+      end
+    end
+
     def namespace_abilities(user, namespace)
       rules = []
 
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c6f77cc055f4908fb6325b41f331924562fa757a..8c19d9dc9c8c8481ce8d29f7cbc9cca425d32be8 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -4,12 +4,20 @@ class ApplicationSetting < ActiveRecord::Base
   add_authentication_token_field :health_check_access_token
 
   CACHE_KEY = 'application_setting.last'
+  DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s*     # comma or semicolon, optionally surrounded by whitespace
+                            |               # or
+                            \s              # any whitespace character
+                            |               # or
+                            [\r\n]          # any number of newline characters
+                          }x
 
   serialize :restricted_visibility_levels
   serialize :import_sources
   serialize :disabled_oauth_sign_in_sources, Array
-  serialize :restricted_signup_domains, Array
-  attr_accessor :restricted_signup_domains_raw
+  serialize :domain_whitelist, Array
+  serialize :domain_blacklist, Array
+
+  attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
 
   validates :session_expire_delay,
             presence: true,
@@ -62,6 +70,10 @@ class ApplicationSetting < ActiveRecord::Base
   validates :enabled_git_access_protocol,
             inclusion: { in: %w(ssh http), allow_blank: true, allow_nil: true }
 
+  validates :domain_blacklist,
+            presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
+            if: :domain_blacklist_enabled?
+
   validates_each :restricted_visibility_levels do |record, attr, value|
     unless value.nil?
       value.each do |level|
@@ -129,7 +141,7 @@ class ApplicationSetting < ActiveRecord::Base
       session_expire_delay: Settings.gitlab['session_expire_delay'],
       default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
       default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
-      restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+      domain_whitelist: Settings.gitlab['domain_whitelist'],
       import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
       shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
       max_artifacts_size: Settings.artifacts['max_size'],
@@ -150,20 +162,30 @@ class ApplicationSetting < ActiveRecord::Base
     ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
   end
 
-  def restricted_signup_domains_raw
-    self.restricted_signup_domains.join("\n") unless self.restricted_signup_domains.nil?
+  def domain_whitelist_raw
+    self.domain_whitelist.join("\n") unless self.domain_whitelist.nil?
+  end
+
+  def domain_blacklist_raw
+    self.domain_blacklist.join("\n") unless self.domain_blacklist.nil?
+  end
+
+  def domain_whitelist_raw=(values)
+    self.domain_whitelist = []
+    self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
+    self.domain_whitelist.reject! { |d| d.empty? }
+    self.domain_whitelist
+  end
+
+  def domain_blacklist_raw=(values)
+    self.domain_blacklist = []
+    self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
+    self.domain_blacklist.reject! { |d| d.empty? }
+    self.domain_blacklist
   end
 
-  def restricted_signup_domains_raw=(values)
-    self.restricted_signup_domains = []
-    self.restricted_signup_domains = values.split(
-      /\s*[,;]\s*     # comma or semicolon, optionally surrounded by whitespace
-      |               # or
-      \s              # any whitespace character
-      |               # or
-      [\r\n]          # any number of newline characters
-      /x)
-    self.restricted_signup_domains.reject! { |d| d.empty? }
+  def domain_blacklist_file=(file)
+    self.domain_blacklist_raw = file.read
   end
 
   def runners_registration_token
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 4279ea2ce578849d80d63da3982e4d5883babfd4..0df2805e448653029aa0d5629bd9325c4f623d13 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -31,6 +31,10 @@ class Blob < SimpleDelegator
     text? && language && language.name == 'SVG'
   end
 
+  def video?
+    UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
+  end
+
   def to_partial_path
     if lfs_pointer?
       'download'
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ffac3a22efc09eab3cd818fa82233bed8b236cc0..08f396210c9361164dbc2bd3e76a3d7ec6aba1b2 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -12,9 +12,11 @@ module Ci
 
     scope :unstarted, ->() { where(runner_id: nil) }
     scope :ignore_failures, ->() { where(allow_failure: false) }
-    scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
+    scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) }
+    scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
     scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
     scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
+    scope :manual_actions, ->() { where(when: :manual) }
 
     mount_uploader :artifacts_file, ArtifactUploader
     mount_uploader :artifacts_metadata, ArtifactUploader
@@ -91,6 +93,29 @@ module Ci
       end
     end
 
+    def manual?
+      self.when == 'manual'
+    end
+
+    def other_actions
+      pipeline.manual_actions.where.not(name: name)
+    end
+
+    def playable?
+      project.builds_enabled? && commands.present? && manual?
+    end
+
+    def play(current_user = nil)
+      # Try to queue a current build
+      if self.queue
+        self.update(user: current_user)
+        self
+      else
+        # Otherwise we need to create a duplicate
+        Ci::Build.retry(self, current_user)
+      end
+    end
+
     def retryable?
       project.builds_enabled? && commands.present? && complete?
     end
@@ -121,11 +146,14 @@ module Ci
     end
 
     def variables
-      variables = []
-      variables += predefined_variables
-      variables += yaml_variables if yaml_variables
-      variables += project_variables
-      variables += trigger_variables
+      variables = predefined_variables
+      variables += project.predefined_variables
+      variables += pipeline.predefined_variables
+      variables += runner.predefined_variables if runner
+      variables += project.container_registry_variables
+      variables += yaml_variables
+      variables += project.secret_variables
+      variables += trigger_request.user_variables if trigger_request
       variables
     end
 
@@ -304,7 +332,7 @@ module Ci
     end
 
     def valid_token?(token)
-      project.valid_runners_token? token
+      project.valid_runners_token?(token)
     end
 
     def has_tags?
@@ -385,6 +413,14 @@ module Ci
       self.update(artifacts_expire_at: nil)
     end
 
+    def when
+      read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
+    end
+
+    def yaml_variables
+      read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
+    end
+
     private
 
     def update_artifacts_size
@@ -403,29 +439,30 @@ module Ci
       self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
     end
 
-    def project_variables
-      project.variables.map do |variable|
-        { key: variable.key, value: variable.value, public: false }
-      end
-    end
-
-    def trigger_variables
-      if trigger_request && trigger_request.variables
-        trigger_request.variables.map do |key, value|
-          { key: key, value: value, public: false }
-        end
-      else
-        []
-      end
-    end
-
     def predefined_variables
-      variables = []
-      variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
-      variables << { key: :CI_BUILD_NAME, value: name, public: true }
-      variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
-      variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
+      variables = [
+        { key: 'CI', value: 'true', public: true },
+        { key: 'GITLAB_CI', value: 'true', public: true },
+        { key: 'CI_BUILD_ID', value: id.to_s, public: true },
+        { key: 'CI_BUILD_TOKEN', value: token, public: false },
+        { key: 'CI_BUILD_REF', value: sha, public: true },
+        { key: 'CI_BUILD_BEFORE_SHA', value: before_sha, public: true },
+        { key: 'CI_BUILD_REF_NAME', value: ref, public: true },
+        { key: 'CI_BUILD_NAME', value: name, public: true },
+        { key: 'CI_BUILD_STAGE', value: stage, public: true },
+        { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
+        { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
+        { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
+      ]
+      variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
+      variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
       variables
     end
+
+    def build_attributes_from_config
+      return {} unless pipeline.config_processor
+      
+      pipeline.config_processor.build_attributes(name)
+    end
   end
 end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index a65a826536de8f4fdc8a43a5e25b238a664035c1..bce6a992af6220e3bdf2c452fdb89506597720cb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -20,6 +20,11 @@ module Ci
     after_touch :update_state
     after_save :keep_around_commits
 
+    # ref can't be HEAD or SHA, can only be branch/tag name
+    scope :latest_successful_for, ->(ref = default_branch) do
+      where(ref: ref).success.order(id: :desc).limit(1)
+    end
+
     def self.truncate_sha(sha)
       sha[0...8]
     end
@@ -69,6 +74,10 @@ module Ci
       !tag?
     end
 
+    def manual_actions
+      builds.latest.manual_actions
+    end
+
     def retryable?
       builds.latest.any? do |build|
         build.failed? && build.retryable?
@@ -142,6 +151,10 @@ module Ci
       end
     end
 
+    def has_warnings?
+      builds.latest.ignored.any?
+    end
+
     def config_processor
       return nil unless ci_yaml_file
       return @config_processor if defined?(@config_processor)
@@ -194,6 +207,12 @@ module Ci
       Note.for_commit_id(sha)
     end
 
+    def predefined_variables
+      [
+        { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
+      ]
+    end
+
     private
 
     def build_builds_for_stages(stages, user, status, trigger_request)
@@ -202,8 +221,9 @@ module Ci
       # build builds only for the first stage that has builds available.
       #
       stages.any? do |stage|
-        CreateBuildsService.new(self)
-          .execute(stage, user, status, trigger_request).present?
+        CreateBuildsService.new(self).
+          execute(stage, user, status, trigger_request).
+          any?(&:active?)
       end
     end
 
@@ -222,7 +242,7 @@ module Ci
 
     def keep_around_commits
       return unless project
-      
+
       project.repository.keep_around(self.sha)
       project.repository.keep_around(self.before_sha)
     end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index b64ec79ec2b46284cd7725242540082d869027ef..49f05f881a25f246f03c5b208238da5c204f75c8 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -114,6 +114,14 @@ module Ci
       tag_list.any?
     end
 
+    def predefined_variables
+      [
+        { key: 'CI_RUNNER_ID', value: id.to_s, public: true },
+        { key: 'CI_RUNNER_DESCRIPTION', value: description, public: true },
+        { key: 'CI_RUNNER_TAGS', value: tag_list.to_s, public: true }
+      ]
+    end
+
     private
 
     def tag_constraints
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index fcf2b6dc5e221ab2d6cdb7c7242de42472ac523f..fc674871743bdb414da97fe199a5fd75a03d1ed1 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -7,5 +7,13 @@ module Ci
     has_many :builds, class_name: 'Ci::Build'
 
     serialize :variables
+
+    def user_variables
+      return [] unless variables
+
+      variables.map do |key, value|
+        { key: key, value: value, public: false }
+      end
+    end
   end
 end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2ef3973c1606c7fb0f658718ca2104974ceb62ae..c52b4a051c2a5655001855b7cea45f485ecfccff 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -123,15 +123,17 @@ class Commit
   # In case this first line is longer than 100 characters, it is cut off
   # after 80 characters and ellipses (`&hellp;`) are appended.
   def title
-    title = safe_message
+    full_title.length > 100 ? full_title[0..79] << "…" : full_title
+  end
 
-    return no_commit_message if title.blank?
+  # Returns the full commits title
+  def full_title
+    return @full_title if @full_title
 
-    title_end = title.index("\n")
-    if (!title_end && title.length > 100) || (title_end && title_end > 100)
-      title[0..79] << "…"
+    if safe_message.blank?
+      @full_title = no_commit_message
     else
-      title.split("\n", 2).first
+      @full_title = safe_message.split("\n", 2).first
     end
   end
 
@@ -178,7 +180,18 @@ class Commit
   end
 
   def author
-    @author ||= User.find_by_any_email(author_email.downcase)
+    if RequestStore.active?
+      key = "commit_author:#{author_email.downcase}"
+      # nil is a valid value since no author may exist in the system
+      if RequestStore.store.has_key?(key)
+        @author = RequestStore.store[key]
+      else
+        @author = find_author_by_any_email
+        RequestStore.store[key] = @author
+      end
+    else
+      @author ||= find_author_by_any_email
+    end
   end
 
   def committer
@@ -295,8 +308,8 @@ class Commit
   def uri_type(path)
     entry = @raw.tree.path(path)
     if entry[:type] == :blob
-      blob = Gitlab::Git::Blob.new(name: entry[:name])
-      blob.image? ? :raw : :blob
+      blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]))
+      blob.image? || blob.video? ? :raw : :blob
     else
       entry[:type]
     end
@@ -306,6 +319,10 @@ class Commit
 
   private
 
+  def find_author_by_any_email
+    User.find_by_any_email(author_email.downcase)
+  end
+
   def repo_changes
     changes = { added: [], modified: [], removed: [] }
 
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index e437e3417a8388a602c6870cc731ea0a6428a717..2d185c28809d69eced89d1057662c5c7d309d7e1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -16,12 +16,20 @@ class CommitStatus < ActiveRecord::Base
 
   alias_attribute :author, :user
 
-  scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
+  scope :latest, -> do
+    max_id = unscope(:select).select("max(#{quoted_table_name}.id)")
+
+    where(id: max_id.group(:name, :commit_id))
+  end
   scope :retried, -> { where.not(id: latest) }
   scope :ordered, -> { order(:name) }
   scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
 
   state_machine :status, initial: :pending do
+    event :queue do
+      transition skipped: :pending
+    end
+
     event :run do
       transition pending: :running
     end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index acb6f5a2998bfd07b6497302e6d333862f320684..cbae1cd439bc3d0ffdd0757679570e35ee62c3ef 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -17,7 +17,7 @@ module Issuable
     belongs_to :assignee, class_name: "User"
     belongs_to :updated_by, class_name: "User"
     belongs_to :milestone
-    has_many :notes, as: :noteable, dependent: :destroy do
+    has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
       def authors_loaded?
         # We check first if we're loaded to not load unnecessarily.
         loaded? && to_a.all? { |note| note.association(:author).loaded? }
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index 2785fbb21c9966a2028cf42199e4973c2a32edc7..4be6a2f621b322b5c2b9d582d757454b1108d19a 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -1,12 +1,6 @@
 module NoteOnDiff
   extend ActiveSupport::Concern
 
-  NUMBER_OF_TRUNCATED_DIFF_LINES = 16
-
-  included do
-    delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
-  end
-
   def diff_note?
     true
   end
@@ -30,23 +24,4 @@ module NoteOnDiff
   def can_be_award_emoji?
     false
   end
-
-  # Returns an array of at most 16 highlighted lines above a diff note
-  def truncated_diff_lines
-    prev_lines = []
-
-    highlighted_diff_lines.each do |line|
-      if line.meta?
-        prev_lines.clear
-      else
-        prev_lines << line
-
-        break if for_line?(line)
-
-        prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
-      end
-    end
-
-    prev_lines
-  end
 end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b8e6df2da9e992583d995ad0228b9e7b8d034de
--- /dev/null
+++ b/app/models/concerns/spammable.rb
@@ -0,0 +1,16 @@
+module Spammable
+  extend ActiveSupport::Concern
+
+  included do
+    attr_accessor :spam
+    after_validation :check_for_spam, on: :create
+  end
+
+  def spam?
+    @spam
+  end
+
+  def check_for_spam
+    self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
+  end
+end
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
index 3ef91caad473004a1d618739246eb15ec0fa209f..44c6b30f2788a89a4425950f6b0d70eaafc6f7b5 100644
--- a/app/models/concerns/statuseable.rb
+++ b/app/models/concerns/statuseable.rb
@@ -16,10 +16,10 @@ module Statuseable
 
       deduce_status = "(CASE
         WHEN (#{builds})=0 THEN NULL
-        WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
-        WHEN (#{builds})=(#{pending}) THEN 'pending'
-        WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
         WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+        WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
+        WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
+        WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
         WHEN (#{running})+(#{pending})>0 THEN 'running'
         ELSE 'failed'
       END)"
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 885deaf78d2d480b0ad416a04f0d3a970ed45833..24c7b26d223d2536e1ca773f48208f03ca0ca1f9 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -1,12 +1,26 @@
 module TokenAuthenticatable
   extend ActiveSupport::Concern
 
+  private
+
+  def write_new_token(token_field)
+    new_token = generate_token(token_field)
+    write_attribute(token_field, new_token)
+  end
+
+  def generate_token(token_field)
+    loop do
+      token = Devise.friendly_token
+      break token unless self.class.unscoped.find_by(token_field => token)
+    end
+  end
+
   class_methods do
     def authentication_token_fields
       @token_fields || []
     end
 
-    private
+    private # rubocop:disable Lint/UselessAccessModifier
 
     def add_authentication_token_field(token_field)
       @token_fields = [] unless @token_fields
@@ -32,18 +46,4 @@ module TokenAuthenticatable
       end
     end
   end
-
-  private
-
-  def write_new_token(token_field)
-    new_token = generate_token(token_field)
-    write_attribute(token_field, new_token)
-  end
-
-  def generate_token(token_field)
-    loop do
-      token = Devise.friendly_token
-      break token unless self.class.unscoped.find_by(token_field => token)
-    end
-  end
 end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 520026c18dd5d96c5dedb739d3187bbaa60d779f..1a7cd60817e7505fdc40728091efb6f53a843283 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base
   def keep_around_commit
     project.repository.keep_around(self.sha)
   end
+
+  def manual_actions
+    deployable.try(:other_actions)
+  end
 end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 9671955db360c77283082556fc3ba9314b1dc3be..c816deb4e0cb65d2b4f2a19c24dfa78f55381856 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -67,7 +67,7 @@ class DiffNote < Note
     return false unless supported?
     return true if for_commit?
 
-    diff_refs ||= self.noteable.diff_refs
+    diff_refs ||= noteable_diff_refs
 
     self.position.diff_refs == diff_refs
   end
@@ -78,6 +78,14 @@ class DiffNote < Note
     !self.for_merge_request? || self.noteable.support_new_diff_notes?
   end
 
+  def noteable_diff_refs
+    if noteable.respond_to?(:diff_sha_refs)
+      noteable.diff_sha_refs
+    else
+      noteable.diff_refs
+    end
+  end
+
   def set_original_position
     self.original_position = self.position.dup
   end
@@ -96,7 +104,7 @@ class DiffNote < Note
       self.project,
       nil,
       old_diff_refs: self.position.diff_refs,
-      new_diff_refs: self.noteable.diff_refs,
+      new_diff_refs: noteable_diff_refs,
       paths: self.position.paths
     ).execute(self)
   end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e2218a5f02bed4b40bfbd510b85310b4a6f4b5b6
--- /dev/null
+++ b/app/models/discussion.rb
@@ -0,0 +1,97 @@
+class Discussion
+  NUMBER_OF_TRUNCATED_DIFF_LINES = 16
+
+  attr_reader :first_note, :notes
+
+  delegate  :created_at,
+            :project,
+            :author,
+
+            :noteable,
+            :for_commit?,
+            :for_merge_request?,
+
+            :line_code,
+            :diff_file,
+            :for_line?,
+            :active?,
+
+            to: :first_note
+
+  delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
+
+  def self.for_notes(notes)
+    notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
+  end
+
+  def self.for_diff_notes(notes)
+    notes.group_by(&:line_code).values.map { |notes| new(notes) }
+  end
+
+  def initialize(notes)
+    @first_note = notes.first
+    @notes = notes
+  end
+
+  def id
+    first_note.discussion_id
+  end
+
+  def diff_discussion?
+    first_note.diff_note?
+  end
+
+  def legacy_diff_discussion?
+    notes.any?(&:legacy_diff_note?)
+  end
+
+  def for_target?(target)
+    self.noteable == target && !diff_discussion?
+  end
+
+  def active?
+    return @active if defined?(@active)
+
+    @active = first_note.active?
+  end
+
+  def expanded?
+    !diff_discussion? || active?
+  end
+
+  def reply_attributes
+    data = {
+      noteable_type: first_note.noteable_type,
+      noteable_id:   first_note.noteable_id,
+      commit_id:     first_note.commit_id,
+      discussion_id: self.id,
+    }
+
+    if diff_discussion?
+      data[:note_type] = first_note.type
+
+      data.merge!(first_note.diff_attributes)
+    end
+
+    data
+  end
+
+  # Returns an array of at most 16 highlighted lines above a diff note
+  def truncated_diff_lines
+    prev_lines = []
+
+    highlighted_diff_lines.each do |line|
+      if line.meta?
+        prev_lines.clear
+      else
+        prev_lines << line
+
+        break if for_line?(line)
+
+        prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
+      end
+    end
+
+    prev_lines
+  end
+end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index ac3a571a1f3aa12f1a9209de40e124190354c788..baed106e8c884e313ddc587f9572d107da840f5a 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -3,6 +3,8 @@ class Environment < ActiveRecord::Base
 
   has_many :deployments
 
+  before_validation :nullify_external_url
+
   validates :name,
             presence: true,
             uniqueness: { scope: :project_id },
@@ -10,7 +12,17 @@ class Environment < ActiveRecord::Base
             format: { with: Gitlab::Regex.environment_name_regex,
                       message: Gitlab::Regex.environment_name_regex_message }
 
+  validates :external_url,
+            uniqueness: { scope: :project_id },
+            length: { maximum: 255 },
+            allow_nil: true,
+            addressable_url: true
+
   def last_deployment
     deployments.last
   end
+
+  def nullify_external_url
+    self.external_url = nil if self.external_url.blank?
+  end
 end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 60abd47409e5bc51dbab480bbeb4192f44723814..11f734cfc6d309de607c8a09338c6370d24eb6ef 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -6,6 +6,7 @@ class Issue < ActiveRecord::Base
   include Referable
   include Sortable
   include Taskable
+  include Spammable
 
   DueDateStruct = Struct.new(:title, :name).freeze
   NoDueDate     = DueDateStruct.new('No Due Date', '0').freeze
@@ -52,10 +53,50 @@ class Issue < ActiveRecord::Base
     attributes
   end
 
+  class << self
+    private
+
+    # Returns the project that the current scope belongs to if any, nil otherwise.
+    #
+    # Examples:
+    # - my_project.issues.without_due_date.owner_project => my_project
+    # - Issue.all.owner_project => nil
+    def owner_project
+      # No owner if we're not being called from an association
+      return unless all.respond_to?(:proxy_association)
+
+      owner = all.proxy_association.owner
+
+      # Check if the association is or belongs to a project
+      if owner.is_a?(Project)
+        owner
+      else
+        begin
+          owner.association(:project).target
+        rescue ActiveRecord::AssociationNotFoundError
+          nil
+        end
+      end
+    end
+  end
+
   def self.visible_to_user(user)
     return where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank?
     return all if user.admin?
 
+    # Check if we are scoped to a specific project's issues
+    if owner_project
+      if owner_project.authorized_for_user?(user, Gitlab::Access::REPORTER)
+        # If the project is authorized for the user, they can see all issues in the project
+        return all
+      else
+        # else only non confidential and authored/assigned to them
+        return where('issues.confidential IS NULL OR issues.confidential IS FALSE
+          OR issues.author_id = :user_id OR issues.assignee_id = :user_id',
+          user_id: user.id)
+      end
+    end
+
     where('
       issues.confidential IS NULL
       OR issues.confidential IS FALSE
@@ -189,6 +230,34 @@ class Issue < ActiveRecord::Base
       self.closed_by_merge_requests(current_user).empty?
   end
 
+  # Returns `true` if the current issue can be viewed by either a logged in User
+  # or an anonymous user.
+  def visible_to_user?(user = nil)
+    user ? readable_by?(user) : publicly_visible?
+  end
+
+  # Returns `true` if the given User can read the current Issue.
+  def readable_by?(user)
+    if user.admin?
+      true
+    elsif project.owner == user
+      true
+    elsif confidential?
+      author == user ||
+        assignee == user ||
+        project.team.member?(user, Gitlab::Access::REPORTER)
+    else
+      project.public? ||
+        project.internal? && !user.external? ||
+        project.team.member?(user)
+    end
+  end
+
+  # Returns `true` if this Issue is visible to everybody.
+  def publicly_visible?
+    project.public? && !confidential?
+  end
+
   def overdue?
     due_date.try(:past?) || false
   end
diff --git a/app/models/key.rb b/app/models/key.rb
index b9bc38a04364d7fafb5233ac64ee3798ac4a645b..568a60b8af3b53c8143e1640a1bec4ebe39bb2d1 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -26,8 +26,9 @@ class Key < ActiveRecord::Base
   end
 
   def publishable_key
-    # Removes anything beyond the keytype and key itself
-    self.key.split[0..1].join(' ')
+    # Strip out the keys comment so we don't leak email addresses
+    # Replace with simple ident of user_name (hostname)
+    self.key.split[0..1].push("#{self.user_name} (#{Gitlab.config.gitlab.host})").join(' ')
   end
 
   # projects that has this key
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 47bd6eaf35f91d3570bf053e0c8bb38fce0c4ce8..51b5c2b1f4c5bac104c136ef3c645dbd24f41657 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,7 +1,9 @@
 class LabelLink < ActiveRecord::Base
+  include Importable
+
   belongs_to :target, polymorphic: true
   belongs_to :label
 
-  validates :target, presence: true
-  validates :label, presence: true
+  validates :target, presence: true, unless: :importing?
+  validates :label, presence: true, unless: :importing?
 end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 04a651d50abd4e99c5b6326f2bab10e4e23c219b..865712268a018bbdaccb4f21ea93d6c4943a501c 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -25,6 +25,14 @@ class LegacyDiffNote < Note
     @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
   end
 
+  def project_repository
+    if RequestStore.active?
+      RequestStore.fetch("project:#{project_id}:repository") { self.project.repository }
+    else
+      self.project.repository
+    end
+  end
+
   def diff_file_hash
     line_code.split('_')[0] if line_code
   end
@@ -34,7 +42,7 @@ class LegacyDiffNote < Note
   end
 
   def diff_file
-    @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff
+    @diff_file ||= Gitlab::Diff::File.new(diff, repository: project_repository) if diff
   end
 
   def diff_line
diff --git a/app/models/member.rb b/app/models/member.rb
index 44db3d977faf05c922e088c5073b48939b0202a2..24ab1276ee936f9a81098648802df78c9a8e585a 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -53,6 +53,10 @@ class Member < ActiveRecord::Base
   default_value_for :notification_level, NotificationSetting.levels[:global]
 
   class << self
+    def access_for_user_ids(user_ids)
+      where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
+    end
+
     def find_by_invite_token(invite_token)
       invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
       find_by(invite_token: invite_token)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 471e32f3b60d3f78feac807b429d62452688176f..a99c4ba52a4f536fec26b738d491f4e7f7ff0a01 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -238,11 +238,11 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def target_branch_sha
-    target_branch_head.try(:sha)
+    @target_branch_sha || target_branch_head.try(:sha)
   end
 
   def source_branch_sha
-    source_branch_head.try(:sha)
+    @source_branch_sha || source_branch_head.try(:sha)
   end
 
   def diff_refs
@@ -255,6 +255,19 @@ class MergeRequest < ActiveRecord::Base
     )
   end
 
+  # Return diff_refs instance trying to not touch the git repository
+  def diff_sha_refs
+    if merge_request_diff && merge_request_diff.diff_refs_by_sha?
+      return Gitlab::Diff::DiffRefs.new(
+        base_sha:  merge_request_diff.base_commit_sha,
+        start_sha: merge_request_diff.start_commit_sha,
+        head_sha:  merge_request_diff.head_commit_sha
+      )
+    else
+      diff_refs
+    end
+  end
+
   def validate_branches
     if target_project == source_project && target_branch == source_branch
       errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -659,7 +672,7 @@ class MergeRequest < ActiveRecord::Base
   end
 
   def support_new_diff_notes?
-    diff_refs && diff_refs.complete?
+    diff_sha_refs && diff_sha_refs.complete?
   end
 
   def update_diff_notes_positions(old_diff_refs:, new_diff_refs:)
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index feaba925bad1197818dd18d623d0a2013b9e48c3..119266f2d2c631953ce6da7c8b65bd35c0eb8c70 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,6 +1,7 @@
 class MergeRequestDiff < ActiveRecord::Base
   include Sortable
   include Importable
+  include EncodingHelper
 
   # Prevent store of diff if commits amount more then 500
   COMMITS_SAFE_SIZE = 100
@@ -81,6 +82,10 @@ class MergeRequestDiff < ActiveRecord::Base
     project.commit(self.head_commit_sha)
   end
 
+  def diff_refs_by_sha?
+    base_commit_sha? && head_commit_sha? && start_commit_sha?
+  end
+
   def compare
     @compare ||=
       begin
@@ -211,6 +216,14 @@ class MergeRequestDiff < ActiveRecord::Base
     branch_base_commit.try(:sha)
   end
 
+  def utf8_st_diffs
+    st_diffs.map do |diff|
+      diff.each do |k, v|
+        diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
+      end
+    end
+  end
+
   #
   # #save or #update_attributes providing changes on serialized attributes do a lot of
   # serialization and deserialization calls resulting in bad performance.
diff --git a/app/models/note.rb b/app/models/note.rb
index 0ce10c77de92dacd1ff627ad3fb246d96e94fff1..b6b2ac6aa42f8a1db950833ab5be79ba28cf018e 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -69,7 +69,7 @@ class Note < ActiveRecord::Base
              project: [:project_members, { group: [:group_members] }])
   end
 
-  before_validation :clear_blank_line_code!
+  before_validation :nullify_blank_type, :nullify_blank_line_code
   after_save :keep_around_commit
 
   class << self
@@ -82,11 +82,12 @@ class Note < ActiveRecord::Base
     end
 
     def discussions
-      all.group_by(&:discussion_id).values
+      Discussion.for_notes(all)
     end
 
-    def grouped_diff_notes
-      diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
+    def grouped_diff_discussions
+      notes = diff_notes.fresh.select(&:active?)
+      Discussion.for_diff_notes(notes).map { |d| [d.line_code, d] }.to_h
     end
 
     # Searches for notes matching the given query.
@@ -216,10 +217,6 @@ class Note < ActiveRecord::Base
     !system?
   end
 
-  def clear_blank_line_code!
-    self.line_code = nil if self.line_code.blank?
-  end
-
   def can_be_award_emoji?
     noteable.is_a?(Awardable)
   end
@@ -237,4 +234,12 @@ class Note < ActiveRecord::Base
   def keep_around_commit
     project.repository.keep_around(self.commit_id)
   end
+
+  def nullify_blank_type
+    self.type = nil if self.type.blank?
+  end
+
+  def nullify_blank_line_code
+    self.line_code = nil if self.line_code.blank?
+  end
 end
diff --git a/app/models/project.rb b/app/models/project.rb
index a805f5d97bc61bab97d16404cf5beb561bb220df..83b848ded8be5338965360799bd010d23697f8cc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -429,6 +429,17 @@ class Project < ActiveRecord::Base
     repository.commit(ref)
   end
 
+  # ref can't be HEAD, can only be branch/tag name or SHA
+  def latest_successful_builds_for(ref = default_branch)
+    latest_pipeline = pipelines.latest_successful_for(ref).first
+
+    if latest_pipeline
+      latest_pipeline.builds.latest.with_artifacts
+    else
+      builds.none
+    end
+  end
+
   def merge_base_commit(first_commit_id, second_commit_id)
     sha = repository.merge_base(first_commit_id, second_commit_id)
     repository.commit(sha) if sha
@@ -440,7 +451,9 @@ class Project < ActiveRecord::Base
 
   def add_import_job
     if forked?
-      job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
+      job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
+                                                  forked_from_project.path_with_namespace,
+                                                  self.namespace.path)
     else
       job_id = RepositoryImportWorker.perform_async(self.id)
     end
@@ -573,7 +586,11 @@ class Project < ActiveRecord::Base
   end
 
   def to_param
-    path
+    if persisted? && errors.include?(:path)
+      path_was
+    else
+      path
+    end
   end
 
   def to_reference(_from_project = nil)
@@ -588,6 +605,13 @@ class Project < ActiveRecord::Base
     web_url.split('://')[1]
   end
 
+  def new_issue_address(author)
+    if Gitlab::IncomingEmail.enabled? && author
+      Gitlab::IncomingEmail.reply_address(
+        "#{path_with_namespace}+#{author.authentication_token}")
+    end
+  end
+
   def build_commit_note(commit)
     notes.new(commit_id: commit.id, noteable_type: 'Commit')
   end
@@ -650,6 +674,22 @@ class Project < ActiveRecord::Base
     update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
   end
 
+  def external_wiki
+    if has_external_wiki.nil?
+      cache_has_external_wiki # Populate
+    end
+
+    if has_external_wiki
+      @external_wiki ||= services.external_wikis.first
+    else
+      nil
+    end
+  end
+
+  def cache_has_external_wiki
+    update_column(:has_external_wiki, services.external_wikis.any?)
+  end
+
   def build_missing_services
     services_templates = Service.where(template: true)
 
@@ -834,14 +874,6 @@ class Project < ActiveRecord::Base
     ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
   end
 
-  def developers_can_push_to_protected_branch?(branch_name)
-    protected_branches.matching(branch_name).any?(&:developers_can_push)
-  end
-
-  def developers_can_merge_to_protected_branch?(branch_name)
-    protected_branches.matching(branch_name).any?(&:developers_can_merge)
-  end
-
   def forked?
     !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
   end
@@ -855,9 +887,13 @@ class Project < ActiveRecord::Base
     old_path_with_namespace = File.join(namespace_dir, path_was)
     new_path_with_namespace = File.join(namespace_dir, path)
 
+    Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
+
     expire_caches_before_rename(old_path_with_namespace)
 
     if has_container_registry_tags?
+      Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present"
+
       # we currently doesn't support renaming repository if it contains tags in container registry
       raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
     end
@@ -876,17 +912,22 @@ class Project < ActiveRecord::Base
         SystemHooksService.new.execute_hooks_for(self, :rename)
 
         @repository = nil
-      rescue
+      rescue => e
+        Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}"
         # Returning false does not rollback after_* transaction but gives
         # us information about failing some of tasks
         false
       end
     else
+      Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
+
       # if we cannot move namespace directory we should rollback
       # db changes in order to prevent out of sync between db and fs
       raise Exception.new('repository cannot be renamed')
     end
 
+    Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
+
     Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
   end
 
@@ -1115,7 +1156,10 @@ class Project < ActiveRecord::Base
 
   def schedule_delete!(user_id, params)
     # Queue this task for after the commit, so once we mark pending_delete it will run
-    run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }
+    run_after_commit do
+      job_id = ProjectDestroyWorker.perform_async(id, user_id, params)
+      Rails.logger.info("User #{user_id} scheduled destruction of project #{path_with_namespace} with job ID #{job_id}")
+    end
 
     update_attribute(:pending_delete, true)
   end
@@ -1164,4 +1208,84 @@ class Project < ActiveRecord::Base
   def ensure_dir_exist
     gitlab_shell.add_namespace(repository_storage_path, namespace.path)
   end
+
+  def predefined_variables
+    [
+      { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
+      { key: 'CI_PROJECT_NAME', value: path, public: true },
+      { key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
+      { key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
+      { key: 'CI_PROJECT_URL', value: web_url, public: true }
+    ]
+  end
+
+  def container_registry_variables
+    return [] unless Gitlab.config.registry.enabled
+
+    variables = [
+      { key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
+    ]
+
+    if container_registry_enabled?
+      variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_repository_url, public: true }
+    end
+
+    variables
+  end
+
+  def secret_variables
+    variables.map do |variable|
+      { key: variable.key, value: variable.value, public: false }
+    end
+  end
+
+  # Checks if `user` is authorized for this project, with at least the
+  # `min_access_level` (if given).
+  #
+  # If you change the logic of this method, please also update `User#authorized_projects`
+  def authorized_for_user?(user, min_access_level = nil)
+    return false unless user
+
+    return true if personal? && namespace_id == user.namespace_id
+
+    authorized_for_user_by_group?(user, min_access_level) ||
+      authorized_for_user_by_members?(user, min_access_level) ||
+      authorized_for_user_by_shared_projects?(user, min_access_level)
+  end
+
+  def append_or_update_attribute(name, value)
+    old_values = public_send(name.to_s)
+
+    if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
+      update_attribute(name, old_values + value)
+    else
+      update_attribute(name, value)
+    end
+  end
+
+  private
+
+  def authorized_for_user_by_group?(user, min_access_level)
+    member = user.group_members.find_by(source_id: group)
+
+    member && (!min_access_level || member.access_level >= min_access_level)
+  end
+
+  def authorized_for_user_by_members?(user, min_access_level)
+    member = members.find_by(user_id: user)
+
+    member && (!min_access_level || member.access_level >= min_access_level)
+  end
+
+  def authorized_for_user_by_shared_projects?(user, min_access_level)
+    shared_projects = user.group_members.joins(group: :shared_projects).
+      where(project_group_links: { project_id: self })
+
+    if min_access_level
+      members_scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
+      shared_projects = shared_projects.where(members: members_scope)
+    end
+
+    shared_projects.any?
+  end
 end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 23e5b16221bfb34f957c628c6b2c1432ec181091..d7c986c1a9112eb77590ae89b8e44bedd5088dc4 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -46,7 +46,7 @@ class HipchatService < Service
     return unless supported_events.include?(data[:object_kind])
     message = create_message(data)
     return unless message.present?
-    gate[room].send('GitLab', message, message_options)
+    gate[room].send('GitLab', message, message_options(data))
   end
 
   def test(data)
@@ -67,8 +67,8 @@ class HipchatService < Service
     @gate ||= HipChat::Client.new(token, options)
   end
 
-  def message_options
-    { notify: notify.present? && notify == '1', color: color || 'yellow' }
+  def message_options(data = nil)
+    { notify: notify.present? && notify == '1', color: message_color(data) }
   end
 
   def create_message(data)
@@ -240,6 +240,21 @@ class HipchatService < Service
     "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
   end
 
+  def message_color(data)
+    build_status_color(data) || color || 'yellow'
+  end
+
+  def build_status_color(data)
+    return unless data && data[:object_kind] == 'build'
+
+    case data[:commit][:status]
+    when 'success'
+      'green'
+    else
+      'red'
+    end
+  end
+
   def project_name
     project.name_with_namespace.gsub(/\s/, '')
   end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index cf9e4d5a8b60c4c018714d4ec4f01ce50e90fe8a..abbc780dc1a09fe6e45ea29102b2ea93b5049539 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -4,6 +4,9 @@ class SlackService < Service
   validates :webhook, presence: true, url: true, if: :activated?
 
   def initialize_properties
+    # Custom serialized properties initialization
+    self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
+
     if properties.nil?
       self.properties = {}
       self.notify_only_broken_builds = true
@@ -29,13 +32,15 @@ class SlackService < Service
   end
 
   def fields
-    [
-      { type: 'text', name: 'webhook',
-        placeholder: 'https://hooks.slack.com/services/...' },
-      { type: 'text', name: 'username', placeholder: 'username' },
-      { type: 'text', name: 'channel', placeholder: '#channel' },
-      { type: 'checkbox', name: 'notify_only_broken_builds' },
-    ]
+    default_fields =
+      [
+        { type: 'text', name: 'webhook',   placeholder: 'https://hooks.slack.com/services/...' },
+        { type: 'text', name: 'username', placeholder: 'username' },
+        { type: 'text', name: 'channel', placeholder: "#general" },
+        { type: 'checkbox', name: 'notify_only_broken_builds' },
+      ]
+
+    default_fields + build_event_channels
   end
 
   def supported_events
@@ -74,7 +79,10 @@ class SlackService < Service
       end
 
     opt = {}
-    opt[:channel] = channel if channel
+
+    event_channel = get_channel_field(object_kind) || channel
+
+    opt[:channel] = event_channel if event_channel
     opt[:username] = username if username
 
     if message
@@ -83,8 +91,35 @@ class SlackService < Service
     end
   end
 
+  def event_channel_names
+    supported_events.map { |event| event_channel_name(event) }
+  end
+
+  def event_field(event)
+    fields.find { |field| field[:name] == event_channel_name(event) }
+  end
+
+  def global_fields
+    fields.reject { |field| field[:name].end_with?('channel') }
+  end
+
   private
 
+  def get_channel_field(event)
+    field_name = event_channel_name(event)
+    self.public_send(field_name)
+  end
+
+  def build_event_channels
+    supported_events.reduce([]) do |channels, event|
+      channels << { type: 'text', name: event_channel_name(event), placeholder: "#general" }
+    end
+  end
+
+  def event_channel_name(event)
+    "#{event}_channel"
+  end
+
   def project_name
     project.name_with_namespace.gsub(/\s/, '')
   end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 0b700930641e32cd8021399fbe8455fb6f79cf43..19fd082534c5787ca9098ce65ceadf3d9be28bfa 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -132,39 +132,68 @@ class ProjectTeam
     Gitlab::Access.options_with_owner.key(max_member_access(user_id))
   end
 
-  # This method assumes project and group members are eager loaded for optimal
-  # performance.
-  def max_member_access(user_id)
-    access = []
+  # Determine the maximum access level for a group of users in bulk.
+  #
+  # Returns a Hash mapping user ID -> maximum access level.
+  def max_member_access_for_user_ids(user_ids)
+    user_ids = user_ids.uniq
+    key = "max_member_access:#{project.id}"
 
-    access += project.members.where(user_id: user_id).has_access.pluck(:access_level)
+    access = {}
 
-    if group
-      access += group.members.where(user_id: user_id).has_access.pluck(:access_level)
+    if RequestStore.active?
+      RequestStore.store[key] ||= {}
+      access = RequestStore.store[key]
     end
 
-    if project.invited_groups.any? && project.allowed_to_share_with_group?
-      access << max_invited_level(user_id)
+    # Lookup only the IDs we need
+    user_ids = user_ids - access.keys
+
+    if user_ids.present?
+      user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
+
+      member_access = project.members.access_for_user_ids(user_ids)
+      merge_max!(access, member_access)
+
+      if group
+        group_access = group.members.access_for_user_ids(user_ids)
+        merge_max!(access, group_access)
+      end
+
+      # Each group produces a list of maximum access level per user. We take the
+      # max of the values produced by each group.
+      if project.invited_groups.any? && project.allowed_to_share_with_group?
+        project.project_group_links.each do |group_link|
+          invited_access = max_invited_level_for_users(group_link, user_ids)
+          merge_max!(access, invited_access)
+        end
+      end
     end
 
-    access.compact.max
+    access
+  end
+
+  def max_member_access(user_id)
+    max_member_access_for_user_ids([user_id])[user_id]
   end
 
   private
 
-  def max_invited_level(user_id)
-    project.project_group_links.map do |group_link|
-      invited_group = group_link.group
-      access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
+  # For a given group, return the maximum access level for the user. This is the min of
+  # the invited access level of the group and the access level of the user within the group.
+  # For example, if the group has been given DEVELOPER access but the member has MASTER access,
+  # the user should receive only DEVELOPER access.
+  def max_invited_level_for_users(group_link, user_ids)
+    invited_group = group_link.group
+    capped_access_level = group_link.group_access
+    access = invited_group.group_members.access_for_user_ids(user_ids)
 
-      # If group member has higher access level we should restrict it
-      # to max allowed access level
-      if access && access > group_link.group_access
-        access = group_link.group_access
-      end
+    # If the user is not in the list, assume he/she does not have access
+    missing_users = user_ids - access.keys
+    missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
 
-      access
-    end.compact.max
+    # Cap the maximum access by the invited level access
+    access.each { |key, value| access[key] = [value, capped_access_level].min }
   end
 
   def fetch_members(level = nil)
@@ -173,7 +202,7 @@ class ProjectTeam
     invited_members = []
 
     if project.invited_groups.any? && project.allowed_to_share_with_group?
-      project.project_group_links.each do |group_link|
+      project.project_group_links.includes(group: [:group_members]).each do |group_link|
         invited_group = group_link.group
         im = invited_group.members
 
@@ -215,4 +244,8 @@ class ProjectTeam
   def group
     project.group
   end
+
+  def merge_max!(first_hash, second_hash)
+    first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
+  end
 end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index b7011d7afdfcd827fc52761992ded119421309fc..226b3f54342134d15ef5a96e3c2ea5e67701a0d6 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -5,6 +5,12 @@ class ProtectedBranch < ActiveRecord::Base
   validates :name, presence: true
   validates :project, presence: true
 
+  has_one :merge_access_level, dependent: :destroy
+  has_one :push_access_level, dependent: :destroy
+
+  accepts_nested_attributes_for :push_access_level
+  accepts_nested_attributes_for :merge_access_level
+
   def commit
     project.commit(self.name)
   end
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b1112ee737df98da6cac950331dba06aeec03c27
--- /dev/null
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -0,0 +1,24 @@
+class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
+  belongs_to :protected_branch
+  delegate :project, to: :protected_branch
+
+  validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
+                                                             Gitlab::Access::DEVELOPER] }
+
+  def self.human_access_levels
+    {
+      Gitlab::Access::MASTER => "Masters",
+      Gitlab::Access::DEVELOPER => "Developers + Masters"
+    }.with_indifferent_access
+  end
+
+  def check_access(user)
+    return true if user.is_admin?
+
+    project.team.max_member_access(user.id) >= access_level
+  end
+
+  def humanize
+    self.class.human_access_levels[self.access_level]
+  end
+end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6a5e49cf45394b0176075844c19f33b097a57e8f
--- /dev/null
+++ b/app/models/protected_branch/push_access_level.rb
@@ -0,0 +1,27 @@
+class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
+  belongs_to :protected_branch
+  delegate :project, to: :protected_branch
+
+  validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
+                                                             Gitlab::Access::DEVELOPER,
+                                                             Gitlab::Access::NO_ACCESS] }
+
+  def self.human_access_levels
+    {
+      Gitlab::Access::MASTER => "Masters",
+      Gitlab::Access::DEVELOPER => "Developers + Masters",
+      Gitlab::Access::NO_ACCESS => "No one"
+    }.with_indifferent_access
+  end
+
+  def check_access(user)
+    return false if access_level == Gitlab::Access::NO_ACCESS
+    return true if user.is_admin?
+
+    project.team.max_member_access(user.id) >= access_level
+  end
+
+  def humanize
+    self.class.human_access_levels[self.access_level]
+  end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 09487b62f989fda2974ba0eae1f1891956655f39..bac37483c470562ada62eadeb88d41aff641ab00 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -11,16 +11,6 @@ class Repository
 
   attr_accessor :path_with_namespace, :project
 
-  def self.clean_old_archives
-    Gitlab::Metrics.measure(:clean_old_archives) do
-      repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
-
-      return unless File.directory?(repository_downloads_path)
-
-      Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
-    end
-  end
-
   def initialize(path_with_namespace, project)
     @path_with_namespace = path_with_namespace
     @project = project
@@ -80,7 +70,12 @@ class Repository
 
   def commit(ref = 'HEAD')
     return nil unless exists?
-    commit = Gitlab::Git::Commit.find(raw_repository, ref)
+    commit =
+      if ref.is_a?(Gitlab::Git::Commit)
+        ref
+      else
+        Gitlab::Git::Commit.find(raw_repository, ref)
+      end
     commit = ::Commit.new(commit, @project) if commit
     commit
   rescue Rugged::OdbError
@@ -168,7 +163,7 @@ class Repository
     before_remove_branch
 
     branch = find_branch(branch_name)
-    oldrev = branch.try(:target)
+    oldrev = branch.try(:target).try(:id)
     newrev = Gitlab::Git::BLANK_SHA
     ref    = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
 
@@ -216,11 +211,23 @@ class Repository
 
     return if kept_around?(sha)
 
-    rugged.references.create(keep_around_ref_name(sha), sha)
+    # This will still fail if the file is corrupted (e.g. 0 bytes)
+    begin
+      rugged.references.create(keep_around_ref_name(sha), sha, force: true)
+    rescue Rugged::ReferenceError => ex
+      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
+    rescue Rugged::OSError => ex
+      raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+      Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
+    end
   end
 
   def kept_around?(sha)
-    ref_exists?(keep_around_ref_name(sha))
+    begin
+      ref_exists?(keep_around_ref_name(sha))
+    rescue Rugged::ReferenceError
+      false
+    end
   end
 
   def tag_names
@@ -257,10 +264,10 @@ class Repository
       # Rugged seems to throw a `ReferenceError` when given branch_names rather
       # than SHA-1 hashes
       number_commits_behind = raw_repository.
-        count_commits_between(branch.target, root_ref_hash)
+        count_commits_between(branch.target.sha, root_ref_hash)
 
       number_commits_ahead = raw_repository.
-        count_commits_between(root_ref_hash, branch.target)
+        count_commits_between(root_ref_hash, branch.target.sha)
 
       { behind: number_commits_behind, ahead: number_commits_ahead }
     end
@@ -392,6 +399,11 @@ class Repository
 
     expire_cache if exists?
 
+    # expire cache that don't depend on repository data (when expiring)
+    expire_tags_cache
+    expire_tag_count_cache
+    expire_branches_cache
+    expire_branch_count_cache
     expire_root_ref_cache
     expire_emptiness_caches
     expire_exists_cache
@@ -606,11 +618,13 @@ class Repository
   # Remove archives older than 2 hours
   def branches_sorted_by(value)
     case value
-    when 'recently_updated'
+    when 'name'
+      branches.sort_by(&:name)
+    when 'updated_desc'
       branches.sort do |a, b|
         commit(b.target).committed_date <=> commit(a.target).committed_date
       end
-    when 'last_updated'
+    when 'updated_asc'
       branches.sort do |a, b|
         commit(a.target).committed_date <=> commit(b.target).committed_date
       end
@@ -679,9 +693,7 @@ class Repository
   end
 
   def local_branches
-    @local_branches ||= rugged.branches.each(:local).map do |branch|
-      Gitlab::Git::Branch.new(branch.name, branch.target)
-    end
+    @local_branches ||= raw_repository.local_branches
   end
 
   alias_method :branches, :local_branches
@@ -704,6 +716,7 @@ class Repository
       options[:commit] = {
         message: message,
         branch: ref,
+        update_ref: false,
       }
 
       raw_repository.mkdir(path, options)
@@ -719,6 +732,7 @@ class Repository
       options[:commit] = {
         message: message,
         branch: ref,
+        update_ref: false,
       }
 
       options[:file] = {
@@ -731,6 +745,33 @@ class Repository
     end
   end
 
+  def update_file(user, path, content, branch:, previous_path:, message:)
+    commit_with_hooks(user, branch) do |ref|
+      committer = user_to_committer(user)
+      options = {}
+      options[:committer] = committer
+      options[:author] = committer
+      options[:commit] = {
+        message: message,
+        branch: ref,
+        update_ref: false
+      }
+
+      options[:file] = {
+        content: content,
+        path: path,
+        update: true
+      }
+
+      if previous_path
+        options[:file][:previous_path] = previous_path
+        Gitlab::Git::Blob.rename(raw_repository, options)
+      else
+        Gitlab::Git::Blob.commit(raw_repository, options)
+      end
+    end
+  end
+
   def remove_file(user, path, message, branch)
     commit_with_hooks(user, branch) do |ref|
       committer = user_to_committer(user)
@@ -739,7 +780,8 @@ class Repository
       options[:author] = committer
       options[:commit] = {
         message: message,
-        branch: ref
+        branch: ref,
+        update_ref: false,
       }
 
       options[:file] = {
@@ -779,11 +821,10 @@ class Repository
     merge_index = rugged.merge_commits(our_commit, their_commit)
     return false if merge_index.conflicts?
 
-    commit_with_hooks(user, merge_request.target_branch) do |tmp_ref|
+    commit_with_hooks(user, merge_request.target_branch) do
       actual_options = options.merge(
         parents: [our_commit, their_commit],
         tree: merge_index.write_tree(rugged),
-        update_ref: tmp_ref
       )
 
       commit_id = Rugged::Commit.create(rugged, actual_options)
@@ -793,30 +834,29 @@ class Repository
   end
 
   def revert(user, commit, base_branch, revert_tree_id = nil)
-    source_sha = find_branch(base_branch).target
+    source_sha = find_branch(base_branch).target.sha
     revert_tree_id ||= check_revert_content(commit, base_branch)
 
     return false unless revert_tree_id
 
-    commit_with_hooks(user, base_branch) do |ref|
+    commit_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.revert_message,
         author: committer,
         committer: committer,
         tree: revert_tree_id,
-        parents: [rugged.lookup(source_sha)],
-        update_ref: ref)
+        parents: [rugged.lookup(source_sha)])
     end
   end
 
   def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
-    source_sha = find_branch(base_branch).target
+    source_sha = find_branch(base_branch).target.sha
     cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
 
     return false unless cherry_pick_tree_id
 
-    commit_with_hooks(user, base_branch) do |ref|
+    commit_with_hooks(user, base_branch) do
       committer = user_to_committer(user)
       source_sha = Rugged::Commit.create(rugged,
         message: commit.message,
@@ -827,13 +867,12 @@ class Repository
         },
         committer: committer,
         tree: cherry_pick_tree_id,
-        parents: [rugged.lookup(source_sha)],
-        update_ref: ref)
+        parents: [rugged.lookup(source_sha)])
     end
   end
 
   def check_revert_content(commit, base_branch)
-    source_sha = find_branch(base_branch).target
+    source_sha = find_branch(base_branch).target.sha
     args       = [commit.id, source_sha]
     args << { mainline: 1 } if commit.merge_commit?
 
@@ -847,7 +886,7 @@ class Repository
   end
 
   def check_cherry_pick_content(commit, base_branch)
-    source_sha = find_branch(base_branch).target
+    source_sha = find_branch(base_branch).target.sha
     args       = [commit.id, source_sha]
     args << 1 if commit.merge_commit?
 
@@ -929,20 +968,6 @@ class Repository
     Gitlab::Popen.popen(args, path_to_repo)
   end
 
-  def with_tmp_ref(oldrev = nil)
-    random_string = SecureRandom.hex
-    tmp_ref = "refs/tmp/#{random_string}/head"
-
-    if oldrev && !Gitlab::Git.blank_ref?(oldrev)
-      rugged.references.create(tmp_ref, oldrev)
-    end
-
-    # Make commit in tmp ref
-    yield(tmp_ref)
-  ensure
-    rugged.references.delete(tmp_ref) rescue nil
-  end
-
   def commit_with_hooks(current_user, branch)
     update_autocrlf_option
 
@@ -952,36 +977,38 @@ class Repository
     was_empty = empty?
 
     if !was_empty && target_branch
-      oldrev = target_branch.target
+      oldrev = target_branch.target.id
     end
 
-    with_tmp_ref(oldrev) do |tmp_ref|
-      # Make commit in tmp ref
-      newrev = yield(tmp_ref)
+    # Make commit
+    newrev = yield(ref)
 
-      unless newrev
-        raise CommitError.new('Failed to create commit')
-      end
+    unless newrev
+      raise CommitError.new('Failed to create commit')
+    end
+
+    GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
+      if was_empty || !target_branch
+        # Create branch
+        rugged.references.create(ref, newrev)
+
+        # If repo was empty expire cache
+        after_create if was_empty
+        after_create_branch
+      else
+        # Update head
+        current_head = find_branch(branch).target.id
 
-      GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
-        if was_empty || !target_branch
-          # Create branch
-          rugged.references.create(ref, newrev)
+        # Make sure target branch was not changed during pre-receive hook
+        if current_head == oldrev
+          rugged.references.update(ref, newrev)
         else
-          # Update head
-          current_head = find_branch(branch).target
-
-          # Make sure target branch was not changed during pre-receive hook
-          if current_head == oldrev
-            rugged.references.update(ref, newrev)
-          else
-            raise CommitError.new('Commit was rejected because branch received new push')
-          end
+          raise CommitError.new('Commit was rejected because branch received new push')
         end
       end
-
-      newrev
     end
+
+    newrev
   end
 
   def ls_files(ref)
@@ -1016,7 +1043,7 @@ class Repository
   private
 
   def cache
-    @cache ||= RepositoryCache.new(path_with_namespace)
+    @cache ||= RepositoryCache.new(path_with_namespace, @project.id)
   end
 
   def head_exists?
@@ -1028,7 +1055,7 @@ class Repository
   end
 
   def tags_sorted_by_committed_date
-    tags.sort_by { |tag| commit(tag.target).committed_date }
+    tags.sort_by { |tag| tag.target.committed_date }
   end
 
   def keep_around_ref_name(sha)
diff --git a/app/models/service.rb b/app/models/service.rb
index 5432f8c7ab43ebb648fda1ab30a183da61972e84..40cd9b861f0c2e29f0179990122831e9de85ca66 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -17,6 +17,7 @@ class Service < ActiveRecord::Base
 
   after_commit :reset_updated_properties
   after_commit :cache_project_has_external_issue_tracker
+  after_commit :cache_project_has_external_wiki
 
   belongs_to :project, inverse_of: :services
   has_one :service_hook
@@ -25,6 +26,7 @@ class Service < ActiveRecord::Base
 
   scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
   scope :issue_trackers, -> { where(category: 'issue_tracker') }
+  scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
   scope :active, -> { where(active: true) }
   scope :without_defaults, -> { where(default: false) }
 
@@ -80,6 +82,18 @@ class Service < ActiveRecord::Base
     Gitlab::PushDataBuilder.build_sample(project, user)
   end
 
+  def event_channel_names
+    []
+  end
+
+  def event_field(event)
+    nil
+  end
+
+  def global_fields
+    fields
+  end
+
   def supported_events
     %w(push tag_push issue merge_request wiki_page)
   end
@@ -212,4 +226,10 @@ class Service < ActiveRecord::Base
       project.cache_has_external_issue_tracker
     end
   end
+
+  def cache_project_has_external_wiki
+    if project && !project.destroyed?
+      project.cache_has_external_wiki
+    end
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 3d0a033785caf04ca5e4fcee235dc9c2fea3dc2d..db7474349598bce9670532c851ebb44d518e66c4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -111,7 +111,7 @@ class User < ActiveRecord::Base
   validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
 
   before_validation :generate_password, on: :create
-  before_validation :restricted_signup_domains, on: :create
+  before_validation :signup_domain_valid?, on: :create
   before_validation :sanitize_attrs
   before_validation :set_notification_email, if: ->(user) { user.email_changed? }
   before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
@@ -412,6 +412,8 @@ class User < ActiveRecord::Base
   end
 
   # Returns projects user is authorized to access.
+  #
+  # If you change the logic of this method, please also update `Project#authorized_for_user`
   def authorized_projects(min_access_level = nil)
     Project.where("projects.id IN (#{projects_union(min_access_level).to_sql})")
   end
@@ -760,29 +762,6 @@ class User < ActiveRecord::Base
     Project.where(id: events)
   end
 
-  def restricted_signup_domains
-    email_domains = current_application_settings.restricted_signup_domains
-
-    unless email_domains.blank?
-      match_found = email_domains.any? do |domain|
-        escaped = Regexp.escape(domain).gsub('\*', '.*?')
-        regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
-        email_domain = Mail::Address.new(self.email).domain
-        email_domain =~ regexp
-      end
-
-      unless match_found
-        self.errors.add :email,
-                        'is not whitelisted. ' +
-                        'Email domains valid for registration are: ' +
-                        email_domains.join(', ')
-        return false
-      end
-    end
-
-    true
-  end
-
   def can_be_removed?
     !solo_owned_groups.present?
   end
@@ -854,7 +833,7 @@ class User < ActiveRecord::Base
                  groups.joins(:shared_projects).select(:project_id)]
 
     if min_access_level
-      scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
+      scope = { access_level: Gitlab::Access.all_values.select { |access| access >= min_access_level } }
       relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
     end
 
@@ -881,4 +860,40 @@ class User < ActiveRecord::Base
     self.can_create_group   = false
     self.projects_limit     = 0
   end
+
+  def signup_domain_valid?
+    valid = true
+    error = nil
+
+    if current_application_settings.domain_blacklist_enabled?
+      blocked_domains = current_application_settings.domain_blacklist
+      if domain_matches?(blocked_domains, self.email)
+        error = 'is not from an allowed domain.'
+        valid = false
+      end
+    end
+
+    allowed_domains = current_application_settings.domain_whitelist
+    unless allowed_domains.blank?
+      if domain_matches?(allowed_domains, self.email)
+        valid = true
+      else
+        error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}"
+        valid = false
+      end
+    end
+
+    self.errors.add(:email, error) unless valid
+
+    valid
+  end
+
+  def domain_matches?(email_domains, email)
+    signup_domain = Mail::Address.new(email).domain
+    email_domains.any? do |domain|
+      escaped = Regexp.escape(domain).gsub('\*', '.*?')
+      regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
+      signup_domain =~ regexp
+    end
+  end
 end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 3d5fd9d3ee96291b4be9933dd07076f3c84cc473..c3de278f5b7ecd14f1d3c029b46e7d9d53f8532f 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -44,7 +44,11 @@ class WikiPage
 
   # The escaped URL path of this page.
   def slug
-    @attributes[:slug]
+    if @attributes[:slug].present?
+      @attributes[:slug]
+    else
+      wiki.wiki.preview_page(title, '', format).url_path
+    end
   end
 
   alias_method :to_param, :slug
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index e294a96235299d1441862485745f1951dc16e4fb..6072123b851e3ffb1d9c6dab9322e9f407aafd4e 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -24,10 +24,14 @@ module Auth
       token[:access] = names.map do |name|
         { type: 'repository', name: name, actions: %w(*) }
       end
-      
+
       token.encoded
     end
 
+    def self.token_expire_at
+      Time.now + current_application_settings.container_registry_token_expire_delay.minutes
+    end
+
     private
 
     def authorized_token(*accesses)
@@ -35,7 +39,7 @@ module Auth
       token.issuer = registry.issuer
       token.audience = params[:service]
       token.subject = current_user.try(:username)
-      token.expire_time = ContainerRegistryAuthenticationService.token_expire_at
+      token.expire_time = self.class.token_expire_at
       token[:access] = accesses.compact
       token
     end
@@ -81,9 +85,5 @@ module Auth
     def registry
       Gitlab.config.registry
     end
-
-    def self.token_expire_at
-      Time.now + current_application_settings.container_registry_token_expire_delay.minutes
-    end
   end
 end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 3b21f0acb966c9f1d9715a50891ad09f76ff34b4..4946f7076fdd515ee1781bccd9bf2af28336ca1d 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -15,7 +15,7 @@ module Ci
           status == 'success'
         when 'on_failure'
           status == 'failed'
-        when 'always'
+        when 'always', 'manual'
           %w(success failed).include?(status)
         end
       end
@@ -47,6 +47,10 @@ module Ci
                            user: user,
                            project: @pipeline.project)
 
+        # TODO: The proper implementation for this is in
+        # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
+        build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
+
         ##
         # We do not persist new builds here.
         # Those will be persisted when @pipeline is saved.
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index d874582d54ffee63a64cd3a1e2a5fc0f053abd89..757fc35a78fdf54f0a6c58498c6f371dbf94b48e 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -15,21 +15,19 @@ class CreateBranchService < BaseService
       return error('Branch already exists')
     end
 
-    new_branch = nil
-
-    if source_project != @project
-      repository.with_tmp_ref do |tmp_ref|
-        repository.fetch_ref(
-          source_project.repository.path_to_repo,
-          "refs/heads/#{ref}",
-          tmp_ref
-        )
-
-        new_branch = repository.add_branch(current_user, branch_name, tmp_ref)
-      end
-    else
-      new_branch = repository.add_branch(current_user, branch_name, ref)
-    end
+    new_branch = if source_project != @project
+                   repository.fetch_ref(
+                     source_project.repository.path_to_repo,
+                     "refs/heads/#{ref}",
+                     "refs/heads/#{branch_name}"
+                   )
+
+                   repository.after_create_branch
+
+                   repository.find_branch(branch_name)
+                 else
+                   repository.add_branch(current_user, branch_name, ref)
+                 end
 
     if new_branch
       success(new_branch)
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 332c55581a1a09d30a073a417f131d3b9eeb78dd..87f066edb6f10f58f1c2d72d54e096851156e00a 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -40,6 +40,6 @@ class DeleteBranchService < BaseService
 
   def build_push_data(branch)
     Gitlab::PushDataBuilder
-      .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
+      .build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
   end
 end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
index 1e41fbe34b614fd3549cfb4daf15cba90b2b55ac..32e0eed6b63644116f74a0391aa49f5299cafc70 100644
--- a/app/services/delete_tag_service.rb
+++ b/app/services/delete_tag_service.rb
@@ -34,6 +34,6 @@ class DeleteTagService < BaseService
 
   def build_push_data(tag)
     Gitlab::PushDataBuilder
-      .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+      .build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
   end
 end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 55da949f56aee8a4561d83a50455fe625ee4c164..c4a206f785e3b493b30a6b1abc74ec5ddf668902 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -9,12 +9,14 @@ module Files
 
       @commit_message = params[:commit_message]
       @file_path      = params[:file_path]
+      @previous_path  = params[:previous_path]
       @file_content   = if params[:file_content_encoding] == 'base64'
                           Base64.decode64(params[:file_content])
                         else
                           params[:file_content]
                         end
 
+      # Validate parameters
       validate
 
       # Create new branch if it different from source_branch
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 1960dc7d949c1abb2612d024227da98050a1c5e9..8d2b5083179e0ed50c507a355e9067a6d5d05d67 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,7 +3,10 @@ require_relative "base_service"
 module Files
   class UpdateService < Files::BaseService
     def commit
-      repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, true)
+      repository.update_file(current_user, @file_path, @file_content,
+                             branch: @target_branch,
+                             previous_path: @previous_path,
+                             message: @commit_message)
     end
   end
 end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e02b50ff9a2d8440bc1ffdc582699f70439e0ca8..3f6a177bf3aff35d55ed58dfa1c05fb26bd66c67 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -88,9 +88,18 @@ class GitPushService < BaseService
 
     # Set protection on the default branch if configured
     if current_application_settings.default_branch_protection != PROTECTION_NONE
-      developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
-      developers_can_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? true : false
-      @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push, developers_can_merge: developers_can_merge })
+
+      params = {
+        name: @project.default_branch,
+        push_access_level_attributes: {
+          access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+        },
+        merge_access_level_attributes: {
+          access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+        }
+      }
+
+      ProtectedBranches::CreateService.new(@project, current_user, params).execute
     end
   end
 
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 585730780485b234230b8fc39f8a76d1fc52add8..969530c4fdc3839dd965aa7f6536205185a66d66 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -26,8 +26,8 @@ class GitTagPushService < BaseService
     unless Gitlab::Git.blank_ref?(params[:newrev])
       tag_name = Gitlab::Git.ref_name(params[:ref])
       tag = project.repository.find_tag(tag_name)
-      
-      if tag && tag.target == params[:newrev]
+
+      if tag && tag.object_sha == params[:newrev]
         commit = project.commit(tag.target)
         commits = [commit].compact
         message = tag.message
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e63e1af876640b87b95687a43a65baa0b980c4da..5e2de2ccf64528942cddf0482320ac5f5d7a2483 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -2,10 +2,14 @@ module Issues
   class CreateService < Issues::BaseService
     def execute
       filter_params
-      label_params = params[:label_ids]
-      issue = project.issues.new(params.except(:label_ids))
+      label_params = params.delete(:label_ids)
+      request = params.delete(:request)
+      api = params.delete(:api)
+      issue = project.issues.new(params)
       issue.author = params[:author] || current_user
 
+      issue.spam = spam_check_service.execute(request, api)
+
       if issue.save
         issue.update_attributes(label_ids: label_params)
         notification_service.new_issue(issue, current_user)
@@ -17,5 +21,11 @@ module Issues
 
       issue
     end
+
+    private
+
+    def spam_check_service
+      SpamCheckService.new(project, current_user, params)
+    end
   end
 end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index bc3606a14c27ea3e95ee73470d8e3c920794f7c6..ba424b09463a83f7116ce8a6c80d4db44e9a8c34 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -17,16 +17,19 @@ module MergeRequests
       end
     end
 
-    def hook_data(merge_request, action)
+    def hook_data(merge_request, action, oldrev = nil)
       hook_data = merge_request.to_hook_data(current_user)
       hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
       hook_data[:object_attributes][:action] = action
+      if oldrev && !Gitlab::Git.blank_ref?(oldrev)
+        hook_data[:object_attributes][:oldrev] = oldrev
+      end
       hook_data
     end
 
-    def execute_hooks(merge_request, action = 'open')
+    def execute_hooks(merge_request, action = 'open', oldrev = nil)
       if merge_request.project
-        merge_data = hook_data(merge_request, action)
+        merge_data = hook_data(merge_request, action, oldrev)
         merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
         merge_request.project.execute_services(merge_data, :merge_request_hooks)
       end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 0dac0614141b69cc350a8bbb0ee11fae4d332a17..b037780c431624f8ca9e0ed4e6abe90265bed696 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -35,7 +35,13 @@ module MergeRequests
       }
 
       commit_id = repository.merge(current_user, merge_request, options)
-      merge_request.update(merge_commit_sha: commit_id)
+
+      if commit_id
+        merge_request.update(merge_commit_sha: commit_id)
+      else
+        merge_request.update(merge_error: 'Conflicts detected during merge')
+        false
+      end
     rescue GitHooksService::PreReceiveError => e
       merge_request.update(merge_error: e.message)
       false
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 1daf6bbf553a8f23e628ebb84cfa926edd9a6387..5cedd6f11d9e06c9b8869889c0708edc947ecfdf 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -137,7 +137,7 @@ module MergeRequests
     # Call merge request webhook with update branches
     def execute_mr_web_hooks
       merge_requests_for_source_branch.each do |merge_request|
-        execute_hooks(merge_request, 'update')
+        execute_hooks(merge_request, 'update', @oldrev)
       end
     end
 
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 998789d64d226a987f7c48313e9af16bbe485c08..06252c7b625a4df7b87fcb98c60c058de7c76002 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -9,7 +9,7 @@ module Projects
       private
 
       def save_all
-        if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
+        if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
           Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
           notify_success
         else
@@ -21,6 +21,10 @@ module Projects
         Gitlab::ImportExport::VersionSaver.new(shared: @shared)
       end
 
+      def avatar_saver
+        Gitlab::ImportExport::AvatarSaver.new(project: project, shared: @shared)
+      end
+
       def project_tree_saver
         Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared)
       end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f06311511cce7cc782c1e8748f8a9cc330b7de0a..921ca6748d3c6cd9dbb10298bbeed6c3e6ebf3d1 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,7 +3,7 @@ module Projects
     def execute
       # check that user is allowed to set specified visibility_level
       new_visibility = params[:visibility_level]
-      
+
       if new_visibility && new_visibility.to_i != project.visibility_level
         unless can?(current_user, :change_visibility_level, project) &&
           Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6150a2a83c93fa0d8aef395c7ec062bc4f3934b7
--- /dev/null
+++ b/app/services/protected_branches/create_service.rb
@@ -0,0 +1,27 @@
+module ProtectedBranches
+  class CreateService < BaseService
+    attr_reader :protected_branch
+
+    def execute
+      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+
+      protected_branch = project.protected_branches.new(params)
+
+      ProtectedBranch.transaction do
+        protected_branch.save!
+
+        if protected_branch.push_access_level.blank?
+          protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER)
+        end
+
+        if protected_branch.merge_access_level.blank?
+          protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER)
+        end
+      end
+
+      protected_branch
+    rescue ActiveRecord::RecordInvalid
+      protected_branch
+    end
+  end
+end
diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89d8ba601345f7cea3f7921aea14da7e1428c19b
--- /dev/null
+++ b/app/services/protected_branches/update_service.rb
@@ -0,0 +1,13 @@
+module ProtectedBranches
+  class UpdateService < BaseService
+    attr_reader :protected_branch
+
+    def execute(protected_branch)
+      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+
+      @protected_branch = protected_branch
+      @protected_branch.update(params)
+      @protected_branch
+    end
+  end
+end
diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0b56b09738d7ce2ba6853340958801653d815789
--- /dev/null
+++ b/app/services/repository_archive_clean_up_service.rb
@@ -0,0 +1,33 @@
+class RepositoryArchiveCleanUpService
+  LAST_MODIFIED_TIME_IN_MINUTES = 120
+
+  def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
+    @mmin = mmin
+    @path = Gitlab.config.gitlab.repository_downloads_path
+  end
+
+  def execute
+    Gitlab::Metrics.measure(:repository_archive_clean_up) do
+      return unless File.directory?(path)
+
+      clean_up_old_archives
+      clean_up_empty_directories
+    end
+  end
+
+  private
+
+  attr_reader :mmin, :path
+
+  def clean_up_old_archives
+    run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete))
+  end
+
+  def clean_up_empty_directories
+    run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete))
+  end
+
+  def run(cmd)
+    Gitlab::Popen.popen(cmd)
+  end
+end
diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7c3e692bde977951a1640e535a32be8d1a67c492
--- /dev/null
+++ b/app/services/spam_check_service.rb
@@ -0,0 +1,38 @@
+class SpamCheckService < BaseService
+  include Gitlab::AkismetHelper
+
+  attr_accessor :request, :api
+
+  def execute(request, api)
+    @request, @api = request, api
+    return false unless request || check_for_spam?(project)
+    return false unless is_spam?(request.env, current_user, text)
+    
+    create_spam_log
+
+    true
+  end
+
+  private
+
+  def text
+    [params[:title], params[:description]].reject(&:blank?).join("\n")
+  end
+  
+  def spam_log_attrs
+    {
+      user_id: current_user.id,
+      project_id: project.id,
+      title: params[:title],
+      description: params[:description],
+      source_ip: client_ip(request.env),
+      user_agent: user_agent(request.env),
+      noteable_type: 'Issue',
+      via_api: api
+    }
+  end
+
+  def create_spam_log
+    CreateSpamLogService.new(project, current_user, spam_log_attrs).execute
+  end
+end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1ab3b5789bc1c5d489849a08f8180fb0bf9bbc81..e13dc9265b83359d122f99aa06357284ff7bbca4 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -2,7 +2,9 @@
 #
 # Used for creating system notes (e.g., when a user references a merge request
 # from an issue, an issue's assignee changes, an issue is closed, etc.)
-class SystemNoteService
+module SystemNoteService
+  extend self
+
   # Called when commits are added to a Merge Request
   #
   # noteable         - Noteable object
@@ -15,7 +17,7 @@ class SystemNoteService
   # See new_commit_summary and existing_commit_summary.
   #
   # Returns the created Note object
-  def self.add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil)
+  def add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil)
     total_count  = new_commits.length + existing_commits.length
     commits_text = "#{total_count} commit".pluralize(total_count)
 
@@ -40,7 +42,7 @@ class SystemNoteService
   #   "Reassigned to @rspeicher"
   #
   # Returns the created Note object
-  def self.change_assignee(noteable, project, author, assignee)
+  def change_assignee(noteable, project, author, assignee)
     body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
@@ -63,7 +65,7 @@ class SystemNoteService
   #   "Removed ~5 label"
   #
   # Returns the created Note object
-  def self.change_label(noteable, project, author, added_labels, removed_labels)
+  def change_label(noteable, project, author, added_labels, removed_labels)
     labels_count = added_labels.count + removed_labels.count
 
     references     = ->(label) { label.to_reference(format: :id) }
@@ -101,7 +103,7 @@ class SystemNoteService
   #   "Miletone changed to 7.11"
   #
   # Returns the created Note object
-  def self.change_milestone(noteable, project, author, milestone)
+  def change_milestone(noteable, project, author, milestone)
     body = 'Milestone '
     body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}"
 
@@ -123,7 +125,7 @@ class SystemNoteService
   #   "Status changed to closed by bc17db76"
   #
   # Returns the created Note object
-  def self.change_status(noteable, project, author, status, source)
+  def change_status(noteable, project, author, status, source)
     body = "Status changed to #{status}"
     body << " by #{source.gfm_reference(project)}" if source
 
@@ -131,26 +133,26 @@ class SystemNoteService
   end
 
   # Called when 'merge when build succeeds' is executed
-  def self.merge_when_build_succeeds(noteable, project, author, last_commit)
+  def merge_when_build_succeeds(noteable, project, author, last_commit)
     body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
   # Called when 'merge when build succeeds' is canceled
-  def self.cancel_merge_when_build_succeeds(noteable, project, author)
+  def cancel_merge_when_build_succeeds(noteable, project, author)
     body = 'Canceled the automatic merge'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
-  def self.remove_merge_request_wip(noteable, project, author)
+  def remove_merge_request_wip(noteable, project, author)
     body = 'Unmarked this merge request as a Work In Progress'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
 
-  def self.add_merge_request_wip(noteable, project, author)
+  def add_merge_request_wip(noteable, project, author)
     body = 'Marked this merge request as a **Work In Progress**'
 
     create_note(noteable: noteable, project: project, author: author, note: body)
@@ -168,7 +170,7 @@ class SystemNoteService
   #   "Title changed from **Old** to **New**"
   #
   # Returns the created Note object
-  def self.change_title(noteable, project, author, old_title)
+  def change_title(noteable, project, author, old_title)
     new_title = noteable.title.dup
 
     old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs
@@ -191,7 +193,7 @@ class SystemNoteService
   # "Made the issue confidential"
   #
   # Returns the created Note object
-  def self.change_issue_confidentiality(issue, project, author)
+  def change_issue_confidentiality(issue, project, author)
     body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
     create_note(noteable: issue, project: project, author: author, note: body)
   end
@@ -210,7 +212,7 @@ class SystemNoteService
   #   "Target branch changed from `Old` to `New`"
   #
   # Returns the created Note object
-  def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch)
+  def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
     body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize
     create_note(noteable: noteable, project: project, author: author, note: body)
   end
@@ -229,7 +231,7 @@ class SystemNoteService
   #   "Restored target branch `feature`"
   #
   # Returns the created Note object
-  def self.change_branch_presence(noteable, project, author, branch_type, branch, presence)
+  def change_branch_presence(noteable, project, author, branch_type, branch, presence)
     verb =
       if presence == :add
         'restored'
@@ -245,7 +247,7 @@ class SystemNoteService
   # Example note text:
   #
   #   "Started branch `201-issue-branch-button`"
-  def self.new_issue_branch(issue, project, author, branch)
+  def new_issue_branch(issue, project, author, branch)
     h = Gitlab::Routing.url_helpers
     link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
 
@@ -270,7 +272,7 @@ class SystemNoteService
   # See cross_reference_note_content.
   #
   # Returns the created Note object
-  def self.cross_reference(noteable, mentioner, author)
+  def cross_reference(noteable, mentioner, author)
     return if cross_reference_disallowed?(noteable, mentioner)
 
     gfm_reference = mentioner.gfm_reference(noteable.project)
@@ -294,7 +296,7 @@ class SystemNoteService
     end
   end
 
-  def self.cross_reference?(note_text)
+  def cross_reference?(note_text)
     note_text.start_with?(cross_reference_note_prefix)
   end
 
@@ -308,7 +310,7 @@ class SystemNoteService
   # mentioner - Mentionable object
   #
   # Returns Boolean
-  def self.cross_reference_disallowed?(noteable, mentioner)
+  def cross_reference_disallowed?(noteable, mentioner)
     return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
     return false unless mentioner.is_a?(MergeRequest)
     return false unless noteable.is_a?(Commit)
@@ -328,7 +330,7 @@ class SystemNoteService
   #
   # Returns Boolean
 
-  def self.cross_reference_exists?(noteable, mentioner)
+  def cross_reference_exists?(noteable, mentioner)
     # Initial scope should be system notes of this noteable type
     notes = Note.system.where(noteable_type: noteable.class)
 
@@ -342,9 +344,60 @@ class SystemNoteService
     notes_for_mentioner(mentioner, noteable, notes).count > 0
   end
 
+  # Build an Array of lines detailing each commit added in a merge request
+  #
+  # new_commits - Array of new Commit objects
+  #
+  # Returns an Array of Strings
+  def new_commit_summary(new_commits)
+    new_commits.collect do |commit|
+      "* #{commit.short_id} - #{escape_html(commit.title)}"
+    end
+  end
+
+  # Called when the status of a Task has changed
+  #
+  # noteable  - Noteable object.
+  # project   - Project owning noteable
+  # author    - User performing the change
+  # new_task  - TaskList::Item object.
+  #
+  # Example Note text:
+  #
+  #   "Soandso marked the task Whatever as completed."
+  #
+  # Returns the created Note object
+  def change_task_status(noteable, project, author, new_task)
+    status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
+    body = "Marked the task **#{new_task.source}** as #{status_label}"
+    create_note(noteable: noteable, project: project, author: author, note: body)
+  end
+
+  # Called when noteable has been moved to another project
+  #
+  # direction    - symbol, :to or :from
+  # noteable     - Noteable object
+  # noteable_ref - Referenced noteable
+  # author       - User performing the move
+  #
+  # Example Note text:
+  #
+  #   "Moved to some_namespace/project_new#11"
+  #
+  # Returns the created Note object
+  def noteable_moved(noteable, project, noteable_ref, author, direction:)
+    unless [:to, :from].include?(direction)
+      raise ArgumentError, "Invalid direction `#{direction}`"
+    end
+
+    cross_reference = noteable_ref.to_reference(project)
+    body = "Moved #{direction} #{cross_reference}"
+    create_note(noteable: noteable, project: project, author: author, note: body)
+  end
+
   private
 
-  def self.notes_for_mentioner(mentioner, noteable, notes)
+  def notes_for_mentioner(mentioner, noteable, notes)
     if mentioner.is_a?(Commit)
       notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
     else
@@ -353,29 +406,18 @@ class SystemNoteService
     end
   end
 
-  def self.create_note(args = {})
+  def create_note(args = {})
     Note.create(args.merge(system: true))
   end
 
-  def self.cross_reference_note_prefix
+  def cross_reference_note_prefix
     'mentioned in '
   end
 
-  def self.cross_reference_note_content(gfm_reference)
+  def cross_reference_note_content(gfm_reference)
     "#{cross_reference_note_prefix}#{gfm_reference}"
   end
 
-  # Build an Array of lines detailing each commit added in a merge request
-  #
-  # new_commits - Array of new Commit objects
-  #
-  # Returns an Array of Strings
-  def self.new_commit_summary(new_commits)
-    new_commits.collect do |commit|
-      "* #{commit.short_id} - #{escape_html(commit.title)}"
-    end
-  end
-
   # Build a single line summarizing existing commits being added in a merge
   # request
   #
@@ -392,7 +434,7 @@ class SystemNoteService
   #   "* ea0f8418 - 1 commit from branch `feature`"
   #
   # Returns a newline-terminated String
-  def self.existing_commit_summary(noteable, existing_commits, oldrev = nil)
+  def existing_commit_summary(noteable, existing_commits, oldrev = nil)
     return '' if existing_commits.empty?
 
     count = existing_commits.size
@@ -415,47 +457,7 @@ class SystemNoteService
     "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
   end
 
-  # Called when the status of a Task has changed
-  #
-  # noteable  - Noteable object.
-  # project   - Project owning noteable
-  # author    - User performing the change
-  # new_task  - TaskList::Item object.
-  #
-  # Example Note text:
-  #
-  #   "Soandso marked the task Whatever as completed."
-  #
-  # Returns the created Note object
-  def self.change_task_status(noteable, project, author, new_task)
-    status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
-    body = "Marked the task **#{new_task.source}** as #{status_label}"
-    create_note(noteable: noteable, project: project, author: author, note: body)
-  end
-
-  # Called when noteable has been moved to another project
-  #
-  # direction    - symbol, :to or :from
-  # noteable     - Noteable object
-  # noteable_ref - Referenced noteable
-  # author       - User performing the move
-  #
-  # Example Note text:
-  #
-  #   "Moved to some_namespace/project_new#11"
-  #
-  # Returns the created Note object
-  def self.noteable_moved(noteable, project, noteable_ref, author, direction:)
-    unless [:to, :from].include?(direction)
-      raise ArgumentError, "Invalid direction `#{direction}`"
-    end
-
-    cross_reference = noteable_ref.to_reference(project)
-    body = "Moved #{direction} #{cross_reference}"
-    create_note(noteable: noteable, project: project, author: author, note: body)
-  end
-
-  def self.escape_html(text)
+  def escape_html(text)
     Rack::Utils.escape_html(text)
   end
 end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 1cd93263c9f42974f91d056231676205b83f5dbe..b6c52ddac7a6a5a16a9e7d327e1ffbcae8a1d35b 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
 class ArtifactUploader < CarrierWave::Uploader::Base
   storage :file
 
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index a65a896e41e597394003888e50d217c7a5dfc9d8..fb3b5dfecd06a1d69613ea95a536d5c775dad9a7 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
 class AttachmentUploader < CarrierWave::Uploader::Base
   include UploaderHelper
 
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 6135c3ad96f4e5c3916f426d08cc177880829a2a..71ff14a3f20d497b2bfccc78820ce433c8395c1e 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
 class AvatarUploader < CarrierWave::Uploader::Base
   include UploaderHelper
 
@@ -14,4 +12,8 @@ class AvatarUploader < CarrierWave::Uploader::Base
   def reset_events_cache(file)
     model.reset_events_cache if model.is_a?(User)
   end
+
+  def exists?
+    model.avatar.file && model.avatar.file.exists?
+  end
 end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 1af9e9b0edb7ccbee614b7474d5b125bdf6aca20..3ac6030c21c747a2b76b64bf292876bdfce1e820 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
 class FileUploader < CarrierWave::Uploader::Base
   include UploaderHelper
   MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
@@ -33,16 +32,15 @@ class FileUploader < CarrierWave::Uploader::Base
   end
 
   def to_h
-    filename = image? ? self.file.basename : self.file.filename
+    filename = image_or_video? ? self.file.basename : self.file.filename
     escaped_filename = filename.gsub("]", "\\]")
 
     markdown = "[#{escaped_filename}](#{self.secure_url})"
-    markdown.prepend("!") if image?
+    markdown.prepend("!") if image_or_video?
 
     {
       alt:      filename,
       url:      self.secure_url,
-      is_image: image?,
       markdown: markdown
     }
   end
diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb
index 046a1d641a92c7e77b07e99dc5d2e23346e75dd9..4f356dd663ee303920027b11dce1b4707259918a 100644
--- a/app/uploaders/lfs_object_uploader.rb
+++ b/app/uploaders/lfs_object_uploader.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
 class LfsObjectUploader < CarrierWave::Uploader::Base
   storage :file
 
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index 5ef440f3367c3c4f12c2577cd84171f5f1964677..b10ad71d052c6c0b2fda56a260929c081eeef04d 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -1,16 +1,37 @@
 # Extra methods for uploader
 module UploaderHelper
+  IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
+  # We recommend using the .mp4 format over .mov. Videos in .mov format can
+  # still be used but you really need to make sure they are served with the
+  # proper MIME type video/mp4 and not video/quicktime or your videos won't play
+  # on IE >= 9.
+  # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
+  VIDEO_EXT = %w[mp4 m4v mov webm ogv]
+
   def image?
-    img_ext = %w(png jpg jpeg gif bmp tiff)
-    if file.respond_to?(:extension)
-      img_ext.include?(file.extension.downcase)
-    else
-      # Not all CarrierWave storages respond to :extension
-      ext = file.path.split('.').last.downcase
-      img_ext.include?(ext)
-    end
-  rescue
-    false
+    extension_match?(IMAGE_EXT)
+  end
+
+  def video?
+    extension_match?(VIDEO_EXT)
+  end
+
+  def image_or_video?
+    image? || video?
+  end
+
+  def extension_match?(extensions)
+    return false unless file
+
+    extension =
+      if file.respond_to?(:extension)
+        file.extension
+      else
+        # Not all CarrierWave storages respond to :extension
+        File.extname(file.path).delete('.')
+      end
+
+    extensions.include?(extension.downcase)
   end
 
   def file_storage?
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 538d8176ce7c28c1cc0af538f864c4aaa5a6b0b7..23b52d08df7ccb55f0debdfd383436bd72d4a39b 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -109,7 +109,7 @@
             Newly registered users will by default be external
 
   %fieldset
-    %legend Sign-in Restrictions
+    %legend Sign-up Restrictions
     .form-group
       .col-sm-offset-2.col-sm-10
         .checkbox
@@ -122,6 +122,49 @@
           = f.label :send_user_confirmation_email do
             = f.check_box :send_user_confirmation_email
             Send confirmation email on sign-up
+    .form-group
+      = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
+        .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+    .form-group
+      = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2'
+      .col-sm-10
+        .checkbox
+          = f.label :domain_blacklist_enabled do
+            = f.check_box :domain_blacklist_enabled
+            Enable domain blacklist for sign ups
+    .form-group
+      .col-sm-offset-2.col-sm-10
+        .radio
+          = label_tag :blacklist_type_file do
+            = radio_button_tag :blacklist_type, :file
+            .option-title
+              Upload blacklist file
+        .radio
+          = label_tag :blacklist_type_raw do
+            = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank?
+            .option-title
+              Enter blacklist manually
+    .form-group.blacklist-file
+      = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf'
+        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.
+    .form-group.blacklist-raw
+      = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
+        .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+
+    .form-group
+      = f.label :after_sign_up_text, class: 'control-label col-sm-2'
+      .col-sm-10
+        = f.text_area :after_sign_up_text, class: 'form-control', rows: 4
+        .help-block Markdown enabled
+
+  %fieldset
+    %legend Sign-in Restrictions
     .form-group
       .col-sm-offset-2.col-sm-10
         .checkbox
@@ -147,11 +190,6 @@
       .col-sm-10
         = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
         .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
-    .form-group
-      = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
-        .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
     .form-group
       = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2'
       .col-sm-10
@@ -167,11 +205,6 @@
       .col-sm-10
         = f.text_area :sign_in_text, class: 'form-control', rows: 4
         .help-block Markdown enabled
-    .form-group
-      = f.label :after_sign_up_text, class: 'control-label col-sm-2'
-      .col-sm-10
-        = f.text_area :after_sign_up_text, class: 'form-control', rows: 4
-        .help-block Markdown enabled
     .form-group
       = f.label :help_page_text, class: 'control-label col-sm-2'
       .col-sm-10
@@ -352,4 +385,4 @@
 
 
   .form-actions
-    = f.submit 'Save', class: 'btn btn-save'
+    = f.submit 'Save', class: 'btn btn-save'
\ No newline at end of file
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 9d722bd7382ae560b12a9bb306420b7b35b04e3a..89d7a40d6b0210c3e27bcf330c248c1c41680417 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -16,3 +16,7 @@
       = link_to admin_health_check_path, title: 'Health Check' do
         %span
           Health Check
+    = nav_link(controller: :requests_profiles) do
+      = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+        %span
+          Requests Profiles
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index ce818c30c3086ed6d17ccd0b501223794452d355..352adbedee4e0a88401c8aed59bac7751537f215 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -11,16 +11,18 @@
       - else
         %span.build-link ##{build.id}
 
-      - if build.stuck?
-        %i.fa.fa-warning.text-warning
-
       - if build.ref
+        .icon-container
+          = build.tag? ? icon('tag') : icon('code-fork')
         = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
       - else
         .light none
-      = custom_icon("icon_commit")
+      .icon-container
+        = custom_icon("icon_commit")
 
       = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
+      - if build.stuck?
+        %i.fa.fa-warning.text-warning
 
       .label-container
         - if build.tags.any?
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index a2ac407c159b79d2d6e765538486b454bee659d3..452fc25ab07552fddce682739c1fb20fbb001d76 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -79,6 +79,10 @@
           GitLab Shell
           %span.pull-right
             = Gitlab::Shell.new.version
+        %p
+          GitLab Workhorse
+          %span.pull-right
+            = Gitlab::Workhorse.version
         %p
           GitLab API
           %span.pull-right
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 0cc405401cf5450f712941b31eed7a21af9420a3..5f7fdfdb011801cf71ded2c5fd22ade8f7c4717b 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -9,6 +9,10 @@
 
   = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
 
+  .form-group
+    .col-sm-offset-2.col-sm-10
+      = render 'shared/allow_request_access', form: f
+
   - if @group.new_record?
     .form-group
       .col-sm-offset-2.col-sm-10
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ae918086a5756cddcd343e9db9dfe433dd16ed4d
--- /dev/null
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -0,0 +1,26 @@
+- @no_container = true
+- page_title 'Requests Profiles'
+= render 'admin/background_jobs/head'
+
+%div{ class: container_class }
+  %h3.page-title
+    = page_title
+
+  .bs-callout.clearfix
+    Pass the header
+    %code X-Profile-Token: #{@profile_token}
+    to profile the request
+
+  - if @profiles.present?
+    .prepend-top-default
+      - @profiles.each do |path, profiles|
+        .panel.panel-default.panel-small
+          .panel-heading
+            %code= path
+          %ul.content-list
+            - profiles.each do |profile|
+              %li
+                = link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
+  - else
+    %p
+      No profiles found
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..fa1ad9efa73c1ea9baef59536faa74a591b16df3
--- /dev/null
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -0,0 +1,6 @@
+%tr.notes_holder
+  %td.notes_line{ colspan: 2 }
+  %td.notes_content
+    %ul.notes{ data: { discussion_id: discussion.id } }
+      = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+    = link_to_reply_discussion(discussion)
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..02b159ffd454b4f2b19bfc407d08723c03dce167
--- /dev/null
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -0,0 +1,14 @@
+- diff_file = discussion.diff_file
+- blob = discussion.blob
+
+.diff-file.file-holder
+  .file-title
+    = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
+
+  .diff-content.code.js-syntax-highlight
+    %table
+      - discussion.truncated_diff_lines.each do |line|
+        = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
+
+        - if discussion.for_line?(line)
+          = render "discussions/diff_discussion", discussion: discussion
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..49702e048aa717fffac79c383e4ed626c48251aa
--- /dev/null
+++ b/app/views/discussions/_discussion.html.haml
@@ -0,0 +1,45 @@
+- expanded = discussion.expanded?
+%li.note.note-discussion.timeline-entry
+  .timeline-entry-inner
+    .timeline-icon
+      = link_to user_path(discussion.author) do
+        = image_tag avatar_icon(discussion.author), class: "avatar s40"
+    .timeline-content
+      .discussion.js-toggle-container{ class: discussion.id }
+        .discussion-header
+          = link_to_member(@project, discussion.author, avatar: false)
+
+          .inline.discussion-headline-light
+            = discussion.author.to_reference
+            started a discussion on
+
+            - if discussion.for_commit?
+              - commit = discussion.noteable
+              - if commit
+                commit
+                = link_to commit.short_id, namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code), class: 'monospace'
+              - else
+                a deleted commit
+            - else
+              - if discussion.active?
+                = link_to diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) do
+                  the diff
+              - else
+                an outdated diff
+
+            = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
+
+          .discussion-actions
+            = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
+              - if expanded
+                = icon("chevron-up")
+              - else
+                = icon("chevron-down")
+
+              Toggle discussion
+
+        .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
+          - if discussion.diff_discussion? && discussion.diff_file
+            = render "discussions/diff_with_notes", discussion: discussion
+          - else
+            = render "discussions/notes", discussion: discussion
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a2642b839f6cacb61cd64f72afd0e62bae9ac7fc
--- /dev/null
+++ b/app/views/discussions/_notes.html.haml
@@ -0,0 +1,5 @@
+.panel.panel-default
+  .notes{ data: { discussion_id: discussion.id } }
+    %ul.notes.timeline
+      = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+  = link_to_reply_discussion(discussion)
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..a798c438ea0e9c26dfe764ff7bc887529069db93
--- /dev/null
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -0,0 +1,22 @@
+%tr.notes_holder
+  - if discussion_left
+    %td.notes_line.old
+    %td.notes_content.parallel.old
+      %ul.notes{ data: { discussion_id: discussion_left.id } }
+        = render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
+
+      = link_to_reply_discussion(discussion_left, 'old')
+  - else
+    %td.notes_line.old= ""
+    %td.notes_content.parallel.old= ""
+
+  - if discussion_right
+    %td.notes_line.new
+    %td.notes_content.parallel.new
+      %ul.notes{ data: { discussion_id: discussion_right.id } }
+        = render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
+
+      = link_to_reply_discussion(discussion_right, 'new')
+  - else
+    %td.notes_line.new= ""
+    %td.notes_content.parallel.new= ""
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
index 8b38b4c2bd4a9b354774f3333779167983ead07b..790d90ad3ee5f5b52de5bf9a7aca479ce9765831 100644
--- a/app/views/emojis/index.html.haml
+++ b/app/views/emojis/index.html.haml
@@ -1,5 +1,5 @@
 .emoji-menu
-  = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Seach emojis"
+  = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control", placeholder: "Search emoji"
   .emoji-menu-content
     - Gitlab::AwardEmoji.emoji_by_category.each do |category, emojis|
       %h5.emoji-menu-title
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index e4629bae0e6fa97a7444d2a0d2bdf05ebd6dcaf4..5c318cd3b8bdcff0f5e7212f654e9c1cf62ef205 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -4,11 +4,7 @@
       #{time_ago_with_tooltip(event.created_at)}
 
     = cache [event, current_application_settings, "v2.2"] do
-      - if event.author
-        = link_to user_path(event.author) do
-          = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
-      - else
-        = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
+      = author_avatar(event, size: 40)
 
       - if event.created_project?
         = render "events/event/created_project", event: event
diff --git a/app/views/events/_event_scope.html.haml b/app/views/events/_event_scope.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..8f7da7d8c4ffa203984affadd7127cfbb450c48a
--- /dev/null
+++ b/app/views/events/_event_scope.html.haml
@@ -0,0 +1,7 @@
+%span.event-scope
+  = event_preposition(event)
+  - if event.project
+    = link_to_project event.project
+  - else
+    = event.project_name
+
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 2e2403347c1b5b37451871faab4c8733b0f4f667..bba6e0d2c2014d8fe871162c5fcd5799c40cab3c 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,6 +1,6 @@
 .event-title
   %span.author_name= link_to_author event
-  %span.event_label{class: event.action_name}
+  %span{class: event.action_name}
   - if event.target
     = event.action_name
     %strong
@@ -10,12 +10,7 @@
   - else
     = event_action_name(event)
 
-  = event_preposition(event)
-
-  - if event.project
-    = link_to_project event.project
-  - else
-    = event.project_name
+  = render "events/event_scope", event: event
 
 - if event.target.respond_to?(:title)
   .event-body
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 5a2a469ba6226e9d2e48b3dddda72f969877cd7e..aba64dd17d0efc9ba4d760f26214fd47e605b6b9 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -1,6 +1,6 @@
 .event-title
   %span.author_name= link_to_author event
-  %span.event_label{class: event.action_name}
+  %span{class: event.action_name}
     = event_action_name(event)
 
   - if event.project
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 830fec0b4ab8e791d233ecb0f97cec7cb8fa2717..f08c96df309c9c68fa33091ea7578e34f38547da 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,14 +1,9 @@
 .event-title
   %span.author_name= link_to_author event
-  %span.event_label
-    = event.action_name
-    = event_note_title_html(event)
-    at
+  = event.action_name
+  = event_note_title_html(event)
 
-  - if event.project
-    = link_to_project event.project
-  - else
-    = event.project_name
+  = render "events/event_scope", event: event
 
 .event-body
   .event-note
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index ea54ef226ec046deaefbdccfdb355a2c6d34fdc7..44fff49d99c20133b188601eacf6d1e51cb5e8f1 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -2,14 +2,14 @@
 
 .event-title
   %span.author_name= link_to_author event
-  %span.event_label.pushed #{event.action_name} #{event.ref_type}
+  %span.pushed #{event.action_name} #{event.ref_type}
   - if event.rm_ref?
     %strong= event.ref_name
   - else
     %strong
       = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
-  at
-  = link_to_project project
+
+  = render "events/event_scope", event: event
 
 - if event.push_with_commits?
   .event-body
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 92cd4c553d0b4d00072c07866331651381ef1f76..decb89b2fd60936c02cc5ab27eaa97be0ba355bc 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -21,6 +21,10 @@
 
       = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
 
+      .form-group
+        .col-sm-offset-2.col-sm-10
+          = render 'shared/allow_request_access', form: f
+
       .form-group
         %hr
         = f.label :share_with_group_lock, class: 'control-label' do
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index eddeae98bc4edf86afc2d46cda70ac3fa503e0cb..53ed4fa991d688264891957c785f000808bc1924 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -6,7 +6,7 @@
 
 .cover-block.groups-cover-block
   %div{ class: container_class }
-    = image_tag group_icon(@group), class: "avatar group-avatar s70"
+    = image_tag group_icon(@group), class: "avatar group-avatar s70 avatar-tile"
     .group-info
       .cover-title
         %h1
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 431d312b4ca3c0b21d97642def227d62ad256b31..85e188d6f8b1da476eba729a0cf81c6a9ad8a61b 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -189,7 +189,7 @@
             %li
               %a Sort by date
 
-        = link_to 'New issue', '#', class: 'btn btn-new'
+        = link_to 'New issue', '#', class: 'btn btn-new btn-inverted'
 
   .lead
     Only nav links without button and search
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 6e993e58f0d7ca8057544e60ad9f563e221f2f56..15dd98077c8736744d64427fbc7bdb0c36ee85f6 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -74,6 +74,4 @@
     = link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
     again.
 
-
-:javascript
-  new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index d3d3c595c172c08a890ed531bf39c5218f80561c..c8a6fa1aa9ed76a227b019079f6f9e0a00bba9f2 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -56,5 +56,4 @@
               Import
               = icon("spinner spin", class: "loading-icon")
 
-:javascript
-  new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 7486b1423e29c2218cff29cc3aa922c88dfc69c2..deaaf9af8751862c8cb991e7dd1e9a74a0d2e7ef 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -55,5 +55,4 @@
               Import
               = icon("spinner spin", class: "loading-icon")
 
-:javascript
-  new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index aedb8468eca9e1d331906cf4955fbbb2bf5f14ea..fcfc6fd37f4e804d07db5bab1389ae5adea1b889 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -51,5 +51,4 @@
               Import
               = icon("spinner spin", class: "loading-icon")
 
-:javascript
-  new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 267eee4f262ae8ebe702cc892b61facd577468a8..ed3afb0ce3386338fa6359f1991750bbe00b9b9f 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -51,5 +51,4 @@
               Import
               = icon("spinner spin", class: "loading-icon")
 
-:javascript
-  new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 5ada6b174ebc3a36b8db6adefa1eb6016ea25c0b..e79f122940ae6c122b47951a3b5d17f4dd7d0f16 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -77,5 +77,4 @@
     = link_to "import flow", new_import_google_code_path
     again.
 
-:javascript
-  new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 12e7ed0e792be75ebfb04955795c57a0ef3eee05..351100f3523383a3a723f261f6ab5fe96b6fcab0 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,7 +1,7 @@
 - project = @target_project || @project
+- noteable_class = @noteable.class if @noteable.present?
 
-- if @noteable
-  :javascript
-    GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}"
-    GitLab.GfmAutoComplete.cachedData = undefined;
-    GitLab.GfmAutoComplete.setup();
+:javascript
+  GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_class, type_id: params[:id])}"
+  GitLab.GfmAutoComplete.cachedData = undefined;
+  GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 5ee8772882ec68a9e31a00c7c06344cc79da6865..ac04f57e2172c1899ca883413b4bef8e2385ae60 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -9,7 +9,7 @@
       = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
         %span
           Overview
-    = nav_link(controller: %w(system_info background_jobs logs health_check)) do
+    = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
       = link_to admin_system_info_path, title: 'Monitoring' do
         %span
           Monitoring
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 216686988143d71713d5707ea92cb59c63d2b741..3a14751ea8ebd9708f62eb5064d3c6eaab5b4938 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -30,7 +30,7 @@
       %span
         Merge Requests
         %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
-  = nav_link(controller: :snippets) do
+  = nav_link(controller: 'dashboard/snippets') do
     = link_to dashboard_snippets_path, title: 'Snippets' do
       %span
         Snippets
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 3b40006a0cce98c536c8bd7e0400880304e3b425..e5bda7b3a6ff5f493ae06b00fcf1dd9055fef123 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,21 +1,17 @@
 %ul.nav.nav-sidebar
   = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
     = link_to explore_root_path, title: 'Projects' do
-      = icon('bookmark fw')
       %span
         Projects
   = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
     = link_to explore_groups_path, title: 'Groups' do
-      = icon('group fw')
       %span
         Groups
   = nav_link(controller: :snippets) do
     = link_to explore_snippets_path, title: 'Snippets' do
-      = icon('clipboard fw')
       %span
         Snippets
   = nav_link(controller: :help) do
     = link_to help_path, title: 'Help' do
-      = icon('question-circle fw')
       %span
         Help
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 9e65d94186b99924eee01a1a224803ebcddaf923..1d3b8fc36833a3dc198c3d52f7d294dece871c10 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -66,7 +66,7 @@
 
     - if project_nav_tab? :issues
       = nav_link(controller: [:issues, :labels, :milestones]) do
-        = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
+        = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
           %span
             Issues
             - if @project.default_issues_tracker?
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 51a54b4f262719f5c126f0c7c3d03c45271fcd52..52a5bdc1a1b1093f98ed10ff6bf7f42f51c24d6d 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -39,7 +39,7 @@
       = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
         %span
           Triggers
-    = nav_link(controller: :badges) do
-      = link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
+    = nav_link(controller: :pipelines_settings) do
+      = link_to namespace_project_pipelines_settings_path(@project.namespace, @project), title: 'CI/CD Pipelines' do
         %span
-          Badges
+          CI/CD Pipelines
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 2049b204956b9eebdf311aa30dbed241d422e7ff..ee9c0366f2bdea8131a4f63883492dea0aa60298 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -6,7 +6,7 @@
 - content_for :scripts_body_top do
   - project = @target_project || @project
   - if @project_wiki && @page
-    - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
+    - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.slug)
   - else
     - markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
   - if current_user
diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml
index 003884a5bd966cf823aa874b5b12cec74a2b6873..943ebdaeffe7180ed7174df5e28b3a295bd06609 100644
--- a/app/views/profiles/_head.html.haml
+++ b/app/views/profiles/_head.html.haml
@@ -1,3 +1,3 @@
 - content_for :page_specific_javascripts do
   = page_specific_javascript_tag('lib/cropper.js')
-  = page_specific_javascript_tag('profile/application.js')
+  = page_specific_javascript_tag('profile/profile_bundle.js')
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 48b0dd6b12142c132cea9a6549e73ca0f3e64d37..ac50ce83f6afcf9eb012ab1a2f1930901854d64f 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -5,7 +5,8 @@
         %i.fa.fa-rss
 
   = render 'shared/event_filter'
-.content_list{:"data-href" => activity_project_path(@project)}
+
+.content_list.project-activity{:"data-href" => activity_project_path(@project)}
 = spinner
 
 :javascript
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
deleted file mode 100644
index fff30f11d822c85fb758dee89f19a7726e99f80e..0000000000000000000000000000000000000000
--- a/app/views/projects/_builds_settings.html.haml
+++ /dev/null
@@ -1,65 +0,0 @@
-%fieldset.builds-feature
-  %h5.prepend-top-0
-    Builds
-  - unless @repository.gitlab_ci_yml
-    .form-group
-      %p Builds need to be configured before you can begin using Continuous Integration.
-      = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
-  .form-group
-    %p Get recent application code using the following command:
-    .radio
-      = f.label :build_allow_git_fetch_false do
-        = f.radio_button :build_allow_git_fetch, 'false'
-        %strong git clone
-        %br
-        %span.descr Slower but makes sure you have a clean dir before every build
-    .radio
-      = f.label :build_allow_git_fetch_true do
-        = f.radio_button :build_allow_git_fetch, 'true'
-        %strong git fetch
-        %br
-        %span.descr Faster
-
-  .form-group
-    = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
-    = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
-    %p.help-block per build in minutes
-  .form-group
-    = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
-    .input-group
-      %span.input-group-addon /
-      = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
-      %span.input-group-addon /
-    %p.help-block
-      We will use this regular expression to find test coverage output in build trace.
-      Leave blank if you want to disable this feature
-    .bs-callout.bs-callout-info
-      %p Below are examples of regex for existing tools:
-      %ul
-        %li
-          Simplecov (Ruby) -
-          %code \(\d+.\d+\%\) covered
-        %li
-          pytest-cov (Python) -
-          %code \d+\%\s*$
-        %li
-          phpunit --coverage-text --colors=never (PHP) -
-          %code ^\s*Lines:\s*\d+.\d+\%
-        %li
-          gcovr (C/C++) -
-          %code ^TOTAL.*\s+(\d+\%)$
-        %li
-          tap --coverage-report=text-summary (Node.js) -
-          %code ^Statements\s*:\s*([^%]+)
-
-  .form-group
-    .checkbox
-      = f.label :public_builds do
-        = f.check_box :public_builds
-        %strong Public builds
-      .help-block Allow everyone to access builds for Public and Internal projects
-
-  .form-group.append-bottom-0
-    = f.label :runners_token, "Runners token", class: 'label-light'
-    = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
-    %p.help-block The secure token used to checkout project.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index cf11723dc8e569706bd2473c852fe06ca9a4bb0a..51f74f3b7ce6dd8e3f177aac29af77f460b69dcc 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,7 +1,7 @@
 - empty_repo = @project.empty_repo?
 .project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
   %div{ class: container_class }
-    = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70')
+    = project_icon(@project, alt: @project.name, class: 'project-avatar avatar s70 avatar-tile')
     %h1.project-title
       = @project.name
       %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)}
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
deleted file mode 100644
index ac80951dd4fdb56c38393b8f8ca95a92f1c9dd7c..0000000000000000000000000000000000000000
--- a/app/views/projects/badges/index.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- page_title 'Badges'
-- badges_path = namespace_project_badges_path(@project.namespace, @project)
-
-.prepend-top-10
-  .panel.panel-default
-    .panel-heading
-      %b Builds badge &middot;
-      = @build_badge.to_html
-      .pull-right
-        = render 'shared/ref_switcher', destination: 'badges', align_right: true
-    .panel-body
-      .row
-        .col-md-2.text-center
-          Markdown
-        .col-md-10.code.js-syntax-highlight
-          = highlight('.md', @build_badge.to_markdown)
-      .row
-        %hr
-      .row
-        .col-md-2.text-center
-          HTML
-        .col-md-10.code.js-syntax-highlight
-          = highlight('.html', @build_badge.to_html)
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index cdac50f7a8d3a5c7c14d57659c38b114ae8262e9..ff893ea74e187cfa6e46b536db4bf81ec0b911db 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -16,6 +16,7 @@
 
 - if current_user
   .btn-group{ role: "group" }
-    = edit_blob_link
+    - if blob_text_viewable?(@blob)
+      = edit_blob_link
     = replace_blob_link
     = delete_blob_link
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 29c7d45074a2241d5f20bb0a78b4443a0cec8184..ff379bafb26759800baee0a81780423ada586aa7 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -4,7 +4,9 @@
       = icon('code-fork')
       = ref
     %span.editor-file-name
-      = @path
+      - if current_action?(:edit) || current_action?(:update)
+        = text_field_tag 'file_path', (params[:file_path] || @path),
+                                      class: 'form-control new-file-path'
 
     - if current_action?(:new) || current_action?(:create)
       %span.editor-file-name
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 9fe65cbb104997e6a8aad64404ddb7dd620cf53a..d54c76ff9c813d22f01a68ceba0cc3b5b9588efa 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,5 +1,5 @@
 .branch-commit
-  = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace"
+  = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
   &middot;
   %span.str-truncated
     = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 77b405f1f3977f1d7b51d4ebc35442bb0b03bd98..e889f29c81605e021a97f5ab8bfec0359c1e9e4c 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -7,28 +7,32 @@
     .nav-text
       Protected branches can be managed in project settings
 
-    - if can? current_user, :push_code, @project
-      .nav-controls
+    .nav-controls
+      = form_tag(filter_branches_path, method: :get) do
+        = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
+
+      .dropdown.inline
+        %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+          %span.light
+            = projects_sort_options_hash[@sort]
+          %b.caret
+        %ul.dropdown-menu.dropdown-menu-align-right
+          %li
+            = link_to filter_branches_path(sort: sort_value_name) do
+              = sort_title_name
+            = link_to filter_branches_path(sort: sort_value_recently_updated) do
+              = sort_title_recently_updated
+            = link_to filter_branches_path(sort: sort_value_oldest_updated) do
+              = sort_title_oldest_updated
+
+      - if can? current_user, :push_code, @project
         = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
           New branch
-        .dropdown.inline
-          %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
-            %span.light
-            - if @sort.present?
-              = @sort.humanize
-            - else
-              Name
-            %b.caret
-          %ul.dropdown-menu.dropdown-menu-align-right
-            %li
-              = link_to namespace_project_branches_path(sort: nil) do
-                Name
-              = link_to namespace_project_branches_path(sort: 'recently_updated') do
-                = sort_title_recently_updated
-              = link_to namespace_project_branches_path(sort: 'last_updated') do
-                = sort_title_oldest_updated
+
   - if @branches.any?
     %ul.content-list.all-branches
       - @branches.each do |branch|
         = render "projects/branches/branch", branch: branch
     = paginate @branches, theme: 'gitlab'
+  - else
+    .nothing-here-block No branches to show
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 396cc4ad925d737e694963a099f8fd181efe5427..a8bc53c284995b93a4466d95707e49e122ef84b2 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -40,7 +40,7 @@
   .block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
     .title
       Build details
-      - if @build.retryable?
+      - if can?(current_user, :update_build, @build) && @build.retryable?
         = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
     - if @build.merge_request
       %p.build-detail-row
@@ -49,7 +49,7 @@
     - if @build.duration
       %p.build-detail-row
         %span.build-light-text Duration:
-        #{duration_in_words(@build.finished_at, @build.started_at)}
+        = time_interval_in_words(@build.duration)
     - if @build.finished_at
       %p.build-detail-row
         %span.build-light-text Finished:
@@ -88,8 +88,9 @@
         %p
           %span.build-light-text Variables:
 
-        %code
-          - @build.trigger_request.variables.each do |key, value|
+
+        - @build.trigger_request.variables.each do |key, value|
+          %code
             #{key}=#{value}
 
   .block
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 16b8e1cca9134e9fab9867a7f579b31153f0a0e6..ca907077c2b33fce8d190addbc173b1561ed00b9 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -9,7 +9,7 @@
 
       - if can_create_issue
         %li
-          = link_to url_for_new_issue(@project, only_path: true) do
+          = link_to new_namespace_project_issue_path(@project.namespace, @project) do
             = icon('exclamation-circle fw')
             New issue
 
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index a9eaed4c5f63264267862fd130fe2b1b65ce5603..d78888e9fe4aef6e60b3934a13a94f4ee908295b 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -2,17 +2,13 @@
   - if current_user && can?(current_user, :fork_project, @project)
     - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
       = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
-        = icon('code-fork fw')
+        = custom_icon('icon_fork')
         Fork
-      %div.count-with-arrow
-        %span.arrow
-        %span.count
-          = @project.forks_count
     - else
       = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
-        = icon('code-fork fw')
+        = custom_icon('icon_fork')
         Fork
-      %div.count-with-arrow
-        %span.arrow
-        = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
-          = @project.forks_count
+    %div.count-with-arrow
+      %span.arrow
+      = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
+        = @project.forks_count
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index e1b42b2cfa5c3ce09063ed137d752e551a0cfa3a..91081435220cab25fb66d81b13ca2477c6df1345 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -13,21 +13,24 @@
       - else
         %span ##{build.id}
 
-      - if build.stuck?
-        = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
-      - if defined?(retried) && retried
-        = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
-
       - if defined?(ref) && ref
         - if build.ref
+          .icon-container
+            = build.tag? ? icon('tag') : icon('code-fork')
           = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
         - else
           .light none
-        = custom_icon("icon_commit")
+        .icon-container
+          = custom_icon("icon_commit")
 
       - if defined?(commit_sha) && commit_sha
         = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
 
+      - if build.stuck?
+        = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+      - if defined?(retried) && retried
+        = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+
       .label-container
         - if build.tags.any?
           - build.tags.each do |tag|
@@ -39,7 +42,8 @@
           %span.label.label-danger allowed to fail
         - if defined?(retried) && retried
           %span.label.label-warning retried
-
+        - if build.manual?
+          %span.label.label-info manual
 
   - if defined?(runner) && runner
     %td
@@ -79,6 +83,10 @@
         - if build.active?
           = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
             = icon('remove', class: 'cred')
-        - elsif defined?(allow_retry) && allow_retry && build.retryable?
-          = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
-            = icon('repeat')
+        - elsif defined?(allow_retry) && allow_retry
+          - if build.retryable?
+            = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+              = icon('repeat')
+          - elsif build.playable?
+            = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+              = icon('play')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 0557d384e334ddf10bb6c40cfc583aedd6c80232..558c35553da5c938269c2a231be12b8eb11191d9 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -10,12 +10,13 @@
       = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
         %span ##{pipeline.id}
       - if pipeline.ref
+        .icon-container
+          = pipeline.tag? ? icon('tag') : icon('code-fork')
         = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
-        = custom_icon("icon_commit")
+        .icon-container
+          = custom_icon("icon_commit")
       = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
-      - if pipeline.tag?
-        %span.label.label-primary tag
-      - elsif pipeline.latest?
+      - if pipeline.latest?
         %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
       - if pipeline.triggered?
         %span.label.label-primary triggered
@@ -26,7 +27,7 @@
 
       %p.commit-title
         - if commit = pipeline.commit
-          = commit_author_avatar(commit, size: 20)
+          = author_avatar(commit, size: 20)
           = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
         - else
           Cant find HEAD commit for this branch
@@ -34,7 +35,7 @@
 
     - stages_status = pipeline.statuses.latest.stages_status
     - stages.each do |stage|
-      %td
+      %td.stage-cell
         - status = stages_status[stage]
         - tooltip = "#{stage.titleize}: #{status || 'not found'}"
         - if status
@@ -56,19 +57,32 @@
 
   %td.pipeline-actions
     .controls.hidden-xs.pull-right
-      - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
-      - if artifacts.present?
-        .inline
-          .btn-group
-            %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
-              = icon("download")
-              %b.caret
-            %ul.dropdown-menu.dropdown-menu-align-right
-              - artifacts.each do |build|
-                %li
-                  = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
-                    = icon("download")
-                    %span Download '#{build.name}' artifacts
+      - artifacts = pipeline.builds.latest.with_artifacts_not_expired
+      - actions = pipeline.manual_actions
+      - if artifacts.present? || actions.any?
+        .btn-group.inline
+          - if actions.any?
+            .btn-group
+              %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+                = icon("play")
+                %b.caret
+              %ul.dropdown-menu.dropdown-menu-align-right
+                - actions.each do |build|
+                  %li
+                    = link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
+                      = icon("play")
+                      %span= build.name.humanize
+          - if artifacts.present?
+            .btn-group
+              %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
+                = icon("download")
+                %b.caret
+              %ul.dropdown-menu.dropdown-menu-align-right
+                - artifacts.each do |build|
+                  %li
+                    = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
+                      = icon("download")
+                      %span Download '#{build.name}' artifacts
 
       - if can?(current_user, :update_pipeline, @project)
         .cancel-retry-btns.inline
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 41fd545942905323bff2f67a360c58af7d5bb6fb..540689f4a6147717d3c50ec801c8e21eb92cfbbb 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -35,8 +35,8 @@
   .bs-callout.bs-callout-warning
     \.gitlab-ci.yml not found in this commit
 
-.table-holder
-  %table.table.builds
+.table-holder.pipeline-holder
+  %table.table.builds.pipeline
     %thead
       %tr
         %th Status
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index c8c7b858baa862d6af6125490b66059101edf36c..fd888f41b1ed86ac1db7abb4480383ef535091c8 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,7 +9,8 @@
 
 = cache(cache_key) do
   %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
-    = commit_author_avatar(commit, size: 36)
+    = author_avatar(commit, size: 36)
+
     .commit-info-block
       .commit-row-title
         %span.item-title
@@ -18,13 +19,14 @@
             &middot;
             = commit.short_id
           - if commit.status
-            = render_commit_status(commit, cssclass: 'visible-xs-inline')
+            .visible-xs-inline
+              = render_commit_status(commit)
           - if commit.description?
             %a.text-expander.hidden-xs.js-toggle-button ...
 
         .commit-actions.hidden-xs
           - if commit.status
-            = render_commit_status(commit, cssclass: 'btn btn-transparent')
+            = render_commit_status(commit)
           = clipboard_button(clipboard_text: commit.id)
           = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
           = link_to_browse_code(project, commit)
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index af09b3418ea888f1043f17f8c6a127c11f078cf4..d79336f5a6035e4cef05bd618d5765d4dc84c2fa 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,7 +1,7 @@
 = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
   .clearfix
     - if params[:to] && params[:from]
-      = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
+      = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
     .form-group.dropdown.compare-form-group.js-compare-from-dropdown
       .input-group.inline-input-group
         %span.input-group-addon from
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f70dba224faabe12d806c82879f5bf405ddc017f
--- /dev/null
+++ b/app/views/projects/deployments/_actions.haml
@@ -0,0 +1,22 @@
+- if can?(current_user, :create_deployment, deployment) && deployment.deployable
+  .pull-right
+    - actions = deployment.manual_actions
+    - if actions.present?
+      .btn-group.inline
+        .btn-group
+          %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+            = icon("play")
+            %b.caret
+          %ul.dropdown-menu.dropdown-menu-align-right
+            - actions.each do |action|
+              %li
+                = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
+                  = icon("play")
+                  %span= action.name.humanize
+
+    - if local_assigns.fetch(:allow_rollback, false)
+      = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
+        - if deployment.last?
+          Re-deploy
+        - else
+          Rollback
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index d08dd92f1f6baad77777ec22eb494b2fdc9d6b3f..baf02f1e6a013ca74f5af737d0f86195b6c80f5b 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -7,17 +7,11 @@
 
   %td
     - if deployment.deployable
-      = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
+      = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
         = "#{deployment.deployable.name} (##{deployment.deployable.id})"
 
   %td
     #{time_ago_with_tooltip(deployment.created_at)}
 
   %td
-    - if can?(current_user, :create_deployment, deployment) && deployment.deployable
-      .pull-right
-        = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
-          - if deployment.last?
-            Retry
-          - else
-            Rollback
+    = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 8ae433b48235da9b68dc904a14cd425d454c39de..4bf3ccace2022ee14e29cef67526cbe966519394 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -7,7 +7,7 @@
 .content-block.oneline-block.files-changed
   .inline-parallel-buttons
     - if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
-      = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
+      = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
     - if show_whitespace_toggle
       - if current_controller?(:commit)
         = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c306909fb1ae6e23b37cbb2e6ddc6c9d243ce9ca..1854c64cbd77561ec30dedaa49ca2b8af46c2738 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -9,10 +9,11 @@
             = icon('comment')
           \
 
-        - if editable_diff?(diff_file)
-          = edit_blob_link(@merge_request.source_project,
-              @merge_request.source_branch, diff_file.new_path,
-              from_merge_request_id: @merge_request.id)
+          - if editable_diff?(diff_file)
+            = edit_blob_link(@merge_request.source_project,
+                @merge_request.source_branch, diff_file.new_path,
+                from_merge_request_id: @merge_request.id,
+                skip_visible_check: true)
 
         = view_file_btn(diff_commit.id, diff_file, project)
 
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 5a8a131d10c162e5252b49b8cdbee31f8a491f54..4d3af905b5890bf54b7a8be86e56535345455a9b 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,8 +1,7 @@
 - plain = local_assigns.fetch(:plain, false)
-- line_code = diff_file.line_code(line)
-- position = diff_file.position(line)
 - type = line.type
-%tr.line_holder{ id: line_code, class: type }
+- line_code = diff_file.line_code(line) unless plain
+%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } }
   - case type
   - when 'match'
     = render "projects/diffs/match_line", { line: line.text,
@@ -24,4 +23,4 @@
         = link_text
       - else
         %a{href: "##{line_code}", data: { linenumber: link_text }}
-    %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type)
+    %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
deleted file mode 100644
index b9c0d9dcdfdc33346a241437535d08f70cf29807..0000000000000000000000000000000000000000
--- a/app/views/projects/diffs/_match_line_parallel.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%td.old_line.diff-line-num.empty-cell
-%td.line_content.parallel.match= line
-%td.new_line.diff-line-num.empty-cell
-%td.line_content.parallel.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index d208fcee10b2971f60b338cef6cc4741f5bc338e..7f30faa20d88e057011c2634acf073f6e4a7f14e 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -5,32 +5,35 @@
       - left = line[:left]
       - right = line[:right]
       %tr.line_holder.parallel
-        - if left[:type] == 'match'
-          = render "projects/diffs/match_line_parallel", { line: left[:text] }
-        - elsif left[:type] == 'nonewline'
-          %td.old_line.diff-line-num.empty-cell
-          %td.line_content.parallel.match= left[:text]
-          %td.new_line.diff-line-num.empty-cell
-          %td.line_content.parallel.match= left[:text]
+        - if left
+          - if left.meta?
+            %td.old_line.diff-line-num.empty-cell
+            %td.line_content.parallel.match= left.text
+          - else
+            - left_line_code = diff_file.line_code(left)
+            - left_position = diff_file.position(left)
+            %td.old_line.diff-line-num{id: left_line_code, class: left.type, data: { linenumber: left.old_pos }}
+              %a{href: "##{left_line_code}" }= raw(left.old_pos)
+            %td.line_content.parallel.noteable_line{class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old')}= diff_line_content(left.text)
         - else
-          %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])], data: { linenumber: left[:number] }}
-            %a{href: "##{left[:line_code]}" }= raw(left[:number])
-          %td.line_content.parallel.noteable_line{class: [left[:type], ('empty-cell' if left[:text].empty?)], data: diff_view_line_data(left[:line_code], left[:position], 'old')}= diff_line_content(left[:text])
+          %td.old_line.diff-line-num.empty-cell
+          %td.line_content.parallel
 
-          - if right[:type] == 'new'
-            - new_line_type = 'new'
-            - new_line_code = right[:line_code]
-            - new_position = right[:position]
+        - if right
+          - if right.meta?
+            %td.old_line.diff-line-num.empty-cell
+            %td.line_content.parallel.match= left.text
           - else
-            - new_line_type = nil
-            - new_line_code = left[:line_code]
-            - new_position = left[:position]
-
-          %td.new_line.diff-line-num{id: new_line_code, class: [new_line_type, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }}
-            %a{href: "##{new_line_code}" }= raw(right[:number])
-          %td.line_content.parallel.noteable_line{class: [new_line_type, ('empty-cell' if right[:text].empty?)], data: diff_view_line_data(new_line_code, new_position, 'new')}= diff_line_content(right[:text])
+            - right_line_code = diff_file.line_code(right)
+            - right_position = diff_file.position(right)
+            %td.new_line.diff-line-num{id: right_line_code, class: right.type, data: { linenumber: right.new_pos }}
+              %a{href: "##{right_line_code}" }= raw(right.new_pos)
+            %td.line_content.parallel.noteable_line{class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new')}= diff_line_content(right.text)
+        - else
+          %td.old_line.diff-line-num.empty-cell
+          %td.line_content.parallel
 
       - unless @diff_notes_disabled
-        - notes_left, notes_right = organize_comments(left, right)
-        - if notes_left.present? || notes_right.present?
-          = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
+        - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
+        - if discussion_left || discussion_right
+          = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index ea2a3e01277543aababa899dc2c73357954aa14a..e751dabdf99de2344fab53d732b3d38e660a0964 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,7 +2,7 @@
   .commit-stat-summary
     Showing
     = link_to '#', class: 'js-toggle-button' do
-      %strong #{pluralize(diff_files.count, "changed file")}
+      %strong #{pluralize(diff_files.size, "changed file")}
     with
     %strong.cgreen #{diff_files.sum(&:added_lines)} additions
     and
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 196f8122db367ca0238d3edf23a0eb55f416880b..5970b9abf2b2e16b73f12cfdf0bbfa890f2daea3 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -11,9 +11,9 @@
 
     - unless @diff_notes_disabled
       - line_code = diff_file.line_code(line)
-      - diff_notes = @grouped_diff_notes[line_code] if line_code
-      - if diff_notes
-        = render "projects/notes/diff_notes_with_reply", notes: diff_notes
+      - discussion = @grouped_diff_discussions[line_code] if line_code
+      - if discussion
+        = render "discussions/diff_discussion", discussion: discussion
 
   - if last_line > 0
     = render "projects/diffs/match_line", { line: "",
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 10fa1ddf2e5376578db39b3c530e4b911627e685..295a1b62535d47b97a7c6e6d66438c1a0de661fe 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -11,5 +11,5 @@
           = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
   %p
     To preserve performance only
-    %strong #{diff_files.count} of #{diff_files.real_size}
+    %strong #{diff_files.size} of #{diff_files.real_size}
     files are displayed.
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 57af167180b254673b29ce1c10d55c3c2b3d0e8b..b282aa52b25d0a6eadcbd7b1ac1d05062f859f5c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -4,6 +4,7 @@
       %h4.prepend-top-0
         Project settings
     .col-lg-9
+      .project-edit-errors
       = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
         %fieldset.append-bottom-0
           .form-group
@@ -32,6 +33,10 @@
               %strong
                 = visibility_level_label(@project.visibility_level)
               .light= visibility_level_description(@project.visibility_level, @project)
+
+        .form-group
+          = render 'shared/allow_request_access', form: f
+
         .form-group
           = f.label :tag_list, "Tags", class: 'label-light'
           = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
@@ -86,8 +91,6 @@
         %hr
         = render 'merge_request_settings', f: f
         %hr
-        = render 'builds_settings', f: f
-        %hr
         %fieldset.features.append-bottom-default
           %h5.prepend-top-0
             Project avatar
@@ -188,6 +191,7 @@
       %h4.prepend-top-0.warning-title
         Rename repository
     .col-lg-9
+      = render 'projects/errors'
       = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
         .form-group.project_name_holder
           = f.label :name, class: 'label-light' do
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index eafa246d05fd7ddf0b95c0f3780426f65f000168..e2453395602c1f6fe82a61a73c2268e491c3942f 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -15,3 +15,6 @@
   %td
     - if last_deployment
       #{time_ago_with_tooltip(last_deployment.created_at)}
+
+  %td
+    = render 'projects/deployments/actions', deployment: last_deployment
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index c07f4bd510ca81b928b7301f56e06818db80aac2..6d040f5cfe6790e4176c31f66528ad5447fd4388 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -1,7 +1,22 @@
-= form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: 'col-lg-9' } do |f|
-  = form_errors(@environment)
-  .form-group
-    = f.label :name, 'Name', class: 'label-light'
-    = f.text_field :name, required: true, class: 'form-control'
-  = f.submit 'Create environment', class: 'btn btn-create'
-  = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
+.row.prepend-top-default.append-bottom-default
+  .col-lg-3
+    %h4.prepend-top-0
+      Environments
+    %p
+      Environments allow you to track deployments of your application
+      = succeed "." do
+        = link_to "Read more about environments", help_page_path("ci/environments")
+
+  = form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f|
+    = form_errors(@environment)
+
+    .form-group
+      = f.label :name, 'Name', class: 'label-light'
+      = f.text_field :name, required: true, class: 'form-control'
+    .form-group
+      = f.label :external_url, 'External URL', class: 'label-light'
+      = f.url_field :external_url, class: 'form-control'
+
+    .form-actions
+      = f.submit 'Save', class: 'btn btn-save'
+      = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6d1bdb9320f302ac72c4dd1c203eb2284f077a4b
--- /dev/null
+++ b/app/views/projects/environments/edit.html.haml
@@ -0,0 +1,6 @@
+- page_title "Edit", @environment.name, "Environments"
+
+%h3.page-title
+  Edit environment
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 303d7c23d01b7203af1486e64078880389ba936f..a6dd34653abd84e4b0db61e0ae5aa38388ae7364 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -28,4 +28,5 @@
           %th Environment
           %th Last deployment
           %th Date
+          %th
         = render @environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 89e06567196efa9c39ddc8922d5f265cce708724..e51667ade2dbb1198fcc33e931c1ff287324f0ba 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,12 +1,6 @@
 - page_title 'New Environment'
 
-.row.prepend-top-default.append-bottom-default
-  .col-lg-3
-    %h4.prepend-top-0
-      New Environment
-    %p
-      Environments allow you to track deployments of your application
-      = succeed "." do
-        = link_to "Read more about environments", help_page_path("ci/environments")
-
-  = render 'form'
+%h3.page-title
+  New environment
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index b17aba2431fd30db3b4e0f3a2f13aa42844e7ca3..a07436ad7c97dd5f3b246644f5c3680a0cd8d852 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -5,11 +5,11 @@
 %div{ class: container_class }
   .top-area
     .col-md-9
-      %h3.page-title= @environment.name.titleize
-
+      %h3.page-title= @environment.name.capitalize
     .col-md-3
       .nav-controls
         - if can?(current_user, :update_environment, @environment)
+          = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
           = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
 
   - if @deployments.blank?
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index dbe9ddfde2f0877b097b715e1e43431b1dd9cf37..a1d79bdabda0b2f0fc76e2beb4ba76f6b4b21673 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -31,11 +31,11 @@
     - if current_user && can?(current_user, :fork_project, @project)
       - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
         = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
-          = icon('code-fork fw')
+          = custom_icon('icon_fork')
           Fork
       - else
         = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
-          = icon('code-fork fw')
+          = custom_icon('icon_fork')
           Fork
 
 
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 73a7fc0e1ac66c5b92ff395dd5801d4676b3b70b..5242bc72b716d4eb40489668a2e3fa87911d6b8d 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -1,45 +1,54 @@
 - page_title "Fork project"
-- if @namespaces.present?
-  %h3.page-title Fork project
-  %p.lead
-    Click to fork the project to a user or group
-  %hr
 
-  .fork-namespaces
-    - @namespaces.in_groups_of(6, false) do |group|
-      .row
-        - group.each do |namespace|
-          .col-md-2.col-sm-3
-            - if fork = namespace.find_fork_of(@project)
-              .fork-thumbnail
-                = link_to project_path(fork), title: "Visit project fork", class: 'has-tooltip' do
-                  = image_tag namespace_icon(namespace, 100)
-                  .caption
-                    %strong
-                      = namespace.human_name
-                    %div.text-primary
-                      Already forked
-
-            - else
-              .fork-thumbnail
-                = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has-tooltip' do
-                  = image_tag namespace_icon(namespace, 100)
-                  .caption
-                    %strong
-                      = namespace.human_name
-
-    %p.light
-      Fork is a copy of a project repository.
+.row.prepend-top-default
+  .col-lg-3
+    %h4.prepend-top-0
+      Fork project
+    %p
+      A fork is a copy of a project.
       %br
-      Forking a repository allows you to do changes without affecting the original project.
-- else
-  %h3 No available namespaces to fork the project
-  %p.slead
-    You must have permission to create a project in a namespace before forking.
+      Forking a repository allows you to make changes without affecting the original project.
+  .col-lg-9
+    .fork-namespaces
+      - if @namespaces.present?
+        %label.label-light
+          %span
+            Click to fork the project to a user or group
+          - @namespaces.in_groups_of(6, false) do |group|
+            .row
+              - group.each do |namespace|
+                - avatar = namespace_icon(namespace, 100)
+                - if fork = namespace.find_fork_of(@project)
+                  .fork-thumbnail.forked
+                    = link_to project_path(fork) do
+                      - if /no_((\w*)_)*avatar/.match(avatar)
+                        .no-avatar
+                          = icon 'question'
+                      - else
+                        = image_tag avatar
+                      .caption
+                        = namespace.human_name
+                - else
+                  .fork-thumbnail
+                    = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
+                      - if /no_((\w*)_)*avatar/.match(avatar)
+                        .no-avatar
+                          = icon 'question'
+                      - else
+                        = image_tag avatar
+                      .caption
+                        = namespace.human_name
+      - else
+        %label.label-light
+          %span
+            No available namespaces to fork the project.
+            %br
+            %small
+              You must have permission to create a project in a namespace before forking.
 
-.save-project-loader.hide
-  .center
-    %h2
-      %i.fa.fa-spinner.fa-spin
-      Forking repository
-    %p Please wait a moment, this page will automatically refresh when ready.
+    .save-project-loader.hide
+      .center
+        %h2
+          %i.fa.fa-spinner.fa-spin
+          Forking repository
+        %p Please wait a moment, this page will automatically refresh when ready.
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 542827b2f1542db5f27be251b453bf5580cd84c2..331dc1fcc29f1165b7bfbadaa039d8ff9fbbc415 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -51,7 +51,7 @@
   %td.duration
     - if generic_commit_status.duration
       = icon("clock-o")
-      #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)}
+      = time_interval_in_words(generic_commit_status.duration)
 
   %td.timestamp
     - if generic_commit_status.finished_at
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index ca347406dfe383fce067f543f889dde8aebbcce5..45e51389c00f9e3f4f141e098631ff333a8e04ca 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -3,7 +3,7 @@
 
     - content_for :page_specific_javascripts do
       = page_specific_javascript_tag('lib/chart.js')
-      = page_specific_javascript_tag('graphs/application.js')
+      = page_specific_javascript_tag('graphs/graphs_bundle.js')
     = nav_link(action: :show) do
       = link_to 'Contributors', namespace_project_graph_path
     = nav_link(action: :commits) do
diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml
index c58223fd39e644c66e6d9f71e9a9dd97ab2e82fa..195f18afc7614725774dc5697e42a58be832e1d4 100644
--- a/app/views/projects/graphs/ci/_build_times.haml
+++ b/app/views/projects/graphs/ci/_build_times.haml
@@ -19,4 +19,9 @@
     ]
   }
   var ctx = $("#build_timesChart").get(0).getContext("2d");
-  new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+  var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+  if (window.innerWidth < 768) {
+    // Scale fonts if window width lower than 768px (iPad portrait)
+    options.scaleFontSize = 8
+  }
+  new Chart(ctx).Bar(data, options);
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
index 8fca07114fad7d8577930924c7d4c7b955c68ee7..1fbf6ca2c1ccad7a9812e362a09a8cba6a568b11 100644
--- a/app/views/projects/graphs/ci/_builds.haml
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -48,4 +48,9 @@
       ]
     }
     var ctx = $("##{scope}Chart").get(0).getContext("2d");
-    new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+    var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+    if (window.innerWidth < 768) {
+      // Scale fonts if window width lower than 768px (iPad portrait)
+      options.scaleFontSize = 8
+    }
+    new Chart(ctx).Line(data, options);
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 65db8af494d4926cc93a12bd46840c9875a9fee4..7e34a89f9ae772aa694c2c9b538824b6c2b701a2 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -59,6 +59,10 @@
     var container = $(selector).parent();
     var generateChart = function() {
       selector.attr('width', $(container).width());
+      if (window.innerWidth < 768) {
+        // Scale fonts if window width lower than 768px (iPad portrait)
+        options.scaleFontSize = 8
+      }
       return new Chart(ctx).Bar(data, options);
     };
     // enabling auto-resizing
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index a985b442b2d6e6c303c701d1cd702f4c2c948916..ac5f792d1402dbc1b3a90fe66019d84e55ea2417 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -32,7 +32,7 @@
 :javascript
   $.ajax({
     type: "GET",
-    url: location.href,
+    url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}",
     dataType: "json",
     success: function (data) {
       var graph = new ContributorsStatGraph();
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 403adb7426bacc3a40da31a80924baf72dfb0c38..60b45115b73bc69746ba914d9d7cd7ca25431d5b 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -2,7 +2,7 @@
   %ul{ class: (container_class) }
     - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
       = nav_link(controller: :issues) do
-        = link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do
+        = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
           %span
             Issues
 
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..72669372497dc53193d858368bc43d13f2153db4
--- /dev/null
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -0,0 +1,27 @@
+.issues-footer.text-center
+  %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } }
+    Email a new issue to this project
+
+#issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
+  .modal-dialog{ role: "document" }
+    .modal-content
+      .modal-header
+        %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } }
+          %span{ aria: { hidden: "true" } }= icon("times")
+        %h4.modal-title
+          Create new issue by email
+      .modal-body
+        %p
+          Write an email to the below email address. (This is a private email address, so keep it secret.)
+        .email-modal-input-group.input-group
+          = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
+          .input-group-btn
+            = clipboard_button(clipboard_target: '#issue_email')
+        %p
+          Send an email to this address to create an issue.
+        %p
+          Use the subject line as the title of your issue.
+        %p
+          Use the message as the body of your issue (feel free to include some nice
+          = succeed ")." do
+            = link_to "Markdown", help_page_path('markdown', 'markdown')
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index e93b7e0d66d11103946754eedcefd34c9b33fd6e..24749699c6d3be5eb09f3280558f7625a3b295e3 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -2,7 +2,7 @@
   .pull-right
     #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)}
       = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid),
-        method: :post, class: 'btn has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
+        method: :post, class: 'btn btn-new btn-inverted has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do
         .checking
           = icon('spinner spin')
           Checking branches
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index c6fc499a7b8b348378bd4f8c1da9473ae12fd472..6ea9f612d13abe4f6560553acc610fbbf539e4b2 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -4,8 +4,8 @@
   %ul.unstyled-list
     - @related_branches.each do |branch|
       %li
-        - sha = @project.repository.find_branch(branch).target
-        - pipeline = @project.pipeline(sha, branch) if sha
+        - target = @project.repository.find_branch(branch).target
+        - pipeline = @project.pipeline(target.sha, branch) if target
         - if pipeline
           %span.related-branch-ci-status
             = render_pipeline_status(pipeline)
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7612fe3719aa2c4f0be95d3f2f0c97cabf849303..1a87045aa60619900f852f0d54c1699c9127be33 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,5 +1,6 @@
 - @no_container = true
 - page_title "Issues"
+- new_issue_email = @project.new_issue_address(current_user)
 = render "projects/issues/head"
 
 = content_for :meta_tags do
@@ -18,12 +19,20 @@
               Subscribe
         = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
         - if can? current_user, :create_issue, @project
-          = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+          = link_to new_namespace_project_issue_path(@project.namespace,
+                                                     @project,
+                                                     issue: { assignee_id: issues_finder.assignee.try(:id),
+                                                              milestone_id: issues_finder.milestones.first.try(:id) }),
+                                                     class: "btn btn-new",
+                                                     title: "New Issue",
+                                                     id: "new_issue_link" do
             New Issue
     = render 'shared/issuable/filter', type: :issues
 
     .issues-holder
-      = render "issues"
+      = render 'issues'
+      - if new_issue_email
+        = render 'issue_by_email', email: new_issue_email
   - else
     .blank-state.blank-state-welcome
       %h2.blank-state-title.blank-state-welcome-title
@@ -40,3 +49,5 @@
       - if can? current_user, :create_issue, @project
         = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
           New Issue
+        - if new_issue_email
+          = render 'issue_by_email', email: new_issue_email
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 9b6a97c0959f83c5de36c29cd089b5ada45a0b2f..e5cce16a171677e1f91ede838020c67c5e3d0b0b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -38,7 +38,7 @@
               %li
                 = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
         - if can?(current_user, :create_issue, @project)
-          = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+          = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
             New issue
         - if can?(current_user, :update_issue, @issue)
           = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 489c632ae229fd77d72e37f92eb2226b207b84ee..6ef640bb6543d1446ca42b3d71b6543b9260aedf 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,6 +1,6 @@
 - if @pipeline
   .mr-widget-heading
-    - %w[success skipped canceled failed running pending].each do |status|
+    - %w[success success_with_warnings skipped canceled failed running pending].each do |status|
       .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
         = ci_icon_for_status(status)
         %span
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 091af4df4a1fdec89a70598301a9c8166d40dc73..b2ece44d9663b7654ec1ef724ef8f303ecb34ac0 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,7 +1,7 @@
 - page_title "Network", @ref
 - content_for :page_specific_javascripts do
   = page_specific_javascript_tag('lib/raphael.js')
-  = page_specific_javascript_tag('network/application.js')
+  = page_specific_javascript_tag('network/network_bundle.js')
 = render "projects/commits/head"
 = render "head"
 %div{ class: container_class }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index c72d0140bb9c9faf15a6aa1f7b052d985279b1f0..facdfcc9447f71fc0970833eadd6e49a63cb9063 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -89,9 +89,9 @@
                     = link_to "#", class: 'btn js-toggle-button import_git' do
                       %i.fa.fa-git
                       %span Repo by URL
-                %div
+                %div{ class: 'import_gitlab_project' }
                   - if gitlab_project_import_enabled?
-                    = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do
+                    = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
                       %i.fa.fa-gitlab
                       %span GitLab export
 
@@ -130,29 +130,29 @@
     $(".modal").hide();
   });
 
-  $('.import_gitlab_project').bind('click', function() {
-    var _href = $("a.import_gitlab_project").attr("href");
-    $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
+  $('.btn_import_gitlab_project').bind('click', function() {
+    var _href = $("a.btn_import_gitlab_project").attr("href");
+    $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
   });
 
-  $('.import_gitlab_project').attr('disabled',true)
-  $('.import_gitlab_project').attr('title', 'Project path required.');
+  $('.btn_import_gitlab_project').attr('disabled',true)
+  $('.import_gitlab_project').attr('title', 'Project path and name required.');
 
   $('.import_gitlab_project').click(function( event ) {
-    if($('.import_gitlab_project').attr('disabled')) {
+    if($('.btn_import_gitlab_project').attr('disabled')) {
       event.preventDefault();
-      new Flash("Please enter a path for the project to be imported to.");
+      new Flash("Please enter path and name for the project to be imported to.");
     }
   });
 
   $('#project_path').keyup(function(){
     if($(this).val().length !=0) {
-      $('.import_gitlab_project').attr('disabled', false);
+      $('.btn_import_gitlab_project').attr('disabled', false);
       $('.import_gitlab_project').attr('title','');
       $(".flash-container").html("")
     } else {
-      $('.import_gitlab_project').attr('disabled',true);
-      $('.import_gitlab_project').attr('title', 'Project path required.');
+      $('.btn_import_gitlab_project').attr('disabled',true);
+      $('.import_gitlab_project').attr('title', 'Project path and name required.');
     }
   });
 
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
deleted file mode 100644
index ec6c4938efc4e072cdfc226e3ff79fce9a0db3fa..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- note = notes.first
-%tr.notes_holder
-  %td.notes_line{ colspan: 2 }
-  %td.notes_content
-    %ul.notes{ data: { discussion_id: note.discussion_id } }
-      = render partial: "projects/notes/note", collection: notes, as: :note
-    = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
deleted file mode 100644
index e50a4f86d03945013558a80a331c93763e9b20ef..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- note_left = notes_left.present? ? notes_left.first : nil
-- note_right = notes_right.present? ? notes_right.first : nil
-
-%tr.notes_holder
-  - if note_left
-    %td.notes_line.old
-    %td.notes_content.parallel.old
-      %ul.notes{ data: { discussion_id: note_left.discussion_id } }
-        = render partial: "projects/notes/note", collection: notes_left, as: :note
-
-      = link_to_reply_discussion(note_left, 'old')
-  - else
-    %td.notes_line.old= ""
-    %td.notes_content.parallel.old= ""
-
-  - if note_right
-    %td.notes_line.new
-    %td.notes_content.parallel.new
-      %ul.notes{ data: { discussion_id: note_right.discussion_id } }
-        = render partial: "projects/notes/note", collection: notes_right, as: :note
-
-      = link_to_reply_discussion(note_right, 'new')
-  - else
-    %td.notes_line.new= ""
-    %td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
deleted file mode 100644
index 7869d6413d8d877d93b2776e263c452b5e5e84e0..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/_discussion.html.haml
+++ /dev/null
@@ -1,46 +0,0 @@
-- note = discussion_notes.first
-- expanded = !note.diff_note? || note.active?
-%li.note.note-discussion.timeline-entry
-  .timeline-entry-inner
-    .timeline-icon
-      = link_to user_path(note.author) do
-        = image_tag avatar_icon(note.author), class: "avatar s40"
-    .timeline-content
-      .discussion.js-toggle-container{ class: note.discussion_id }
-        .discussion-header
-          = link_to_member(@project, note.author, avatar: false)
-
-          .inline.discussion-headline-light
-            = note.author.to_reference
-            started a discussion on
-
-            - if note.for_commit?
-              - commit = note.noteable
-              - if commit
-                commit
-                = link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace'
-              - else
-                a deleted commit
-            - else
-              - if note.active?
-                = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
-                  the diff
-              - else
-                an outdated diff
-
-            = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago")
-
-          .discussion-actions
-            = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
-              - if expanded
-                = icon("chevron-up")
-              - else
-                = icon("chevron-down")
-
-              Toggle discussion
-
-        .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
-          - if note.diff_note?
-            = render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes
-          - else
-            = render "projects/notes/discussions/notes", discussion_notes: discussion_notes
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index af0046886fbbeab813e1c1bb70f9260a97eea64f..71da8ac9d7c1e582cb4ac456c099564dd47678ff 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -30,7 +30,7 @@
             = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do
               = icon('trash-o')
       .note-body{class: note_editable ? 'js-task-list-container' : ''}
-        .note-text
+        .note-text.md
           = preserve do
             = note.note_html
           = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index ebf7e8a9cb344836a85e76a5a6f436b814771907..022578bd6db539eb401a70cfa2f366f1583cc2d1 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -1,10 +1,8 @@
 - if @discussions.present?
-  - @discussions.each do |discussion_notes|
-    - note = discussion_notes.first
-    - if note_for_main_target?(note)
-      = render partial: "projects/notes/note", object: note, as: :note
+  - @discussions.each do |discussion|
+    - if discussion.for_target?(@noteable)
+      = render partial: "projects/notes/note", object: discussion.first_note, as: :note
     - else
-      = render 'projects/notes/discussion', discussion_notes: discussion_notes
+      = render 'discussions/discussion', discussion: discussion
 - else
-  - @notes.each do |note|
-    = render partial: "projects/notes/note", object: note, as: :note
+  = render partial: "projects/notes/note", collection: @notes, as: :note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 56d302fab82540fd072f73f3106329d43d13f61c..74538a9723e089cf05ac043ca9dbb5abb11c7d3f 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -14,9 +14,9 @@
       .disabled-comment.text-center
         .disabled-comment-text.inline
           Please
-          = link_to "register",new_user_session_path
+          = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
           or
-          = link_to "login",new_user_session_path
+          = link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
           to post a comment
 
 :javascript
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
deleted file mode 100644
index 4a69b8f8840ded5e235cf8383f129d56b57e3ada..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- note = discussion_notes.first
-- diff_file = note.diff_file
-- return unless diff_file
-
-- blob = note.blob
-
-.diff-file.file-holder
-  .file-title
-    = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: note.project, url: diff_note_path(note)
-
-  .diff-content.code.js-syntax-highlight
-    %table
-      - note.truncated_diff_lines.each do |line|
-        = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
-
-        - if note.for_line?(line)
-          = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml
deleted file mode 100644
index a785149549dc98c06d7f7c3ce736c9d447bd0e74..0000000000000000000000000000000000000000
--- a/app/views/projects/notes/discussions/_notes.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- note = discussion_notes.first
-.panel.panel-default
-  .notes{ data: { discussion_id: note.discussion_id } }
-    %ul.notes.timeline
-      = render partial: "projects/notes/note", collection: discussion_notes, as: :note
-  = link_to_reply_discussion(note)
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..228bad36ebd97c099db4e603c2f9b3816812bf57
--- /dev/null
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -0,0 +1,103 @@
+- page_title "CI/CD Pipelines"
+
+.row.prepend-top-default
+  .col-lg-3.profile-settings-sidebar
+    %h4.prepend-top-0
+      = page_title
+  .col-lg-9
+    %h5.prepend-top-0
+      Pipelines
+    = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f|
+      %fieldset.builds-feature
+        - unless @repository.gitlab_ci_yml
+          .form-group
+            %p Pipelines need to be configured before you can begin using Continuous Integration.
+            = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
+        .form-group
+          %p Get recent application code using the following command:
+          .radio
+            = f.label :build_allow_git_fetch_false do
+              = f.radio_button :build_allow_git_fetch, 'false'
+              %strong git clone
+              %br
+              %span.descr Slower but makes sure you have a clean dir before every build
+          .radio
+            = f.label :build_allow_git_fetch_true do
+              = f.radio_button :build_allow_git_fetch, 'true'
+              %strong git fetch
+              %br
+              %span.descr Faster
+
+        .form-group
+          = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
+          = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+          %p.help-block per build in minutes
+        .form-group
+          = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light'
+          .input-group
+            %span.input-group-addon /
+            = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+            %span.input-group-addon /
+          %p.help-block
+            We will use this regular expression to find test coverage output in build trace.
+            Leave blank if you want to disable this feature
+          .bs-callout.bs-callout-info
+            %p Below are examples of regex for existing tools:
+            %ul
+              %li
+                Simplecov (Ruby) -
+                %code \(\d+.\d+\%\) covered
+              %li
+                pytest-cov (Python) -
+                %code \d+\%\s*$
+              %li
+                phpunit --coverage-text --colors=never (PHP) -
+                %code ^\s*Lines:\s*\d+.\d+\%
+              %li
+                gcovr (C/C++) -
+                %code ^TOTAL.*\s+(\d+\%)$
+              %li
+                tap --coverage-report=text-summary (Node.js) -
+                %code ^Statements\s*:\s*([^%]+)
+
+        .form-group
+          .checkbox
+            = f.label :public_builds do
+              = f.check_box :public_builds
+              %strong Public pipelines
+            .help-block Allow everyone to access pipelines for Public and Internal projects
+
+        .form-group.append-bottom-default
+          = f.label :runners_token, "Runners token", class: 'label-light'
+          = f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
+          %p.help-block The secure token used to checkout project.
+
+        = f.submit 'Save changes', class: "btn btn-save"
+
+%hr
+
+.row.prepend-top-default
+  .col-lg-3.profile-settings-sidebar
+    %h4.prepend-top-0
+      Builds Badge
+  .col-lg-9
+    .prepend-top-10
+      .panel.panel-default
+        .panel-heading
+          %b Builds badge &middot;
+          = @build_badge.to_html
+          .pull-right
+            = render 'shared/ref_switcher', destination: 'badges', align_right: true
+        .panel-body
+          .row
+            .col-md-2.text-center
+              Markdown
+            .col-md-10.code.js-syntax-highlight
+              = highlight('.md', @build_badge.to_markdown)
+          .row
+            %hr
+          .row
+            .col-md-2.text-center
+              HTML
+            .col-md-10.code.js-syntax-highlight
+              = highlight('.html', @build_badge.to_html)
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 720d67dff7c4ff7faa08a3a0b8bd20a14b90db43..0603a014008cf43e9bb0c8312c80e605575c53f9 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -5,24 +5,22 @@
     No branches are protected, protect a branch with the form above.
 - else
   - can_admin_project = can?(current_user, :admin_project, @project)
-  .table-responsive
-    %table.table.protected-branches-list
-      %colgroup
-        %col{ width: "20%" }
-        %col{ width: "30%" }
-        %col{ width: "25%" }
-        %col{ width: "25%" }
+
+  %table.table.protected-branches-list
+    %colgroup
+      %col{ width: "20%" }
+      %col{ width: "30%" }
+      %col{ width: "25%" }
+      %col{ width: "25%" }
+    %thead
+      %tr
+        %th Branch
+        %th Last commit
+        %th Allowed to merge
+        %th Allowed to push
         - if can_admin_project
-          %col
-      %thead
-        %tr
-          %th Protected Branch
-          %th Commit
-          %th Developers Can Push
-          %th Developers Can Merge
-          - if can_admin_project
-            %th
-      %tbody
-        = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+          %th
+    %tbody
+      = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
 
   = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 7fda7f96047342065fd51532bb16c33b4e873e01..498e412235e44dca9a46eec653d37e5f5e0db97e 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -15,9 +15,15 @@
       - else
         (branch was removed from repository)
   %td
-    = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
+    = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
+    = dropdown_tag(protected_branch.merge_access_level.humanize,
+                   options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge',
+                   data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "merge_access_level" }})
   %td
-    = check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url })
+    = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level
+    = dropdown_tag(protected_branch.push_access_level.humanize,
+                   options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push',
+                   data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "push_access_level" }})
   - if can_admin_project
     %td
       = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 151e1d6485177b808d17128f050d34162a0367e3..4efe44c72335db454cd746936acc6c543b317d11 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -6,12 +6,13 @@
       = page_title
     %p Keep stable branches secure and force developers to use merge requests.
     %p.prepend-top-20
-      Protected branches are designed to:
+      By default, protected branches are designed to:
       %ul
-        %li prevent pushes from everybody except #{link_to "masters", help_page_path("user/permissions"), class: "vlink"}
-        %li prevent anyone from force pushing to the branch
-        %li prevent anyone from deleting the branch
-      %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}
+        %li prevent their creation, if not already created, from everybody except Masters
+        %li prevent pushes from everybody except Masters
+        %li prevent <strong>anyone</strong> from force pushing to the branch
+        %li prevent <strong>anyone</strong> from deleting the branch
+      %p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
   .col-lg-9
     %h5.prepend-top-0
       Protect a branch
@@ -23,7 +24,7 @@
           = f.label :name, "Branch", class: "label-light"
           = render partial: "dropdown", locals: { f: f }
           %p.help-block
-            = link_to "Wildcards", help_page_path('workflow/protected_branches', anchor: "wildcard-protected-branches")
+            = link_to "Wildcards", help_page_path('user/project/protected_branches', anchor: "wildcard-protected-branches")
             such as
             %code *-stable
             or
@@ -31,18 +32,22 @@
             are supported.
 
         .form-group
-          = f.check_box :developers_can_push, class: "pull-left"
-          .prepend-left-20
-            = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
-            %p.light.append-bottom-0
-              Allow developers to push to this branch
+          = hidden_field_tag 'protected_branch[merge_access_level_attributes][access_level]'
+          = label_tag "Allowed to merge: ", nil, class: "label-light append-bottom-0"
+          = dropdown_tag("<Make a selection>",
+                         options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge',
+                                    dropdown_class: 'dropdown-menu-selectable',
+                                    data: { field_name: "protected_branch[merge_access_level_attributes][access_level]" }})
 
         .form-group
-          = f.check_box :developers_can_merge, class: "pull-left"
-          .prepend-left-20
-            = f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0"
-            %p.light.append-bottom-0
-              Allow developers to accept merge requests to this branch
+          = hidden_field_tag 'protected_branch[push_access_level_attributes][access_level]'
+          = label_tag "Allowed to push: ", nil, class: "label-light append-bottom-0"
+          = dropdown_tag("<Make a selection>",
+                         options: { title: "Allowed to push", toggle_class: 'allowed-to-push',
+                                    dropdown_class: 'dropdown-menu-selectable',
+                                    data: { field_name: "protected_branch[push_access_level_attributes][access_level]" }})
+
+
         = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
 
     %hr
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 166dc4a01fc3dadb96714fa1723018ecd6f28b4d..752fbc21a111e9cfbb56ea5d637c58b03fef4b99 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -8,6 +8,7 @@
   .col-lg-9
     = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
       = render 'shared/service_settings', form: form
+
       = form.submit 'Save changes', class: 'btn btn-save'
       &nbsp;
       - if @service.valid? && @service.activated?
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index dd1cf680cfadbc129e32ba20b05ed1edc153a50f..a666d07e9ebac91030631e50cc616623b4c95481 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -43,6 +43,10 @@
       %li
         = link_to 'Contribution guide', contribution_guide_path(@project)
 
+    - if @repository.gitlab_ci_yml
+      %li
+        = link_to 'CI configuration', ci_configuration_path(@project)
+
     - if current_user && can_push_branch?(@project, @project.default_branch)
       - unless @repository.changelog
         %li.missing
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index b7d7d5c5382da27ecb1d83113ce49797d2c9e0de..395d7af6cbb4d033ea539e5c2fabfc0fe35fce79 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,36 +1,39 @@
+- @no_container = true
 - page_title @tag.name, "Tags"
 = render "projects/commits/head"
 
-.row-content-block
-  .pull-right
-    - if can?(current_user, :push_code, @project)
-      = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
-        = icon("pencil")
-    = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
-      = icon('files-o')
-    = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
-      = icon('history')
-    - if can? current_user, :download_code, @project
-      = render 'projects/tags/download', ref: @tag.name, project: @project
-    - if can?(current_user, :admin_project, @project)
-      .pull-right
-        = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
-          %i.fa.fa-trash-o
-  .title
-    %span.item-title= @tag.name
-  - if @commit
-    = render 'projects/branches/commit', commit: @commit, project: @project
-  - else
-    Cant find HEAD commit for this tag
-  - if @tag.message.present?
-    %pre.body
-      = strip_gpg_signature(@tag.message)
+%div{ class: container_class }
+  .sub-header-block
+    .pull-right.tag-buttons
+      - if can?(current_user, :push_code, @project)
+        = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
+          = icon("pencil")
+      = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
+        = icon('files-o')
+      = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
+        = icon('history')
+      - if can? current_user, :download_code, @project
+        = render 'projects/tags/download', ref: @tag.name, project: @project
+      - if can?(current_user, :admin_project, @project)
+        .pull-right
+          = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+            %i.fa.fa-trash-o
+    .tag-info.append-bottom-10
+      .title
+        %span.item-title= @tag.name
+      - if @commit
+        = render 'projects/branches/commit', commit: @commit, project: @project
+      - else
+        Cant find HEAD commit for this tag
+    - if @tag.message.present?
+      %pre.body
+        = strip_gpg_signature(@tag.message)
 
-.append-bottom-default.prepend-top-default
-  - if @release.description.present?
-    .description
-      .wiki
-        = preserve do
-          = markdown @release.description
-  - else
-    This tag has no release notes.
+  .append-bottom-default.prepend-top-default
+    - if @release.description.present?
+      .description
+        .wiki
+          = preserve do
+            = markdown @release.description
+    - else
+      This tag has no release notes.
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index a3a4bd4f7527bb1d908fab5b98f47847807a4b11..84da16b6bb15fbfc0125c3159612deeb9de93284 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
 %span.str-truncated
-  = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
+  = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 7d9bd08385afc67ae89793a40558547530e01598..dcf1f767bf7967a8c4f9ab97bb1ea94b211c2c5e 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
     $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
     $('.save-project-loader').hide();
     $('.project-edit-container').show();
-    $('.project-edit-content .btn-save').enable();
+    $('.edit-project .btn-save').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 797a1a59e9f7e5cab24e5c9b26b5dd72517b65e2..643f7c589e6545243e5448eb967d63daf2a388eb 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -18,9 +18,14 @@
       .error-alert
 
       .help-block
-        To link to a (new) page, simply type
-        %code [Link Title](page-slug)
-        \.
+        = succeed '.' do
+          To link to a (new) page, simply type
+          %code [Link Title](page-slug)
+
+        = succeed '.' do
+          More examples are in the
+          = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown")
+
   .form-group
     = f.label :commit_message, class: 'control-label'
     .col-sm-10= f.text_field :message, class: 'form-control', rows: 18
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 8163aff43b67226960864acf3e44cc787ef3d67c..e040008387034a27bca2109e4d76ee0daa9f0877 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,6 +1,7 @@
 - project = note.project
 - note_url = Gitlab::UrlBuilder.build(note)
-- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
+- noteable_identifier = note.noteable.try(:iid) || note.noteable.try(:id)
+
 .search-result-row
   %h5.note-search-caption.str-truncated
     %i.fa.fa-comment
@@ -10,7 +11,10 @@
     &middot;
 
     - if note.for_commit?
-      = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
+      = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do
+        = truncate_sha(note.commit_id)
+        %span.light Commit deleted
+
     - else
       %span #{note.noteable_type.titleize} ##{noteable_identifier}
       &middot;
diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..53a99a736c0ea7d1b97f1f89ecb2e1fe48071953
--- /dev/null
+++ b/app/views/shared/_allow_request_access.html.haml
@@ -0,0 +1,6 @@
+.checkbox
+  = form.label :request_access_enabled do
+    = form.check_box :request_access_enabled
+    %strong Allow users to request access
+    %br
+    %span.descr Allow users to request access if visibility is public or internal.
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 4eaf7c2a025aad454ab734b90347b40a4f14fd84..5254d2659186cb0ca49814224a895db9be492ffd 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -10,69 +10,28 @@
   .col-sm-10
     = form.check_box :active
 
-- if @service.supported_events.length > 1
-  .form-group
-    = form.label :url, "Trigger", class: 'control-label'
-    .col-sm-10
-      - if @service.supported_events.include?("push")
-        %div
-          = form.check_box :push_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :push_events, class: 'list-label' do
-              %strong Push events
-            %p.light
-              This url will be triggered by a push to the repository
-      - if @service.supported_events.include?("tag_push")
-        %div
-          = form.check_box :tag_push_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :tag_push_events, class: 'list-label' do
-              %strong Tag push events
-            %p.light
-              This url will be triggered when a new tag is pushed to the repository
-      - if @service.supported_events.include?("note")
-        %div
-          = form.check_box :note_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :note_events, class: 'list-label' do
-              %strong Comments
-            %p.light
-              This url will be triggered when someone adds a comment
-      - if @service.supported_events.include?("issue")
-        %div
-          = form.check_box :issues_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :issues_events, class: 'list-label' do
-              %strong Issues events
-            %p.light
-              This url will be triggered when an issue is created/updated/merged
-      - if @service.supported_events.include?("merge_request")
-        %div
-          = form.check_box :merge_requests_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :merge_requests_events, class: 'list-label' do
-              %strong Merge Request events
-            %p.light
-              This url will be triggered when a merge request is created/updated/merged
-      - if @service.supported_events.include?("build")
-        %div
-          = form.check_box :build_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :build_events, class: 'list-label' do
-              %strong Build events
-            %p.light
-              This url will be triggered when a build status changes
-      - if @service.supported_events.include?("wiki_page")
-        %div
-          = form.check_box :wiki_page_events, class: 'pull-left'
-          .prepend-left-20
-            = form.label :wiki_page_events, class: 'list-label' do
-              %strong Wiki Page events
-            %p.light
-              This url will be triggered when a wiki page is created/updated
+.form-group
+  = form.label :url, "Trigger", class: 'control-label'
+
+  .col-sm-10
+    - @service.supported_events.each do |event|
+      %div
+        = form.check_box service_event_field_name(event), class: 'pull-left'
+        .prepend-left-20
+          = form.label service_event_field_name(event), class: 'list-label' do
+            %strong
+              = event.humanize
+
+      - field = @service.event_field(event)
+
+      - if field
+        %p
+          = form.text_field field[:name], class: "form-control", placeholder: field[:placeholder]
 
+      %p.light
+        = service_event_description(event)
 
-- @service.fields.each do |field|
+- @service.global_fields.each do |field|
   - type = field[:type]
 
   - if type == 'fieldset'
diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a21f8f3a95151f554d21afc9d66d0f7df16ce570
--- /dev/null
+++ b/app/views/shared/icons/_icon_fork.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
+  <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_cancel.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fd1ebbcbabda8928110260ab8877f317f6a70c07
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_cancel.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#5C5C5C" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
+  </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e56e0887416f495466c55a9350642222a5120044
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_failed.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#D22852" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <path d="M7.72916667,6.27083333 L7.72916667,4.28939247 C7.72916667,4.12531853 7.59703895,4 7.43405116,4 L6.56594884,4 C6.40541585,4 6.27083333,4.12956542 6.27083333,4.28939247 L6.27083333,6.27083333 L4.28939247,6.27083333 C4.12531853,6.27083333 4,6.40296105 4,6.56594884 L4,7.43405116 C4,7.59458415 4.12956542,7.72916667 4.28939247,7.72916667 L6.27083333,7.72916667 L6.27083333,9.71060753 C6.27083333,9.87468147 6.40296105,10 6.56594884,10 L7.43405116,10 C7.59458415,10 7.72916667,9.87043458 7.72916667,9.71060753 L7.72916667,7.72916667 L9.71060753,7.72916667 C9.87468147,7.72916667 10,7.59703895 10,7.43405116 L10,6.56594884 C10,6.40541585 9.87043458,6.27083333 9.71060753,6.27083333 L7.72916667,6.27083333 Z" transform="rotate(-45 7 7)"/>
+  </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg
new file mode 100644
index 0000000000000000000000000000000000000000..117f036716146a8eb89122b21b93d528ddf6a0d8
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_pending.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#E75E40" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <path d="M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z"/>
+  </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg
new file mode 100644
index 0000000000000000000000000000000000000000..920d7952eb5c51e3514e039f7fcdfef634181de1
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_running.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#2D9FD8" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <path d="M7,3 C9.209139,3 11,4.790861 11,7 C11,9.209139 9.209139,11 7,11 C5.65802855,11 4.47040669,10.3391508 3.74481446,9.32513253 L7,7 L7,3 L7,3 Z"/>
+  </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
new file mode 100644
index 0000000000000000000000000000000000000000..67b378b3571d6cf7723df009d65cae497ae54514
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_success.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#31AF64" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <path d="M7.29166667,7.875 L5.54840803,7.875 C5.38293028,7.875 5.25,8.00712771 5.25,8.17011551 L5.25,9.03821782 C5.25,9.19875081 5.38360183,9.33333333 5.54840803,9.33333333 L8.24853534,9.33333333 C8.52035522,9.33333333 8.75,9.11228506 8.75,8.83960819 L8.75,8.46475969 L8.75,4.07392947 C8.75,3.92144267 8.61787229,3.79166667 8.45488449,3.79166667 L7.58678218,3.79166667 C7.42624919,3.79166667 7.29166667,3.91804003 7.29166667,4.07392947 L7.29166667,7.875 Z" transform="rotate(45 7 6.563)"/>
+  </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d0ad4bd65b19f48d0082fb97e0a56a3bcaab4306
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_warning.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+  <g fill="#FF8A24" fill-rule="evenodd">
+    <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+    <path d="M6,3.49769878 C6,3.22282734 6.21403503,3 6.50468445,3 L7.49531555,3 C7.77404508,3 8,3.21484375 8,3.49769878 L8,7.50230122 C8,7.77717266 7.78596497,8 7.49531555,8 L6.50468445,8 C6.22595492,8 6,7.78515625 6,7.50230122 L6,3.49769878 Z M6,9.50468445 C6,9.22595492 6.21403503,9 6.50468445,9 L7.49531555,9 C7.77404508,9 8,9.21403503 8,9.50468445 L8,10.4953156 C8,10.7740451 7.78596497,11 7.49531555,11 L6.50468445,11 C6.22595492,11 6,10.785965 6,10.4953156 L6,9.50468445 Z"/>
+  </g>
+</svg>
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 0acb825313991095783dd45daa02b96d75b25544..4e280c371acc253702982ad989cf8860e59b2377 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -4,7 +4,7 @@
 - filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
 .dropdown-page-one
   = dropdown_title(title)
-  = dropdown_filter(filter_placeholder, search_id: "label-name")
+  = dropdown_filter(filter_placeholder)
   = dropdown_content
   - if @project && show_footer
     = dropdown_footer do
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index e020a7d4d00ccedca7669c011b2008b67041a8ac..8e2fcbdfab8c24090ace207c8a4e9c8c53d63415 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -156,7 +156,7 @@
 
       - project_ref = cross_project_reference(@project, issuable)
       .block.project-reference
-        .sidebar-collapsed-icon
+        .sidebar-collapsed-icon.dont-change-state
           = clipboard_button(clipboard_text: project_ref)
         .cross-project-reference.hide-collapsed
           %span
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index db2b4885861ec23a2f3cb098eec3f246dd741533..c7f39868e71f5487fd052d89bfbc3ab2c93215d2 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -2,7 +2,7 @@
 - page_description @user.bio
 - content_for :page_specific_javascripts do
   = page_specific_javascript_tag('lib/d3.js')
-  = page_specific_javascript_tag('users/application.js')
+  = page_specific_javascript_tag('users/users_bundle.js')
 - header_title     @user.name, user_path(@user)
 - @no_container = true
 
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index f2649e38eb34baafeaf2eded7f17821ff46ade8f..842eebdea9e2e63ec826daa1487357f517c6cfe0 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -21,31 +21,35 @@ class EmailReceiverWorker
     return unless raw.present?
 
     can_retry = false
-    reason = nil
-
-    case e
-    when Gitlab::Email::Receiver::SentNotificationNotFoundError
-      reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
-    when Gitlab::Email::Receiver::EmptyEmailError
-      can_retry = true
-      reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
-    when Gitlab::Email::Receiver::AutoGeneratedEmailError
-      reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
-    when Gitlab::Email::Receiver::UserNotFoundError
-      reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
-    when Gitlab::Email::Receiver::UserBlockedError
-      reason = "Your account has been blocked. If you believe this is in error, contact a staff member."
-    when Gitlab::Email::Receiver::UserNotAuthorizedError
-      reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
-    when Gitlab::Email::Receiver::NoteableNotFoundError
-      reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
-    when Gitlab::Email::Receiver::InvalidNoteError
-      can_retry = true
-      reason = e.message
-    else
-      return
+    reason =
+      case e
+      when Gitlab::Email::UnknownIncomingEmail
+        "We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+      when Gitlab::Email::SentNotificationNotFoundError
+        "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+      when Gitlab::Email::ProjectNotFound
+        "We couldn't find the project. Please check if there's any typo."
+      when Gitlab::Email::EmptyEmailError
+        can_retry = true
+        "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+      when Gitlab::Email::AutoGeneratedEmailError
+        "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
+      when Gitlab::Email::UserNotFoundError
+        "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+      when Gitlab::Email::UserBlockedError
+        "Your account has been blocked. If you believe this is in error, contact a staff member."
+      when Gitlab::Email::UserNotAuthorizedError
+        "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+      when Gitlab::Email::NoteableNotFoundError
+        "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+      when Gitlab::Email::InvalidNoteError,
+           Gitlab::Email::InvalidIssueError
+        can_retry = true
+        e.message
+      end
+
+    if reason
+      EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
     end
-
-    EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
   end
 end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 8551288e2f24f15b9b1a40f313712d6a89d2c87b..0b6a01a3200829b86fdd8ca2b25a37457bafcffb 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -28,12 +28,12 @@ class EmailsOnPushWorker
         :push
       end
 
-    merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
-
     diff_refs = nil
     compare = nil
     reverse_compare = false
+
     if action == :push
+      merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
       compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
 
       diff_refs = Gitlab::Diff::DiffRefs.new(
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
index 47c5a670ed4d17eb00a71ef7c4ccd542577c859e..a2e49c61f59543d917dbdfff4311ca9c445124d8 100644
--- a/app/workers/repository_archive_cache_worker.rb
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -4,6 +4,6 @@ class RepositoryArchiveCacheWorker
   sidekiq_options queue: :default
 
   def perform
-    Repository.clean_old_archives
+    RepositoryArchiveCleanUpService.new.execute
   end
 end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index f7604e48f8320778277c4140d3d10629502f20a4..d69d6037053d3da556ab1b80fde4a75a529706b9 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -4,7 +4,7 @@ class RepositoryForkWorker
 
   sidekiq_options queue: :gitlab_shell
 
-  def perform(project_id, source_path, target_path)
+  def perform(project_id, forked_from_repository_storage_path, source_path, target_path)
     project = Project.find_by_id(project_id)
 
     unless project.present?
@@ -12,7 +12,8 @@ class RepositoryForkWorker
       return
     end
 
-    result = gitlab_shell.fork_repository(project.repository_storage_path, source_path, target_path)
+    result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path,
+                                          project.repository_storage_path, target_path)
     unless result
       logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
       project.mark_import_as_failed('The project could not be forked.')
diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9dd228a248377de6d5f01e7f7b3ff0dfec6e5ad3
--- /dev/null
+++ b/app/workers/requests_profiles_worker.rb
@@ -0,0 +1,9 @@
+class RequestsProfilesWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: :default
+
+  def perform
+    Gitlab::RequestProfiler.remove_all_profiles
+  end
+end
diff --git a/config/application.rb b/config/application.rb
index 5f7b6a3c049467e62001965f6639782dcc59daa7..06ebb14a5fea258e2a6bdbfff6cb6319e2a81a69 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -76,15 +76,15 @@ module Gitlab
 
     # Enable the asset pipeline
     config.assets.enabled = true
-    config.assets.paths << Gemojione.index.images_path
+    config.assets.paths << Gemojione.images_path
     config.assets.precompile << "*.png"
     config.assets.precompile << "print.css"
     config.assets.precompile << "notify.css"
     config.assets.precompile << "mailers/*.css"
-    config.assets.precompile << "graphs/application.js"
-    config.assets.precompile << "users/application.js"
-    config.assets.precompile << "network/application.js"
-    config.assets.precompile << "profile/application.js"
+    config.assets.precompile << "graphs/graphs_bundle.js"
+    config.assets.precompile << "users/users_bundle.js"
+    config.assets.precompile << "network/network_bundle.js"
+    config.assets.precompile << "profile/profile_bundle.js"
     config.assets.precompile << "lib/utils/*.js"
     config.assets.precompile << "lib/*.js"
     config.assets.precompile << "u2f.js"
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 293f2b71d65df0d4d3a85a4c49d4af45a466283c..74325872b09713cc318e1d94d94ff72176adc7e1 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -68,6 +68,25 @@
     :why: https://opensource.org/licenses/BSD-2-Clause
     :versions: []
     :when: 2016-05-02 05:55:09.796363000 Z
+- - :whitelist
+  - LGPLv2+
+  - :who: Stan Hu
+    :why: Equivalent to LGPLv2
+    :versions: []
+    :when: 2016-06-07 17:14:10.907682000 Z
+- - :whitelist
+  - Artistic 2.0
+  - :who: Josh Frye
+    :why: Disk/mount information display on Admin pages
+    :versions: []
+    :when: 2016-06-29 16:32:45.432113000 Z
+- - :whitelist
+  - Simplified BSD
+  - :who: Douwe Maan
+    :why: https://opensource.org/licenses/BSD-2-Clause
+    :versions: []
+    :when: 2016-07-26 21:24:07.248480000 Z
+
 
 # LICENSE BLACKLIST
 - - :blacklist
@@ -175,15 +194,3 @@
     :why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
     :versions: []
     :when: 2016-05-02 05:56:50.696858000 Z
-- - :whitelist
-  - LGPLv2+
-  - :who: Stan Hu
-    :why: Equivalent to LGPLv2
-    :versions: []
-    :when: 2016-06-07 17:14:10.907682000 Z
-- - :whitelist
-  - Artistic 2.0
-  - :who: Josh Frye
-    :why: Disk/mount information display on Admin pages
-    :versions: []
-    :when: 2016-06-29 16:32:45.432113000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 325eca72862841ab5c8d251edebfba41388b485c..1470a6e2550baf123c40c5765fb6dad942c08169 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -106,8 +106,8 @@ production: &base
 
     ## Repository downloads directory
     # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
-    # The default is 'tmp/repositories' relative to the root of the Rails app.
-    # repository_downloads_path: tmp/repositories
+    # The default is 'shared/cache/archive/' relative to the root of the Rails app.
+    # repository_downloads_path: shared/cache/archive/
 
   ## Reply by email
   # Allow users to comment on issues and merge requests by replying to notification emails.
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 51d93e8cde088b9abf4ae114f098ae8d4eaf554c..49130f37b3186e852e1e9a695d98a13f74aa2029 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -211,8 +211,7 @@ Settings.gitlab.default_projects_features['snippets']           = false if Setti
 Settings.gitlab.default_projects_features['builds']             = true if Settings.gitlab.default_projects_features['builds'].nil?
 Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
 Settings.gitlab.default_projects_features['visibility_level']   = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
-Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
-Settings.gitlab['restricted_signup_domains'] ||= []
+Settings.gitlab['domain_whitelist'] ||= []
 Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project]
 Settings.gitlab['trusted_proxies'] ||= []
 
@@ -291,6 +290,9 @@ Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'Repository
 Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({})
 Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
 Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker'
+Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
 
 #
 # GitLab Shell
@@ -315,6 +317,21 @@ Settings.repositories['storages'] ||= {}
 # Setting gitlab_shell.repos_path is DEPRECATED and WILL BE REMOVED in version 9.0
 Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/'
 
+#
+# The repository_downloads_path is used to remove outdated repository
+# archives, if someone has it configured incorrectly, and it points
+# to the path where repositories are stored this can cause some
+# data-integrity issue. In this case, we sets it to the default
+# repository_downloads_path value.
+#
+repositories_storages_path     = Settings.repositories.storages.values
+repository_downloads_path      = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
+repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
+
+if repository_downloads_path.blank? || repositories_storages_path.any? { |path| [repository_downloads_path, repository_downloads_full_path].include?(path.gsub(/\/$/, '')) }
+  Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
+end
+
 #
 # Backup
 #
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 3ba9e36c567c13aaacd8018524052358cbb7c0f5..d92f64e164710d65e0a7574a90ed48a556c0caa3 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -3,22 +3,27 @@ def storage_name_valid?(name)
 end
 
 def find_parent_path(name, path)
+  parent = Pathname.new(path).realpath.parent
   Gitlab.config.repositories.storages.detect do |n, p|
-    name != n && path.chomp('/').start_with?(p.chomp('/'))
+    name != n && Pathname.new(p).realpath == parent
   end
 end
 
-def error(message)
+def storage_validation_error(message)
   raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
 end
 
-error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
+def validate_storages
+  storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
 
-Gitlab.config.repositories.storages.each do |name, path|
-  error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
+  Gitlab.config.repositories.storages.each do |name, path|
+    storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
 
-  parent_name, _parent_path = find_parent_path(name, path)
-  if parent_name
-    error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
+    parent_name, _parent_path = find_parent_path(name, path)
+    if parent_name
+      storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
+    end
   end
 end
+
+validate_storages unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index c4266ab8ba5fe017810d120df62511396fdec6f5..b68a09ce730250d45a0d2cf22b8b3938a625a347 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -136,7 +136,15 @@ if Gitlab::Metrics.enabled?
     config.instrument_instance_methods(Rouge::Plugins::Redcarpet)
     config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
 
+    [:XML, :HTML].each do |namespace|
+      namespace_mod = Nokogiri.const_get(namespace)
+
+      config.instrument_methods(namespace_mod)
+      config.instrument_methods(namespace_mod::Document)
+    end
+
     config.instrument_methods(Rinku)
+    config.instrument_instance_methods(Repository)
   end
 
   GC::Profiler.enable
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index ca58ae92d1b35d1d3f34dc51c37ec63680bd64e5..3e55312020593edad806eb627e1b9e749937d7c1 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -6,5 +6,9 @@
 
 Mime::Type.register_alias "text/plain", :diff
 Mime::Type.register_alias "text/plain", :patch
-Mime::Type.register_alias 'text/html',  :markdown
-Mime::Type.register_alias 'text/html',  :md
+Mime::Type.register_alias "text/html",  :markdown
+Mime::Type.register_alias "text/html",  :md
+
+Mime::Type.register "video/mp4",  :mp4, [], [:m4v, :mov]
+Mime::Type.register "video/webm", :webm
+Mime::Type.register "video/ogg",  :ogv
diff --git a/config/initializers/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb
new file mode 100644
index 0000000000000000000000000000000000000000..59abe1b9b91984a2b68058eff8f7da8d34299a93
--- /dev/null
+++ b/config/initializers/relative_naming_ci_namespace.rb
@@ -0,0 +1,16 @@
+# Description: https://coderwall.com/p/heed_q/rails-routing-and-namespaced-models
+#
+# This allows us to use CI ActiveRecord objects in all routes and use it:
+# - [project.namespace, project, build]
+#
+# instead of:
+# - namespace_project_build_path(project.namespace, project, build)
+#
+# Without that, Ci:: namespace is used for resolving routes:
+# - namespace_project_ci_build_path(project.namespace, project, build)
+
+module Ci
+  def self.use_relative_model_naming?
+    true
+  end
+end
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a9aa802681a0211835a6f5dfb8b00fda27c56868
--- /dev/null
+++ b/config/initializers/request_profiler.rb
@@ -0,0 +1,5 @@
+require 'gitlab/request_profiler/middleware'
+
+Rails.application.configure do |config|
+  config.middleware.use(Gitlab::RequestProfiler::Middleware)
+end
diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb
deleted file mode 100644
index 9fd24a667cc2a79ecaf5559df04091104682d9b5..0000000000000000000000000000000000000000
--- a/config/initializers/secure_headers.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# CSP headers have to have single quotes, so failures relating to quotes
-# inside Ruby string arrays are irrelevant.
-# rubocop:disable Lint/PercentStringArray
-require 'gitlab/current_settings'
-include Gitlab::CurrentSettings
-
-# If Sentry is enabled and the Rails app is running in production mode,
-# this will construct the Report URI for Sentry.
-if Rails.env.production? && current_application_settings.sentry_enabled
-  uri = URI.parse(current_application_settings.sentry_dsn)
-  CSP_REPORT_URI = "#{uri.scheme}://#{uri.host}/api#{uri.path}/csp-report/?sentry_key=#{uri.user}"
-else
-  CSP_REPORT_URI = ''
-end
-
-# Content Security Policy Headers
-# For more information on CSP see:
-# - https://gitlab.com/gitlab-org/gitlab-ce/issues/18231
-# - https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
-SecureHeaders::Configuration.default do |config|
-  # Mark all cookies as "Secure", "HttpOnly", and "SameSite=Strict".
-  config.cookies = {
-    secure: true,
-    httponly: true,
-    samesite: {
-      strict: true 
-    }
-  }
-  config.x_content_type_options = "nosniff"
-  config.x_xss_protection = "1; mode=block"
-  config.x_download_options = "noopen"
-  config.x_permitted_cross_domain_policies = "none"
-  config.referrer_policy = "origin-when-cross-origin"
-  config.csp = {
-    # "Meta" values.
-    report_only: true,
-    preserve_schemes: true,
-
-    # "Directive" values.
-    # Default source allows nothing, more permissive values are set per-policy.
-    default_src: %w('none'),
-    # (Deprecated) Don't allow iframes.
-    frame_src: %w('none'),
-    # Only allow XMLHTTPRequests from the GitLab instance itself.
-    connect_src: %w('self'),
-    # Only load local fonts.
-    font_src: %w('self'),
-    # Load local images, any external image available over HTTPS.
-    img_src: %w(* 'self' data:),
-    # Audio and video can't be played on GitLab currently, so it's disabled.
-    media_src: %w('none'),
-    # Don't allow <object>, <embed>, or <applet> elements.
-    object_src: %w('none'),
-    # Allow local scripts and inline scripts.
-    script_src: %w('unsafe-inline' 'unsafe-eval' 'self'),
-    # Allow local stylesheets and inline styles.
-    style_src: %w('unsafe-inline' 'self'),
-    # The URIs that a user agent may use as the document base URL.
-    base_uri: %w('self'),
-    # Only allow local iframes and service workers
-    child_src: %w('self'),
-    # Only submit form information to the GitLab instance.
-    form_action: %w('self'),
-    # Disallow any parents from embedding a page in an iframe.
-    frame_ancestors: %w('none'),
-    # Don't allow any plugins (Flash, Shockwave, etc.)
-    plugin_types: %w(),
-    # Blocks all mixed (HTTP) content.
-    block_all_mixed_content: true,
-    # Upgrades insecure requests to HTTPS when possible.
-    upgrade_insecure_requests: true
-  }
-
-  # Reports are sent to Sentry if it's enabled.
-  if current_application_settings.sentry_enabled
-    config.csp[:report_uri] = %W(#{CSP_REPORT_URI})
-  end
-
-  # Allow Bootstrap Linter in development mode.
-  if Rails.env.development?
-    config.csp[:script_src] << "maxcdn.bootstrapcdn.com"
-  end
-
-  # reCAPTCHA
-  if current_application_settings.recaptcha_enabled
-    config.csp[:script_src] << "https://www.google.com/recaptcha/"
-    config.csp[:script_src] << "https://www.gstatic.com/recaptcha/"
-    config.csp[:frame_src] << "https://www.google.com/recaptcha/"
-    config.x_frame_options = "SAMEORIGIN"
-  end
-
-  # Gravatar
-  if current_application_settings.gravatar_enabled?
-    config.csp[:img_src] << "www.gravatar.com"
-    config.csp[:img_src] << "secure.gravatar.com"
-    config.csp[:img_src] << Gitlab.config.gravatar.host
-  end
-
-  # Piwik
-  if Gitlab.config.extra.has_key?('piwik_url') && Gitlab.config.extra.has_key?('piwik_site_id')
-    config.csp[:script_src] << Gitlab.config.extra.piwik_url
-    config.csp[:img_src] << Gitlab.config.extra.piwik_url
-  end
-
-  # Google Analytics
-  if Gitlab.config.extra.has_key?('google_analytics_id')
-    config.csp[:script_src] << "https://www.google-analytics.com"
-  end
-end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 593c14a289fa4b831a907522a038d669fa76827b..cf49ec2194c8e1e682d073262091aea10e7ffd9e 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -7,13 +7,22 @@ Sidekiq.configure_server do |config|
   config.server_middleware do |chain|
     chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
     chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
+    chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
   end
 
   # Sidekiq-cron: load recurring jobs from gitlab.yml
   # UGLY Hack to get nested hash from settingslogic
   cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json)
   # UGLY hack: Settingslogic doesn't allow 'class' key
-  cron_jobs.each { |k, v| cron_jobs[k]['class'] = cron_jobs[k].delete('job_class') }
+  cron_jobs_required_keys = %w(job_class cron)
+  cron_jobs.each do |k, v|
+    if cron_jobs[k] && cron_jobs_required_keys.all? { |s| cron_jobs[k].key?(s) }
+      cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
+    else
+      cron_jobs.delete(k)
+      Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
+    end
+  end
   Sidekiq::Cron::Job.load_from_hash! cron_jobs
 
   # Database pool should be at least `sidekiq_concurrency` + 2
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index df4a933e22f3aec9aa9a35ec5794b1252705b097..cd869657c530f7f0b6c1b76e45d25a3d0fedd973 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -7,10 +7,18 @@ module Rack
   class Request
     def trusted_proxy?(ip)
       Rails.application.config.action_dispatch.trusted_proxies.any? { |proxy| proxy === ip }
+    rescue IPAddr::InvalidAddressError
+      false
     end
   end
 end
 
+gitlab_trusted_proxies = Array(Gitlab.config.gitlab.trusted_proxies).map do |proxy|
+  begin
+    IPAddr.new(proxy)
+  rescue IPAddr::InvalidAddressError
+  end
+end.compact
+
 Rails.application.config.action_dispatch.trusted_proxies = (
-  [ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies)
-).map { |proxy| IPAddr.new(proxy) }
+  [ '127.0.0.1', '::1' ] + gitlab_trusted_proxies)
diff --git a/config/routes.rb b/config/routes.rb
index 3160fd767b84a43baa42b8b6a8e5c30faff18f50..2f5f32d9e30a4693d66fb3fbce4d6b0433a25062 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,10 +42,9 @@ Rails.application.routes.draw do
 
     resource :lint, only: [:show, :create]
 
-    resources :projects do
+    resources :projects, only: [:index, :show] do
       member do
         get :status, to: 'projects#badge'
-        get :integration
       end
     end
 
@@ -89,11 +88,10 @@ Rails.application.routes.draw do
   mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
 
   # Help
-  
-  get 'help'                  => 'help#index'
-  get 'help/*path'            => 'help#show', as: :help_page
-  get 'help/shortcuts'
-  get 'help/ui' => 'help#ui'
+  get 'help'           => 'help#index'
+  get 'help/shortcuts' => 'help#shortcuts'
+  get 'help/ui'        => 'help#ui'
+  get 'help/*path'     => 'help#show', as: :help_page
 
   #
   # Global snippets
@@ -145,13 +143,13 @@ Rails.application.routes.draw do
       get :jobs
     end
 
-    resource :gitlab, only: [:create, :new], controller: :gitlab do
+    resource :gitlab, only: [:create], controller: :gitlab do
       get :status
       get :callback
       get :jobs
     end
 
-    resource :bitbucket, only: [:create, :new], controller: :bitbucket do
+    resource :bitbucket, only: [:create], controller: :bitbucket do
       get :status
       get :callback
       get :jobs
@@ -244,7 +242,6 @@ Rails.application.routes.draw do
         get :projects
         get :keys
         get :groups
-        put :team_update
         put :block
         put :unblock
         put :unlock
@@ -282,6 +279,7 @@ Rails.application.routes.draw do
     resource :health_check, controller: 'health_check', only: [:show]
     resource :background_jobs, controller: 'background_jobs', only: [:show]
     resource :system_info, controller: 'system_info', only: [:show]
+    resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
 
     resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
       root to: 'projects#index', as: :projects
@@ -301,7 +299,7 @@ Rails.application.routes.draw do
       end
     end
 
-    resource :appearances, path: 'appearance' do
+    resource :appearances, only: [:show, :create, :update], path: 'appearance' do
       member do
         get :preview
         delete :logo
@@ -310,7 +308,7 @@ Rails.application.routes.draw do
     end
 
     resource :application_settings, only: [:show, :update] do
-      resources :services
+      resources :services, only: [:index, :edit, :update]
       put :reset_runners_token
       put :reset_health_check_token
       put :clear_repository_check_states
@@ -347,7 +345,7 @@ Rails.application.routes.draw do
     end
 
     scope module: :profiles do
-      resource :account, only: [:show, :update] do
+      resource :account, only: [:show] do
         member do
           delete :unlink
         end
@@ -359,7 +357,7 @@ Rails.application.routes.draw do
         end
       end
       resource :preferences, only: [:show, :update]
-      resources :keys
+      resources :keys, only: [:index, :show, :new, :create, :destroy]
       resources :emails, only: [:index, :create, :destroy]
       resource :avatar, only: [:destroy]
 
@@ -628,13 +626,17 @@ Rails.application.routes.draw do
 
         get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
 
-        resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
+        # Don't use format parameter as file extension (old 3.0.x behavior)
+        # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
+        scope format: false do
+          resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex }
 
-        resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
-          member do
-            get :commits
-            get :ci
-            get :languages
+          resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do
+            member do
+              get :commits
+              get :ci
+              get :languages
+            end
           end
         end
 
@@ -661,7 +663,7 @@ Rails.application.routes.draw do
           post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
         end
 
-        resource :repository, only: [:show, :create] do
+        resource :repository, only: [:create] do
           member do
             get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
           end
@@ -733,13 +735,17 @@ Rails.application.routes.draw do
         resources :triggers, only: [:index, :create, :destroy]
 
         resources :pipelines, only: [:index, :new, :create, :show] do
+          collection do
+            resource :pipelines_settings, path: 'settings', only: [:show, :update]
+          end
+
           member do
             post :cancel
             post :retry
           end
         end
 
-        resources :environments, only: [:index, :show, :new, :create, :destroy]
+        resources :environments
 
         resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
           collection do
@@ -750,6 +756,7 @@ Rails.application.routes.draw do
             get :status
             post :cancel
             post :retry
+            post :play
             post :erase
             get :trace
             get :raw
@@ -778,7 +785,7 @@ Rails.application.routes.draw do
           end
         end
 
-        resources :labels, constraints: { id: /\d+/ } do
+        resources :labels, except: [:show], constraints: { id: /\d+/ } do
           collection do
             post :generate
             post :set_priorities
@@ -803,7 +810,7 @@ Rails.application.routes.draw do
           end
         end
 
-        resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
+        resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
           collection do
             delete :leave
 
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index b463999996747aaeb53e31af3f2b0b21ea2c6422..e3316ecdb6cc5463ed04c6bc0c9cbee3f45a93b1 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do
       'https://github.com/airbnb/javascript.git',
       'https://github.com/tessalt/echo-chamber-js.git',
       'https://github.com/atom/atom.git',
-      'https://github.com/ipselon/react-ui-builder.git',
       'https://github.com/mattermost/platform.git',
       'https://github.com/purifycss/purifycss.git',
       'https://github.com/facebook/nuclide.git',
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index 51ff451eb4c0280570dc6657b4a21bffd84bc6fe..124704cb45157574822bef247b469bbbb72c6235 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,13 +1,34 @@
 class Gitlab::Seeder::Builds
+  STAGES = %w[build notify_build test notify_test deploy notify_deploy]
+  
   def initialize(project)
     @project = project
   end
 
   def seed!
-    ci_commits.each do |ci_commit|
+    pipelines.each do |pipeline|
       begin
-        build_create!(ci_commit, name: 'test build 1')
-        build_create!(ci_commit, status: 'success', name: 'test build 2')
+        build_create!(pipeline, name: 'build:linux', stage: 'build')
+        build_create!(pipeline, name: 'build:osx', stage: 'build')
+
+        build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
+
+        build_create!(pipeline, name: 'rspec:linux', stage: 'test')
+        build_create!(pipeline, name: 'rspec:windows', stage: 'test')
+        build_create!(pipeline, name: 'rspec:windows', stage: 'test')
+        build_create!(pipeline, name: 'rspec:osx', stage: 'test')
+        build_create!(pipeline, name: 'spinach:linux', stage: 'test')
+        build_create!(pipeline, name: 'spinach:osx', stage: 'test')
+        build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
+        build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
+
+        build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
+
+        build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
+        build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
+
+        commit_status_create!(pipeline, name: 'jenkins')
+
         print '.'
       rescue ActiveRecord::RecordInvalid
         print 'F'
@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
     end
   end
 
-  def ci_commits
-    commits = @project.repository.commits('master', nil, 5)
+  def pipelines
+    commits = @project.repository.commits('master', limit: 5)
     commits_sha = commits.map { |commit| commit.raw.id }
     commits_sha.map do |sha|
       @project.ensure_pipeline(sha, 'master')
@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
     []
   end
 
-  def build_create!(ci_commit, opts = {})
-    attributes = build_attributes_for(ci_commit).merge(opts)
+  def build_create!(pipeline, opts = {})
+    attributes = build_attributes_for(pipeline, opts)
     build = Ci::Build.new(attributes)
 
-    if %w(success failed).include?(build.status)
+    if opts[:name].start_with?('build')
       artifacts_cache_file(artifacts_archive_path) do |file|
         build.artifacts_file = file
       end
@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
     end
 
     build.save!
+    build.update(status: build_status)
 
     if %w(running success failed).include?(build.status)
       # We need to set build trace after saving a build (id required)
       build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
     end
   end
+  
+  def commit_status_create!(pipeline, opts = {})
+    attributes = commit_status_attributes_for(pipeline, opts)
+    GenericCommitStatus.create(attributes)
+  end
+  
+  def commit_status_attributes_for(pipeline, opts)
+    { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
+      ref: 'master', user: build_user, project: @project, pipeline: pipeline,
+      created_at: Time.now, updated_at: Time.now
+    }.merge(opts)
+  end
 
-  def build_attributes_for(ci_commit)
-    { name: 'test build', commands: "$ build command",
-      stage: 'test', stage_idx: 1, ref: 'master',
-      user_id: build_user, gl_project_id: @project.id,
-      status: build_status, commit_id: ci_commit.id,
-      created_at: Time.now, updated_at: Time.now }
+  def build_attributes_for(pipeline, opts)
+    commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
   end
 
   def build_user
@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
     Ci::Build::AVAILABLE_STATUSES.sample
   end
 
+  def stage_index(stage)
+    STAGES.index(stage) || 0
+  end
+
   def artifacts_archive_path
     Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
   end
 
   def artifacts_metadata_path
     Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
-
   end
 
   def artifacts_cache_file(file_path)
diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb
new file mode 100644
index 0000000000000000000000000000000000000000..103c7f9445c08f9cfa436e2f8e092095cc6c413f
--- /dev/null
+++ b/db/fixtures/development/16_protected_branches.rb
@@ -0,0 +1,12 @@
+Gitlab::Seeder.quiet do
+  admin_user = User.find(1)
+
+  Project.all.each do |project|
+    params = {
+      name: 'master'
+    }
+
+    ProtectedBranches::CreateService.new(project, admin_user, params).execute
+    print '.'
+  end
+end
diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
index ac7eac0ea7c64589f0d6302eb263244567a86b97..611767ac7fe2497457df2e7fa629b1c4c3f905bd 100644
--- a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
+++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
@@ -7,7 +7,13 @@ class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
   class ProjectImportDataFake
     extend AttrEncrypted
     attr_accessor :credentials
-    attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
+    attr_encrypted :credentials,
+                   key: Gitlab::Application.secrets.db_key_base,
+                   marshal: true,
+                   encode: true,
+                   :mode => :per_attribute_iv_and_salt,
+                   insecure_mode: true,
+                   algorithm: 'aes-256-cbc'
   end
 
   def up
diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f27295524e1b12cee8b96fd5c5c0c6650a405704
--- /dev/null
+++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddProtectedBranchesPushAccess < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :protected_branch_push_access_levels do |t|
+      t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false
+
+      # Gitlab::Access::MASTER == 40
+      t.integer :access_level, default: 40, null: false
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32adfa266cd7c252583f536d5235ea249e818a8b
--- /dev/null
+++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddProtectedBranchesMergeAccess < ActiveRecord::Migration
+  DOWNTIME = false
+
+  def change
+    create_table :protected_branch_merge_access_levels do |t|
+      t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false
+
+      # Gitlab::Access::MASTER == 40
+      t.integer :access_level, default: 40, null: false
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fa93936ced7637deae62ac332de08757dadd4bc7
--- /dev/null
+++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::Migration
+  DOWNTIME = true
+  DOWNTIME_REASON = <<-HEREDOC
+    We're creating a `merge_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this
+    is running, we might be left with a `protected_branch` _without_ an associated `merge_access_level`. The `protected_branches`
+    table must not change while this is running, so downtime is required.
+
+    https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410
+  HEREDOC
+
+  def up
+    execute <<-HEREDOC
+      INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at)
+        SELECT id, (CASE WHEN developers_can_merge THEN 1 ELSE 0 END), now(), now()
+          FROM protected_branches
+    HEREDOC
+  end
+
+  def down
+    execute <<-HEREDOC
+      UPDATE protected_branches SET developers_can_merge = TRUE
+        WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels
+                       WHERE access_level = 1);
+    HEREDOC
+  end
+end
diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..56f6159d1d8e6ce1a63ae22187fa5fb83f6cf26c
--- /dev/null
+++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Migration
+  DOWNTIME = true
+  DOWNTIME_REASON = <<-HEREDOC
+    We're creating a `push_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this
+    is running, we might be left with a `protected_branch` _without_ an associated `push_access_level`. The `protected_branches`
+    table must not change while this is running, so downtime is required.
+
+    https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410
+  HEREDOC
+
+  def up
+    execute <<-HEREDOC
+      INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at)
+        SELECT id, (CASE WHEN developers_can_push THEN 1 ELSE 0 END), now(), now()
+          FROM protected_branches
+    HEREDOC
+  end
+
+  def down
+    execute <<-HEREDOC
+      UPDATE protected_branches SET developers_can_push = TRUE
+        WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels
+                       WHERE access_level = 1);
+    HEREDOC
+  end
+end
diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f563f660ddf301069ba103662892cdcc7bc8d8b0
--- /dev/null
+++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # This is only required for `#down`
+  disable_ddl_transaction!
+
+  DOWNTIME = false
+
+  def up
+    remove_column :protected_branches, :developers_can_push, :boolean
+  end
+
+  def down
+    add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, null: false)
+  end
+end
diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aa71e06d36e846abdefbbdcd6ad73fcc1ddb2135
--- /dev/null
+++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # This is only required for `#down`
+  disable_ddl_transaction!
+
+  DOWNTIME = false
+
+  def up
+    remove_column :protected_branches, :developers_can_merge, :boolean
+  end
+
+  def down
+    add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, null: false)
+  end
+end
diff --git a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ecdd1bd7e5e4b3f053c7f9c4a8a9a2a00dc06442
--- /dev/null
+++ b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
@@ -0,0 +1,22 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddDomainBlacklistToApplicationSettings < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    add_column :application_settings, :domain_blacklist_enabled, :boolean, default: false
+    add_column :application_settings, :domain_blacklist, :text
+  end
+end
diff --git a/db/migrate/20160715154212_add_request_access_enabled_to_projects.rb b/db/migrate/20160715154212_add_request_access_enabled_to_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf0131c6d76379dcc71131721c7f5a283ccf0f08
--- /dev/null
+++ b/db/migrate/20160715154212_add_request_access_enabled_to_projects.rb
@@ -0,0 +1,12 @@
+class AddRequestAccessEnabledToProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default :projects, :request_access_enabled, :boolean, default: true
+  end
+
+  def down
+    remove_column :projects, :request_access_enabled
+  end
+end
diff --git a/db/migrate/20160715204316_add_request_access_enabled_to_groups.rb b/db/migrate/20160715204316_add_request_access_enabled_to_groups.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e7b14cd3ee29b4a33e6a6f9c758d80ad67e876b0
--- /dev/null
+++ b/db/migrate/20160715204316_add_request_access_enabled_to_groups.rb
@@ -0,0 +1,12 @@
+class AddRequestAccessEnabledToGroups < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+  disable_ddl_transaction!
+
+  def up
+    add_column_with_default :namespaces, :request_access_enabled, :boolean, default: true
+  end
+
+  def down
+    remove_column :namespaces, :request_access_enabled
+  end
+end
diff --git a/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb b/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dd15704800a58f7467a4a04eb92b522ed3afd251
--- /dev/null
+++ b/db/migrate/20160715230841_rename_application_settings_restricted_signup_domains.rb
@@ -0,0 +1,21 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameApplicationSettingsRestrictedSignupDomains < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # When using the methods "add_concurrent_index" or "add_column_with_default"
+  # you must disable the use of transactions as these methods can not run in an
+  # existing transaction. When using "add_concurrent_index" make sure that this
+  # method is the _only_ method called in the migration, any other changes
+  # should go in a separate migration. This ensures that upon failure _only_ the
+  # index creation fails and can be retried or reverted easily.
+  #
+  # To disable transactions uncomment the following line and remove these
+  # comments:
+  # disable_ddl_transaction!
+
+  def change
+    rename_column :application_settings, :restricted_signup_domains, :domain_whitelist
+  end
+end
diff --git a/db/migrate/20160718153603_add_has_external_wiki_to_projects.rb b/db/migrate/20160718153603_add_has_external_wiki_to_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..55a3e954292d89f734f0a4b2900f248bb176f754
--- /dev/null
+++ b/db/migrate/20160718153603_add_has_external_wiki_to_projects.rb
@@ -0,0 +1,7 @@
+class AddHasExternalWikiToProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  def change
+    add_column :projects, :has_external_wiki, :boolean
+  end
+end
diff --git a/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1eb99feb40c9ef3d07ba11f69cfe16f384d653fe
--- /dev/null
+++ b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
@@ -0,0 +1,15 @@
+class DropAndReaddHasExternalWikiInProjects < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  def up
+    update_column_in_batches(:projects, :has_external_wiki, nil) do |table, query|
+      query.where(table[:has_external_wiki].not_eq(nil))
+    end
+  end
+
+  def down
+  end
+end
diff --git a/db/migrate/20160722221922_nullify_blank_type_on_notes.rb b/db/migrate/20160722221922_nullify_blank_type_on_notes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c4b78e8e15cf57161b77cb1486c1720f516ffbe9
--- /dev/null
+++ b/db/migrate/20160722221922_nullify_blank_type_on_notes.rb
@@ -0,0 +1,9 @@
+class NullifyBlankTypeOnNotes < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def up
+    execute "UPDATE notes SET type = NULL WHERE type = ''"
+  end
+end
diff --git a/db/migrate/20160725083350_add_external_url_to_enviroments.rb b/db/migrate/20160725083350_add_external_url_to_enviroments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..21a8abd310b21fe66cf2367a867e2739ce16f89b
--- /dev/null
+++ b/db/migrate/20160725083350_add_external_url_to_enviroments.rb
@@ -0,0 +1,9 @@
+class AddExternalUrlToEnviroments < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  def change
+    add_column(:environments, :external_url, :string)
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8882377f9f4bad57c26aa0cd7fd56c62ed0a6f47..5b35a528e71b7c1ff0fa6028d94fe0391c88c211 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160716115710) do
+ActiveRecord::Schema.define(version: 20160726093600) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -49,7 +49,7 @@ ActiveRecord::Schema.define(version: 20160716115710) do
     t.integer  "max_attachment_size",                   default: 10,          null: false
     t.integer  "default_project_visibility"
     t.integer  "default_snippet_visibility"
-    t.text     "restricted_signup_domains"
+    t.text     "domain_whitelist"
     t.boolean  "user_oauth_applications",               default: true
     t.string   "after_sign_out_path"
     t.integer  "session_expire_delay",                  default: 10080,       null: false
@@ -84,10 +84,12 @@ ActiveRecord::Schema.define(version: 20160716115710) do
     t.string   "health_check_access_token"
     t.boolean  "send_user_confirmation_email",          default: false
     t.integer  "container_registry_token_expire_delay", default: 5
-    t.boolean  "user_default_external",                 default: false,        null: false
+    t.boolean  "user_default_external",                 default: false,       null: false
     t.text     "after_sign_up_text"
     t.string   "repository_storage",                    default: "default"
     t.string   "enabled_git_access_protocol"
+    t.boolean  "domain_blacklist_enabled",              default: false
+    t.text     "domain_blacklist"
   end
 
   create_table "audit_events", force: :cascade do |t|
@@ -425,9 +427,10 @@ ActiveRecord::Schema.define(version: 20160716115710) do
 
   create_table "environments", force: :cascade do |t|
     t.integer  "project_id"
-    t.string   "name",       null: false
+    t.string   "name",         null: false
     t.datetime "created_at"
     t.datetime "updated_at"
+    t.string   "external_url"
   end
 
   add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
@@ -605,9 +608,9 @@ ActiveRecord::Schema.define(version: 20160716115710) do
   add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
 
   create_table "merge_requests", force: :cascade do |t|
-    t.string   "target_branch",                             null: false
-    t.string   "source_branch",                             null: false
-    t.integer  "source_project_id",                         null: false
+    t.string   "target_branch",                                null: false
+    t.string   "source_branch",                                null: false
+    t.integer  "source_project_id",                            null: false
     t.integer  "author_id"
     t.integer  "assignee_id"
     t.string   "title"
@@ -616,20 +619,21 @@ ActiveRecord::Schema.define(version: 20160716115710) do
     t.integer  "milestone_id"
     t.string   "state"
     t.string   "merge_status"
-    t.integer  "target_project_id",                         null: false
+    t.integer  "target_project_id",                            null: false
     t.integer  "iid"
     t.text     "description"
-    t.integer  "position",                  default: 0
+    t.integer  "position",                     default: 0
     t.datetime "locked_at"
     t.integer  "updated_by_id"
     t.string   "merge_error"
     t.text     "merge_params"
-    t.boolean  "merge_when_build_succeeds", default: false, null: false
+    t.boolean  "merge_when_build_succeeds",    default: false, null: false
     t.integer  "merge_user_id"
     t.string   "merge_commit_sha"
     t.datetime "deleted_at"
     t.string   "in_progress_merge_commit_sha"
   end
+
   add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
   add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
   add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
@@ -664,16 +668,17 @@ ActiveRecord::Schema.define(version: 20160716115710) do
   add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
 
   create_table "namespaces", force: :cascade do |t|
-    t.string   "name",                                  null: false
-    t.string   "path",                                  null: false
+    t.string   "name",                                   null: false
+    t.string   "path",                                   null: false
     t.integer  "owner_id"
     t.datetime "created_at"
     t.datetime "updated_at"
     t.string   "type"
-    t.string   "description",           default: "",    null: false
+    t.string   "description",            default: "",    null: false
     t.string   "avatar"
-    t.boolean  "share_with_group_lock", default: false
-    t.integer  "visibility_level",      default: 20,    null: false
+    t.boolean  "share_with_group_lock",  default: false
+    t.integer  "visibility_level",       default: 20,    null: false
+    t.boolean  "request_access_enabled", default: true,  null: false
   end
 
   add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
@@ -842,6 +847,8 @@ ActiveRecord::Schema.define(version: 20160716115710) do
     t.boolean  "only_allow_merge_if_build_succeeds", default: false,     null: false
     t.boolean  "has_external_issue_tracker"
     t.string   "repository_storage",                 default: "default", null: false
+    t.boolean  "has_external_wiki"
+    t.boolean  "request_access_enabled",             default: true,      null: false
   end
 
   add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -861,13 +868,29 @@ ActiveRecord::Schema.define(version: 20160716115710) do
   add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
   add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
 
+  create_table "protected_branch_merge_access_levels", force: :cascade do |t|
+    t.integer  "protected_branch_id",              null: false
+    t.integer  "access_level",        default: 40, null: false
+    t.datetime "created_at",                       null: false
+    t.datetime "updated_at",                       null: false
+  end
+
+  add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree
+
+  create_table "protected_branch_push_access_levels", force: :cascade do |t|
+    t.integer  "protected_branch_id",              null: false
+    t.integer  "access_level",        default: 40, null: false
+    t.datetime "created_at",                       null: false
+    t.datetime "updated_at",                       null: false
+  end
+
+  add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree
+
   create_table "protected_branches", force: :cascade do |t|
-    t.integer  "project_id",                           null: false
-    t.string   "name",                                 null: false
+    t.integer  "project_id", null: false
+    t.string   "name",       null: false
     t.datetime "created_at"
     t.datetime "updated_at"
-    t.boolean  "developers_can_push",  default: false, null: false
-    t.boolean  "developers_can_merge", default: false, null: false
   end
 
   add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -1130,5 +1153,7 @@ ActiveRecord::Schema.define(version: 20160716115710) do
   add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
 
   add_foreign_key "personal_access_tokens", "users"
+  add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
+  add_foreign_key "protected_branch_push_access_levels", "protected_branches"
   add_foreign_key "u2f_registrations", "users"
 end
diff --git a/doc/README.md b/doc/README.md
index cc0b6e0c1e52d66fef8f7b53e604825e5eff7185..d28ad499d3a9dbed32993c860f99946c6110e488 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -9,7 +9,7 @@
 - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
 - [Importing to GitLab](workflow/importing/README.md).
 - [Importing and exporting projects between instances](user/project/settings/import_export.md).
-- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Markdown](user/markdown.md) GitLab's advanced formatting system.
 - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
 - [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
 - [Profile Settings](profile/README.md)
@@ -21,7 +21,7 @@
 
 ## Administrator documentation
 
-- [Access restrictions](administration/access_restrictions.md) Define which Git access protocols can be used to talk to GitLab
+- [Access restrictions](user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols) Define which Git access protocols can be used to talk to GitLab
 - [Authentication/Authorization](administration/auth/README.md) Configure
   external authentication with LDAP, SAML, CAS and additional Omniauth providers.
 - [Custom Git hooks](administration/custom_hooks.md) Custom Git hooks (on the filesystem) for when webhooks aren't enough.
@@ -50,6 +50,7 @@
 - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
 - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
 - [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
+- [Multiple mountpoints for the repositories storage](administration/repository_storages.md) Define multiple repository storage paths to distribute the storage load.
 
 ## Contributor documentation
 
diff --git a/doc/administration/img/access_restrictions.png b/doc/administration/img/access_restrictions.png
deleted file mode 100644
index 66fd9491e854f7cb4a8ac51f931e9b96575d4e14..0000000000000000000000000000000000000000
Binary files a/doc/administration/img/access_restrictions.png and /dev/null differ
diff --git a/doc/administration/img/repository_storages_admin_ui.png b/doc/administration/img/repository_storages_admin_ui.png
new file mode 100644
index 0000000000000000000000000000000000000000..599350bc098052df2b51da8cb065f246463d8031
Binary files /dev/null and b/doc/administration/img/repository_storages_admin_ui.png differ
diff --git a/doc/administration/img/restricted_url.png b/doc/administration/img/restricted_url.png
deleted file mode 100644
index 0a677433dcf097c5b026415a09b8951dd7bcc431..0000000000000000000000000000000000000000
Binary files a/doc/administration/img/restricted_url.png and /dev/null differ
diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md
index 81bfe173151eb183a42993e3eb6fefb59c6533c1..55b054fc1a440039384825e5698bccce1ea25743 100644
--- a/doc/administration/repository_storages.md
+++ b/doc/administration/repository_storages.md
@@ -1,18 +1,99 @@
 # Repository storages
 
-GitLab allows you to define repository storage paths to enable distribution of
-storage load between several mount points.
-
-## For installations from source
+> [Introduced][ce-4578] in GitLab 8.10.
 
-Add your repository storage paths in your `gitlab.yml` under repositories -> storages, using key -> value pairs.
+GitLab allows you to define multiple repository storage paths to distribute the
+storage load between several mount points.
 
 >**Notes:**
+>
 - You must have at least one storage path called `default`.
-- In order for backups to work correctly the storage path must **not** be a
+- The paths are defined in key-value pairs. The key is an arbitrary name you
+  can pick to name the file path.
+- The target directories and any of its subpaths must not be a symlink.
+
+## Configure GitLab
+
+>**Warning:**
+In order for [backups] to work correctly, the storage path must **not** be a
 mount point and the GitLab user should have correct permissions for the parent
-directory of the path.
+directory of the path. In Omnibus GitLab this is taken care of automatically,
+but for source installations you should be extra careful.
+>
+The thing is that for compatibility reasons `gitlab.yml` has a different
+structure than Omnibus. In `gitlab.yml` you indicate the path for the
+repositories, for example `/home/git/repositories`, while in Omnibus you
+indicate `git_data_dirs`, which for the example above would be `/home/git`.
+Then, Omnibus will create a `repositories` directory under that path to use with
+`gitlab.yml`.
+>
+This little detail matters because while restoring a backup, the current
+contents of  `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`,
+so if `/home/git/repositories` is the mount point, then `mv` would be moving
+things between mount points, and bad things could happen. Ideally,
+`/home/git` would be the mount point, so then things would be moving within the
+same mount point. This is guaranteed with Omnibus installations (because they
+don't specify the full repository path but the parent path), but not for source
+installations.
+
+---
+
+Now that you've read that big fat warning above, let's edit the configuration
+files and add the full paths of the alternative repository storage paths. In
+the example below, we add two more mountpoints that are named `nfs` and `cephfs`
+respectively.
+
+**For installations from source**
+
+1. Edit `gitlab.yml` and add the storage paths:
+
+    ```yaml
+    repositories:
+      # Paths where repositories can be stored. Give the canonicalized absolute pathname.
+      # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
+      storages: # You must have at least a 'default' storage path.
+        default: /home/git/repositories
+        nfs: /mnt/nfs/repositories
+        cephfs: /mnt/cephfs/repositories
+    ```
+
+1. [Restart GitLab] for the changes to take effect.
+
+>**Note:**
+The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be
+deprecated and replaced by `repositories: storages` in the future, so if you
+are upgrading from a version prior to 8.10, make sure to add the configuration
+as described in the step above. After you make the changes and confirm they are
+working, you can remove the `repos_path` line.
+
+---
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the
+   default one:
+
+    ```ruby
+    git_data_dirs({
+      "default" => "/var/opt/gitlab/git-data",
+      "nfs" => "/mnt/nfs/git-data",
+      "cephfs" => "/mnt/cephfs/git-data"
+    })
+    ```
+
+    Note that Omnibus stores the repositories in a `repositories` subdirectory
+    of the `git-data` directory.
+
+## Choose where new project repositories will be stored
+
+Once you set the multiple storage paths, you can choose where new projects will
+be stored via the **Application Settings** in the Admin area.
 
-## For omnibus installations
+![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png)
 
-Follow the instructions at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/configuration.md#storing-git-data-in-an-alternative-directory
+[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
+[restart gitlab]: restart_gitlab.md#installations-from-source
+[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
+[backups]: ../raketasks/backup_restore.md
+[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56
+[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index b44f8cfd628675fc3ab1cf217c7e0ff5faf54db2..796b3680a755879aa26d5bd88765cfc38c0f0fc3 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -67,9 +67,9 @@ Example Response:
 ]
 ```
 
-### Get single issue note
+### Get single award emoji
 
-Gets a single award emoji
+Gets a single award emoji from an issue or merge request.
 
 ```
 GET /projects/:id/issues/:issue_id/award_emoji/:award_id
diff --git a/doc/api/branches.md b/doc/api/branches.md
index abc4732c395584d2ea88ecf3a4a9ef56238bd64c..dbe8306c66f42b392419a0d74451ce4af8558a2b 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -23,6 +23,8 @@ Example response:
   {
     "name": "master",
     "protected": true,
+    "developers_can_push": false,
+    "developers_can_merge": false,
     "commit": {
       "author_email": "john@example.com",
       "author_name": "John Smith",
@@ -64,6 +66,8 @@ Example response:
 {
   "name": "master",
   "protected": true,
+  "developers_can_push": false,
+  "developers_can_merge": false,
   "commit": {
     "author_email": "john@example.com",
     "author_name": "John Smith",
@@ -91,13 +95,15 @@ PUT /projects/:id/repository/branches/:branch/protect
 ```
 
 ```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
 ```
 
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
 | `id` | integer | yes | The ID of a project |
 | `branch` | string | yes | The name of the branch |
+| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
+| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
 
 Example response:
 
@@ -117,7 +123,9 @@ Example response:
     ]
   },
   "name": "master",
-  "protected": true
+  "protected": true,
+  "developers_can_push": true,
+  "developers_can_merge": true
 }
 ```
 
@@ -158,7 +166,9 @@ Example response:
     ]
   },
   "name": "master",
-  "protected": false
+  "protected": false,
+  "developers_can_push": false,
+  "developers_can_merge": false
 }
 ```
 
@@ -196,7 +206,9 @@ Example response:
     ]
   },
   "name": "newbranch",
-  "protected": false
+  "protected": false,
+  "developers_can_push": false,
+  "developers_can_merge": false
 }
 ```
 
diff --git a/doc/api/builds.md b/doc/api/builds.md
index 2adea11247e7dedeceb8ad1747a1b918d9aebc2f..24d90e22a9b67615f7e1a795842277c384217eda 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -283,6 +283,40 @@ Response:
 
 [ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
 
+## Download the artifacts file
+
+> [Introduced][ce-5347] in GitLab 8.10.
+
+Download the artifacts file from the given reference name and job provided the
+build finished successfully.
+
+```
+GET /projects/:id/builds/artifacts/:ref_name/download?job=name
+```
+
+Parameters
+
+| Attribute   | Type    | Required | Description               |
+|-------------|---------|----------|-------------------------- |
+| `id`        | integer | yes      | The ID of a project       |
+| `ref_name`  | string  | yes      | The ref from a repository |
+| `job`       | string  | yes      | The name of the job       |
+
+Example request:
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/artifacts/master/download?job=test"
+```
+
+Example response:
+
+| Status    | Description                     |
+|-----------|---------------------------------|
+| 200       | Serves the artifacts file       |
+| 404       | Build not found or no artifacts |
+
+[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
+
 ## Get a trace file
 
 Get a trace of a specific build of a project
@@ -409,7 +443,7 @@ POST /projects/:id/builds/:build_id/erase
 
 Parameters
 
-| Attribute   | Type    | required | Description         |
+| Attribute   | Type    | Required | Description         |
 |-------------|---------|----------|---------------------|
 | `id`        | integer | yes      | The ID of a project |
 | `build_id`  | integer | yes      | The ID of a build   |
@@ -459,7 +493,7 @@ POST /projects/:id/builds/:build_id/artifacts/keep
 
 Parameters
 
-| Attribute   | Type    | required | Description         |
+| Attribute   | Type    | Required | Description         |
 |-------------|---------|----------|---------------------|
 | `id`        | integer | yes      | The ID of a project |
 | `build_id`  | integer | yes      | The ID of a build   |
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 57c2e1d9b8710de15587cea2921a5b01a8a1bec4..2960c2ae428db0dd2514de264147697d930a52bb 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -81,6 +81,11 @@ Example response:
   "parent_ids": [
     "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
   ],
+  "stats": {
+    "additions": 15,
+    "deletions": 10,
+    "total": 25
+  },
   "status": "running"
 }
 ```
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index 3ad836f51b5bea85da8b13e70d455dc81f7a5f89..9280f0d68b62ec3b128ae52f27f71ad012f28797 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -24,6 +24,6 @@ With those IDs, add the same deploy key to all:
 ```
 for project_id in 321 456 987; do
     curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
-    --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
+    --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/deploy_keys
 done
 ```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 9da1fe22e615946b20446d2cff64d1ebeca4f4ed..4e620ccc81a5967e760fdf8000d96510f17bb2d6 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -1,11 +1,42 @@
 # Deploy Keys
 
-## List deploy keys
+## List all deploy keys
+
+Get a list of all deploy keys across all projects.
+
+```
+GET /deploy_keys
+```
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/deploy_keys"
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 1,
+    "title": "Public key",
+    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+    "created_at": "2013-10-02T10:12:29Z"
+  },
+  {
+    "id": 3,
+    "title": "Another Public key",
+    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+    "created_at": "2013-10-02T11:12:29Z"
+  }
+]
+```
+
+## List project deploy keys
 
 Get a list of a project's deploy keys.
 
 ```
-GET /projects/:id/keys
+GET /projects/:id/deploy_keys
 ```
 
 | Attribute | Type | Required | Description |
@@ -13,7 +44,7 @@ GET /projects/:id/keys
 | `id` | integer | yes | The ID of the project |
 
 ```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys"
 ```
 
 Example response:
@@ -40,7 +71,7 @@ Example response:
 Get a single key.
 
 ```
-GET /projects/:id/keys/:key_id
+GET /projects/:id/deploy_keys/:key_id
 ```
 
 Parameters:
@@ -51,7 +82,7 @@ Parameters:
 | `key_id`  | integer | yes | The ID of the deploy key |
 
 ```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/11"
 ```
 
 Example response:
@@ -73,7 +104,7 @@ If the deploy key already exists in another project, it will be joined to curren
 project only if original one was is accessible by the same user.
 
 ```
-POST /projects/:id/keys
+POST /projects/:id/deploy_keys
 ```
 
 | Attribute | Type | Required | Description |
@@ -83,7 +114,7 @@ POST /projects/:id/keys
 | `key`   | string  | yes | New deploy key |
 
 ```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
 ```
 
 Example response:
@@ -102,7 +133,7 @@ Example response:
 Delete a deploy key from a project
 
 ```
-DELETE /projects/:id/keys/:key_id
+DELETE /projects/:id/deploy_keys/:key_id
 ```
 
 | Attribute | Type | Required | Description |
@@ -111,7 +142,7 @@ DELETE /projects/:id/keys/:key_id
 | `key_id`  | integer | yes | The ID of the deploy key |
 
 ```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13"
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/13"
 ```
 
 Example response:
diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md
new file mode 100644
index 0000000000000000000000000000000000000000..1e12ded448c09b5c281ede3d84b82bfb0b684310
--- /dev/null
+++ b/doc/api/enviroments.md
@@ -0,0 +1,117 @@
+# Environments
+
+## List environments
+
+Get all environments for a given project.
+
+```
+GET /projects/:id/environments
+```
+
+| Attribute | Type    | Required | Description           |
+| --------- | ------- | -------- | --------------------- |
+| `id`      | integer | yes      | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/environments
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 1,
+    "name": "Env1",
+    "external_url": "https://env1.example.gitlab.com"
+  }
+]
+```
+
+## Create a new environment
+
+Creates a new environment with the given name and external_url.
+
+It returns 201 if the environment was successfully created, 400 for wrong parameters.
+
+```
+POST /projects/:id/environment
+```
+
+| Attribute     | Type    | Required | Description                  |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id`          | integer | yes      | The ID of the project        |
+| `name`        | string  | yes      | The name of the environment  |
+| `external_url` | string  | no     | Place to link to for this environment |
+
+```bash
+curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments"
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "name": "deploy",
+  "external_url": "https://deploy.example.gitlab.com"
+}
+```
+
+## Edit an existing environment
+
+Updates an existing environment's name and/or external_url.
+
+It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned.
+
+```
+PUT /projects/:id/environments/:environments_id
+```
+
+| Attribute       | Type    | Required                          | Description                      |
+| --------------- | ------- | --------------------------------- | -------------------------------  |
+| `id`            | integer | yes                               | The ID of the project            |
+| `environment_id` | integer | yes | The ID of the environment  | The ID of the environment        |
+| `name`          | string  | no                                | The new name of the environment  |
+| `external_url`  | string  | no                                | The new external_url             |
+
+```bash
+curl -X PUT --data "name=staging&external_url=https://staging.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "name": "staging",
+  "external_url": "https://staging.example.gitlab.com"
+}
+```
+
+## Delete an environment
+
+It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist.
+
+```
+DELETE /projects/:id/environments/:environment_id
+```
+
+| Attribute | Type    | Required | Description           |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
+| `environment_id` | integer | yes | The ID of the environment |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+```
+
+Example response:
+
+```json
+{
+  "id": 1,
+  "name": "deploy",
+  "external_url": "https://deploy.example.gitlab.com"
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index a8c3b068d22f1ec43f3c3e1fd20892191a080721..e00882e6d5db225c9f44d5768cb14226639554e9 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -276,6 +276,7 @@ Parameters:
 ```json
 {
   "id": 1,
+  "iid": 1,
   "target_branch": "master",
   "source_branch": "test1",
   "project_id": 3,
@@ -350,6 +351,7 @@ Parameters:
 ```json
 {
   "id": 1,
+  "iid": 1,
   "target_branch": "master",
   "project_id": 3,
   "title": "test1",
@@ -449,6 +451,7 @@ Parameters:
 ```json
 {
   "id": 1,
+  "iid": 1,
   "target_branch": "master",
   "source_branch": "test1",
   "project_id": 3,
@@ -517,6 +520,7 @@ Parameters:
 ```json
 {
   "id": 1,
+  "iid": 1,
   "target_branch": "master",
   "source_branch": "test1",
   "project_id": 3,
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 31902e145f6ef64934cbb7930bf89b6ba422d9e7..7ce89adc98b763ccaeac2e5484959f4b45054f8d 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -35,7 +35,7 @@ Where REDIRECT_URI is the URL in your app where users will be sent after authori
 To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client:
 
 ```
-parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI'
+parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
 RestClient.post 'http://localhost:3000/oauth/token', parameters
 
 # The response will be
diff --git a/doc/api/projects.md b/doc/api/projects.md
index dceee7b4ea77c805a5f190fb342440db539ab834..0ba0bffb4ac38f6b692326a5d8c561631bfaa774 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -850,7 +850,6 @@ Parameters:
 {
   "alt": "dk",
   "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
-  "is_image": true,
   "markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
 }
 ```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index d9b68eaeadfd165b571594a5838d57f52f0a7c9b..ea39b32561c38048a89d6857e2c6ac654b7d68d7 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -33,7 +33,9 @@ Example response:
    "session_expire_delay" : 10080,
    "home_page_url" : null,
    "default_snippet_visibility" : 0,
-   "restricted_signup_domains" : [],
+   "domain_whitelist" : [],
+   "domain_blacklist_enabled" : false,
+   "domain_blacklist" : [],
    "created_at" : "2016-01-04T15:44:55.176Z",
    "default_project_visibility" : 0,
    "gravatar_enabled" : true,
@@ -63,7 +65,9 @@ PUT /application/settings
 | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
 | `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
 | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
-| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
+| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
+| `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` |
+| `domain_blacklist` | array of strings | yes (if `domain_whitelist_enabled` is `true` | People trying to sign-up with emails from this domain will not be allowed to do so. |
 | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
 | `after_sign_out_path` | string | no | Where to redirect users after logout |
 | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
@@ -93,7 +97,9 @@ Example response:
   "session_expire_delay": 10080,
   "default_project_visibility": 1,
   "default_snippet_visibility": 0,
-  "restricted_signup_domains": [],
+  "domain_whitelist": [],
+  "domain_blacklist_enabled" : false,
+  "domain_blacklist" : [],
   "user_oauth_applications": true,
   "after_sign_out_path": "",
   "container_registry_token_expire_delay": 5,
diff --git a/doc/api/todos.md b/doc/api/todos.md
index 23f6e35f2a4879111ae45a686b2f13446b6006bd..937c71de386db95859085bd95d42fbfdefdf3c3b 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -277,8 +277,7 @@ Example Response:
 
 ## Mark all todos as done
 
-Marks all pending todos for the current user as done. All todos marked as done
-are returned in the response.
+Marks all pending todos for the current user as done. It returns the number of marked todos.
 
 ```
 DELETE /todos
@@ -291,154 +290,7 @@ curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.c
 Example Response:
 
 ```json
-[
-  {
-    "id": 102,
-    "project": {
-      "id": 2,
-      "name": "Gitlab Ce",
-      "name_with_namespace": "Gitlab Org / Gitlab Ce",
-      "path": "gitlab-ce",
-      "path_with_namespace": "gitlab-org/gitlab-ce"
-    },
-    "author": {
-      "name": "Administrator",
-      "username": "root",
-      "id": 1,
-      "state": "active",
-      "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/root"
-    },
-    "action_name": "marked",
-    "target_type": "MergeRequest",
-    "target": {
-      "id": 34,
-      "iid": 7,
-      "project_id": 2,
-      "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
-      "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
-      "state": "opened",
-      "created_at": "2016-06-17T07:49:24.419Z",
-      "updated_at": "2016-06-17T07:52:43.484Z",
-      "target_branch": "tutorials_git_tricks",
-      "source_branch": "DNSBL_docs",
-      "upvotes": 0,
-      "downvotes": 0,
-      "author": {
-        "name": "Maxie Medhurst",
-        "username": "craig_rutherford",
-        "id": 12,
-        "state": "active",
-        "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/craig_rutherford"
-      },
-      "assignee": {
-        "name": "Administrator",
-        "username": "root",
-        "id": 1,
-        "state": "active",
-        "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/root"
-      },
-      "source_project_id": 2,
-      "target_project_id": 2,
-      "labels": [],
-      "work_in_progress": false,
-      "milestone": {
-        "id": 32,
-        "iid": 2,
-        "project_id": 2,
-        "title": "v1.0",
-        "description": "Assumenda placeat ea voluptatem voluptate qui.",
-        "state": "active",
-        "created_at": "2016-06-17T07:47:34.163Z",
-        "updated_at": "2016-06-17T07:47:34.163Z",
-        "due_date": null
-      },
-      "merge_when_build_succeeds": false,
-      "merge_status": "cannot_be_merged",
-      "subscribed": true,
-      "user_notes_count": 7
-    },
-    "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
-    "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
-    "state": "done",
-    "created_at": "2016-06-17T07:52:35.225Z"
-  },
-  {
-    "id": 98,
-    "project": {
-      "id": 2,
-      "name": "Gitlab Ce",
-      "name_with_namespace": "Gitlab Org / Gitlab Ce",
-      "path": "gitlab-ce",
-      "path_with_namespace": "gitlab-org/gitlab-ce"
-    },
-    "author": {
-      "name": "Maxie Medhurst",
-      "username": "craig_rutherford",
-      "id": 12,
-      "state": "active",
-      "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/u/craig_rutherford"
-    },
-    "action_name": "assigned",
-    "target_type": "MergeRequest",
-    "target": {
-      "id": 34,
-      "iid": 7,
-      "project_id": 2,
-      "title": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
-      "description": "Et ea et omnis illum cupiditate. Dolor aspernatur tenetur ducimus facilis est nihil. Quo esse cupiditate molestiae illo corrupti qui quidem dolor.",
-      "state": "opened",
-      "created_at": "2016-06-17T07:49:24.419Z",
-      "updated_at": "2016-06-17T07:52:43.484Z",
-      "target_branch": "tutorials_git_tricks",
-      "source_branch": "DNSBL_docs",
-      "upvotes": 0,
-      "downvotes": 0,
-      "author": {
-        "name": "Maxie Medhurst",
-        "username": "craig_rutherford",
-        "id": 12,
-        "state": "active",
-        "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/craig_rutherford"
-      },
-      "assignee": {
-        "name": "Administrator",
-        "username": "root",
-        "id": 1,
-        "state": "active",
-        "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
-        "web_url": "https://gitlab.example.com/u/root"
-      },
-      "source_project_id": 2,
-      "target_project_id": 2,
-      "labels": [],
-      "work_in_progress": false,
-      "milestone": {
-        "id": 32,
-        "iid": 2,
-        "project_id": 2,
-        "title": "v1.0",
-        "description": "Assumenda placeat ea voluptatem voluptate qui.",
-        "state": "active",
-        "created_at": "2016-06-17T07:47:34.163Z",
-        "updated_at": "2016-06-17T07:47:34.163Z",
-        "due_date": null
-      },
-      "merge_when_build_succeeds": false,
-      "merge_status": "cannot_be_merged",
-      "subscribed": true,
-      "user_notes_count": 7
-    },
-    "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ce/merge_requests/7",
-    "body": "Dolores in voluptatem tenetur praesentium omnis repellendus voluptatem quaerat.",
-    "state": "done",
-    "created_at": "2016-06-17T07:49:24.624Z"
-  },
-]
+3
 ```
 
 [ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 7f83f84645496e3037705d63680c7cac096ed5c4..0f64137a8a9bc1c44abcb4aec24bed9abdaf7e3e 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -38,7 +38,7 @@ GitLab Runner then executes build scripts as the `gitlab-runner` user.
     $ sudo gitlab-ci-multi-runner register -n \
       --url https://gitlab.com/ci \
       --registration-token REGISTRATION_TOKEN \
-      --executor shell
+      --executor shell \
       --description "My Runner"
     ```
 
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 7fa1a478f344d8d12a1108f03639e9f3a0ac9b7e..6a3c416d995121180acb00198a2e8fcb3a267904 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -233,7 +233,7 @@ Awesome! You started using CI in GitLab!
 Visit the [examples README][examples] to see a list of examples using GitLab
 CI with various languages.
 
-[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
+[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner
 [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
 [examples]: ../examples/README.md
 [ci]: https://about.gitlab.com/gitlab-ci/
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 137b080a8f796d68f35f39cb6bff49ecab471924..4a7c21f811de542b1cdfdda2ce149f7bc1da9021 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -18,25 +18,35 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`.
 
 ### Predefined variables (Environment Variables)
 
-| Variable                | Runner | Description |
-|-------------------------|-----|--------|
-| **CI**                  | 0.4 | Mark that build is executed in CI environment |
-| **GITLAB_CI**           | all | Mark that build is executed in GitLab CI environment |
-| **CI_SERVER**           | all | Mark that build is executed in CI environment |
-| **CI_SERVER_NAME**      | all | CI server that is used to coordinate builds |
-| **CI_SERVER_VERSION**   | all | Not yet defined |
-| **CI_SERVER_REVISION**  | all | Not yet defined |
-| **CI_BUILD_REF**        | all | The commit revision for which project is built |
-| **CI_BUILD_TAG**        | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_BUILD_NAME**       | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_STAGE**      | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_REF_NAME**   | all | The branch or tag name for which project is built |
-| **CI_BUILD_ID**         | all | The unique id of the current build that GitLab CI uses internally |
-| **CI_BUILD_REPO**       | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED**  | 0.5 | The flag to indicate that build was [triggered] |
-| **CI_BUILD_TOKEN**      | 1.2 | Token used for authenticating with the GitLab Container Registry |
-| **CI_PROJECT_ID**       | all | The unique id of the current project that GitLab CI uses internally |
-| **CI_PROJECT_DIR**      | all | The full path where the repository is cloned and where the build is ran |
+| Variable                | GitLab | Runner | Description |
+|-------------------------|--------|--------|-------------|
+| **CI**                  | all    | 0.4    | Mark that build is executed in CI environment |
+| **GITLAB_CI**           | all    | all    | Mark that build is executed in GitLab CI environment |
+| **CI_SERVER**           | all    | all    | Mark that build is executed in CI environment |
+| **CI_SERVER_NAME**      | all    | all    | The name of CI server that is used to coordinate builds |
+| **CI_SERVER_VERSION**   | all    | all    | GitLab version that is used to schedule builds |
+| **CI_SERVER_REVISION**  | all    | all    | GitLab revision that is used to schedule builds |
+| **CI_BUILD_ID**         | all    | all    | The unique id of the current build that GitLab CI uses internally |
+| **CI_BUILD_REF**        | all    | all    | The commit revision for which project is built |
+| **CI_BUILD_TAG**        | all    | 0.5    | The commit tag name. Present only when building tags. |
+| **CI_BUILD_NAME**       | all    | 0.5    | The name of the build as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_STAGE**      | all    | 0.5    | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_REF_NAME**   | all    | all    | The branch or tag name for which project is built |
+| **CI_BUILD_REPO**       | all    | all    | The URL to clone the Git repository |
+| **CI_BUILD_TRIGGERED**  | all    | 0.5    | The flag to indicate that build was [triggered] |
+| **CI_BUILD_TOKEN**      | all    | 1.2    | Token used for authenticating with the GitLab Container Registry |
+| **CI_PIPELINE_ID**      | 8.10   | 0.5    | The unique id of the current pipeline that GitLab CI uses internally |
+| **CI_PROJECT_ID**       | all    | all    | The unique id of the current project that GitLab CI uses internally |
+| **CI_PROJECT_NAME**     | 8.10   | 0.5    | The project name that is currently being built |
+| **CI_PROJECT_NAMESPACE**| 8.10   | 0.5    | The project namespace (username or groupname) that is currently being built |
+| **CI_PROJECT_PATH**     | 8.10   | 0.5    | The namespace with project name |
+| **CI_PROJECT_URL**      | 8.10   | 0.5    | The HTTP address to access project |
+| **CI_PROJECT_DIR**      | all    | all    | The full path where the repository is cloned and where the build is run |
+| **CI_REGISTRY**         | 8.10   | 0.5    | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
+| **CI_REGISTRY_IMAGE**   | 8.10   | 0.5    | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project |
+| **CI_RUNNER_ID**        | 8.10   | 0.5    | The unique id of runner being used |
+| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5    | The description of the runner as saved in GitLab |
+| **CI_RUNNER_TAGS**      | 8.10   | 0.5    | The defined runner tags |
 
 **Some of the variables are only available when using runner with at least defined version.**
 
@@ -46,18 +56,28 @@ Example values:
 export CI_BUILD_ID="50"
 export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
 export CI_BUILD_REF_NAME="master"
-export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git"
+export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git"
 export CI_BUILD_TAG="1.0.0"
 export CI_BUILD_NAME="spec:other"
 export CI_BUILD_STAGE="test"
 export CI_BUILD_TRIGGERED="true"
 export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
-export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
+export CI_PIPELINE_ID="1000"
 export CI_PROJECT_ID="34"
+export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
+export CI_PROJECT_NAME="gitlab-ce"
+export CI_PROJECT_NAMESPACE="gitlab-org"
+export CI_PROJECT_PATH="gitlab-org/gitlab-ce"
+export CI_PROJECT_URL="https://gitlab.com/gitlab-org/gitlab-ce"
+export CI_REGISTRY="registry.gitlab.com"
+export CI_REGISTRY_IMAGE="registry.gitlab.com/gitlab-org/gitlab-ce"
+export CI_RUNNER_ID="10"
+export CI_RUNNER_DESCRIPTION="my runner"
+export CI_RUNNER_TAGS="docker, linux"
 export CI_SERVER="yes"
-export CI_SERVER_NAME="GitLab CI"
-export CI_SERVER_REVISION=""
-export CI_SERVER_VERSION=""
+export CI_SERVER_NAME="GitLab"
+export CI_SERVER_REVISION="8.9.0"
+export CI_SERVER_VERSION="70606bf"
 ```
 
 ### YAML-defined variables
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 50fa263f6930da0014f7113b90bfb157f832ff77..01d7108854398bbaf676d2026a587770216d22ea 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -13,34 +13,36 @@ If you want a quick introduction to GitLab CI, follow our
 **Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
 
 - [.gitlab-ci.yml](#gitlab-ci-yml)
-  - [image and services](#image-and-services)
-  - [before_script](#before_script)
-  - [after_script](#after_script)
-  - [stages](#stages)
-  - [types](#types)
-  - [variables](#variables)
-  - [cache](#cache)
-    - [cache:key](#cache-key)
+    - [image and services](#image-and-services)
+    - [before_script](#before_script)
+    - [after_script](#after_script)
+    - [stages](#stages)
+    - [types](#types)
+    - [variables](#variables)
+    - [cache](#cache)
+        - [cache:key](#cache-key)
 - [Jobs](#jobs)
-  - [script](#script)
-  - [stage](#stage)
-  - [only and except](#only-and-except)
-  - [job variables](#job-variables)
-  - [tags](#tags)
-  - [when](#when)
-  - [environment](#environment)
-  - [artifacts](#artifacts)
-    - [artifacts:name](#artifactsname)
-    - [artifacts:when](#artifactswhen)
-    - [artifacts:expire_in](#artifactsexpire_in)
-  - [dependencies](#dependencies)
-  - [before_script and after_script](#before_script-and-after_script)
+    - [script](#script)
+    - [stage](#stage)
+    - [only and except](#only-and-except)
+    - [job variables](#job-variables)
+    - [tags](#tags)
+    - [allow_failure](#allow_failure)
+    - [when](#when)
+        - [Manual actions](#manual-actions)
+    - [environment](#environment)
+    - [artifacts](#artifacts)
+        - [artifacts:name](#artifacts-name)
+        - [artifacts:when](#artifacts-when)
+        - [artifacts:expire_in](#artifacts-expire_in)
+    - [dependencies](#dependencies)
+    - [before_script and after_script](#before_script-and-after_script)
 - [Git Strategy](#git-strategy)
 - [Shallow cloning](#shallow-cloning)
 - [Hidden jobs](#hidden-jobs)
 - [Special YAML features](#special-yaml-features)
-  - [Anchors](#anchors)
-- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ciyml)
+    - [Anchors](#anchors)
+- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
 - [Skipping builds](#skipping-builds)
 - [Examples](#examples)
 
@@ -377,6 +379,8 @@ job:
     - bundle exec rspec
 ```
 
+Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``).
+
 ### stage
 
 `stage` allows to group build into different stages. Builds of the same `stage`
@@ -473,6 +477,39 @@ job:
 The specification above, will make sure that `job` is built by a Runner that
 has both `ruby` AND `postgres` tags defined.
 
+### allow_failure
+
+`allow_failure` is used when you want to allow a build to fail without impacting
+the rest of the CI suite. Failed builds don't contribute to the commit status.
+
+When enabled and the build fails, the pipeline will be successful/green for all
+intents and purposes, but a "CI build passed with warnings" message  will be
+displayed on the merge request or commit or build page. This is to be used by
+builds that are allowed to fail, but where failure indicates some other (manual)
+steps should be taken elsewhere.
+
+In the example below, `job1` and `job2` will run in parallel, but if `job1`
+fails, it will not stop the next stage from running, since it's marked with
+`allow_failure: true`:
+
+```yaml
+job1:
+  stage: test
+  script:
+  - execute_script_that_will_fail
+  allow_failure: true
+
+job2:
+  stage: test
+  script:
+  - execute_script_that_will_succeed
+
+job3:
+  stage: deploy
+  script:
+  - deploy_to_staging
+```
+
 ### when
 
 `when` is used to implement jobs that are run in case of failure or despite the
@@ -485,6 +522,8 @@ failure.
 1. `on_failure` - execute build only when at least one build from prior stages
     fails.
 1. `always` - execute build regardless of the status of builds from prior stages.
+1. `manual` - execute build manually (added in GitLab 8.10). Read about
+    [manual actions](#manual-actions) below.
 
 For example:
 
@@ -516,6 +555,7 @@ deploy_job:
   stage: deploy
   script:
   - make deploy
+  when: manual
 
 cleanup_job:
   stage: cleanup
@@ -526,8 +566,22 @@ cleanup_job:
 
 The above script will:
 
-1. Execute `cleanup_build_job` only when `build_job` fails
-2. Always execute `cleanup_job` as the last step in pipeline.
+1. Execute `cleanup_build_job` only when `build_job` fails.
+2. Always execute `cleanup_job` as the last step in pipeline regardless of
+   success or failure.
+3. Allow you to manually execute `deploy_job` from GitLab's UI.
+
+#### Manual actions
+
+>**Note:**
+Introduced in GitLab 8.10.
+
+Manual actions are a special type of job that are not executed automatically;
+they need to be explicitly started by a user. Manual actions can be started
+from pipeline, build, environment, and deployment views. You can execute the
+same manual action multiple times.
+
+An example usage of manual actions is deployment to production.
 
 ### environment
 
@@ -630,9 +684,10 @@ be available for download in the GitLab UI.
 Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
 
 The `name` directive allows you to define the name of the created artifacts
-archive. That way, you can have a unique name of every archive which could be
+archive. That way, you can have a unique name for every archive which could be
 useful when you'd like to download the archive from GitLab. The `artifacts:name`
 variable can make use of any of the [predefined variables](../variables/README.md).
+The default name is `artifacts`, which becomes `artifacts.zip` when downloaded.
 
 ---
 
@@ -757,12 +812,13 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
 This feature should be used in conjunction with [`artifacts`](#artifacts) and
 allows you to define the artifacts to pass between different builds.
 
-Note that `artifacts` from previous [stages](#stages) are passed by default.
+Note that `artifacts` from all previous [stages](#stages) are passed by default.
 
 To use this feature, define `dependencies` in context of the job and pass
 a list of all previous builds from which the artifacts should be downloaded.
 You can only define builds from stages that are executed before the current one.
 An error will be shown if you define builds from the current stage or next ones.
+Defining an empty array will skip downloading any artifacts for that job.
 
 ---
 
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 1b46543449859937436e3564ee09ab965d66eae5..55077197ff9606a79519139ef45bedbc42480d70 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -1,7 +1,8 @@
 # GitLab Container Registry
 
 > **Note:**
-This feature was [introduced][ce-4040] in GitLab 8.8.
+This feature was [introduced][ce-4040] in GitLab 8.8. Docker Registry manifest
+v1 support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
 
 > **Note:**
 This document is about the user guide. To learn how to enable GitLab Container
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index fac35ec964d9b1b527585ee78a0e0b5155b503f2..3a3597bccaa01924157a618499df1cbe751b33a6 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -244,6 +244,12 @@ In this case:
 Here is a list of must-have items. Use them in the exact order that appears
 on this document. Further explanation is given below.
 
+- Every method must be described using [Grape's DSL](https://github.com/ruby-grape/grape/tree/v0.13.0#describing-methods)
+  (see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb
+  for a good example):
+  - `desc` for the method summary (you can pass it a block for additional details)
+  - `params` for the method params (this acts as description **and** validation
+    of the params)
 - Every method must have the REST API request. For example:
 
     ```
@@ -359,7 +365,7 @@ restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and
 `example.net`, you would do something like this:
 
 ```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domains[]=*.example.com" -d "restricted_signup_domains[]=example.net" https://gitlab.example.com/api/v3/application/settings
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.example.com" -d "domain_whitelist[]=example.net" https://gitlab.example.com/api/v3/application/settings
 ```
 
 [cURL]: http://curl.haxx.se/ "cURL website"
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index e2ca46504e71093dbf7ca7f824b7c6ac19537d8a..b8fab3aaff7e376d5cf20b3d4aa978a033182e45 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -11,7 +11,8 @@ migrations are written carefully, can be applied online and adhere to the style
 Migrations should not require GitLab installations to be taken offline unless
 _absolutely_ necessary. If a migration requires downtime this should be
 clearly mentioned during the review process as well as being documented in the
-monthly release post.
+monthly release post. For more information see the "Downtime Tagging" section
+below.
 
 When writing your migrations, also consider that databases might have stale data
 or inconsistencies and guard for that. Try to make as little assumptions as possible
@@ -20,35 +21,34 @@ about the state of the database.
 Please don't depend on GitLab specific code since it can change in future versions.
 If needed copy-paste GitLab code into the migration to make it forward compatible.
 
-## Comments in the migration
+## Downtime Tagging
 
-Each migration you write needs to have the two following pieces of information
-as comments.
+Every migration must specify if it requires downtime or not, and if it should
+require downtime it must also specify a reason for this. To do so, add the
+following two constants to the migration class' body:
 
-### Online, Offline, errors?
+* `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
+  downtime.
+* `DOWNTIME_REASON`: a String containing the reason for the migration requiring
+  downtime. This constant **must** be set when `DOWNTIME` is set to `true`.
 
-First, you need to provide information on whether the migration can be applied:
+For example:
 
-1. online without errors (works on previous version and new one)
-2. online with errors on old instances after migrating
-3. online with errors on new instances while migrating
-4. offline (needs to happen without app servers to prevent db corruption)
-
-For example: 
-
-```
-# Migration type: online without errors (works on previous version and new one)
+```ruby
 class MyMigration < ActiveRecord::Migration
-...
-```
+  DOWNTIME = true
+  DOWNTIME_REASON = 'This migration requires downtime because ...'
 
-It is always preferable to have a migration run online. If you expect the migration
-to take particularly long (for instance, if it loops through all notes),
-this is valuable information to add.
+  def change
+    ...
+  end
+end
+```
 
-If you don't provide the information it means that a migration is safe to run online.
+It is an error (that is, CI will fail) if the `DOWNTIME` constant is missing
+from a migration class.
 
-### Reversibility
+## Reversibility
 
 Your migration should be reversible. This is very important, as it should
 be possible to downgrade in case of a vulnerability or bugs.
@@ -100,7 +100,7 @@ value of `10` you'd write the following:
 class MyMigration < ActiveRecord::Migration
   include Gitlab::Database::MigrationHelpers
   disable_ddl_transaction!
-  
+
   def up
     add_column_with_default(:projects, :foo, :integer, default: 10)
   end
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 41685c7ee416beb866ae679d1fd2eeebfbe54757..8852dbcb19eab4ca018df70b83a3fe5e207c5acf 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -53,3 +53,8 @@ Generating a sprite file containing all the Emoji can be done by running:
 ```
 bundle exec rake gemojione:sprite
 ```
+
+If new emoji are added, the spritesheet may change size. To compensate for
+such changes, first generate the `emoji.png` spritesheet with the above Rake
+task, then check the dimensions of the new spritesheet and update the
+`SPRITESHEET_WIDTH` and `SPRITESHEET_HEIGHT` constants accordingly.
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 89ce8bcc3e88b217cb18362942f036da66fa9bf2..b61f436c1a4f254c35400ae23754625770e081fb 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -120,3 +120,11 @@ You need to be in the created branch.
 git checkout NAME-OF-BRANCH
 git merge master
 ```
+
+### Merge master branch with created branch
+You need to be in the master branch.
+```
+git checkout master
+git merge NAME-OF-BRANCH
+```
+
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 19d083d580d7b350a754623c0008966f2c86ca2b..af8e31a705bfeb3422945120edde9f121c40b1c3 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -269,9 +269,9 @@ sudo usermod -aG redis git
 ### Clone the Source
 
     # Clone GitLab repository
-    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-10-stable gitlab
+    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab
 
-**Note:** You can change `8-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
 
 ### Configure It
 
@@ -398,7 +398,7 @@ If you are not using Linux you may have to run `gmake` instead of
     cd /home/git
     sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
     cd gitlab-workhorse
-    sudo -u git -H git checkout v0.7.7
+    sudo -u git -H git checkout v0.7.8
     sudo -u git -H make
 
 ### Initialize Database and Activate Advanced Features
diff --git a/doc/integration/README.md b/doc/integration/README.md
index fd330dd7a7deae5c9bd99398697ad2705d34b46d..ddbd570ac6c9ed37b61e303be07adc7c66b26849 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -11,7 +11,6 @@ See the documentation below for details on how to configure these services.
 - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
 - [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
 - [CAS](cas.md) Configure GitLab to sign in using CAS
-- [Slack](slack.md) Integrate with the Slack chat service
 - [OAuth2 provider](oauth_provider.md) OAuth2 application creation
 - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
 - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
index 5cc09bd536de65ac8f6ff2e24d28b088a9609173..c222d21612f602a49021dfa8834b003d7b5d0330 100644
--- a/doc/integration/akismet.md
+++ b/doc/integration/akismet.md
@@ -1,9 +1,14 @@
 # Akismet
 
+> *Note:* Before 8.11 only issues submitted via the API and for non-project
+members were submitted to Akismet.
+
 GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
-GitLab uses Akismet to prevent users who are not members of a project from
-creating spam via the GitLab API. Detected spam will be rejected, and
-an entry in the "Spam Log" section in the Admin page will be created.
+GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues
+created via the WebUI or the API can be submitted to Akismet for review.
+
+Detected spam will be rejected, and an entry in the "Spam Log" section in the
+Admin page will be created.
 
 Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
 adding a user to a project will disable the Akismet check and prevent this
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index f6ba80f46d5d67041ab863815f0ce929a964fdc2..8cd151fbf950531d77f47cb0bddf29c569d1a65b 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -1,41 +1 @@
-# Slack integration
-
-## On Slack
-
-To enable Slack integration you must create an Incoming WebHooks integration on Slack:
-
-1.  [Sign in to Slack](https://slack.com/signin)
-
-1.  Visit [Incoming WebHooks](https://my.slack.com/services/new/incoming-webhook/)
-
-1.  Choose the channel name you want to send notifications to.
-
-1.  Click **Add Incoming WebHooks Integration**
-    - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
-
-1. Copy the **Webhook URL**, we'll need this later for GitLab.
-
-
-## On GitLab
-
-After Slack is ready we need to setup GitLab. Here are the steps to achieve this.
-
-1.  Sign in to GitLab
-
-1.  Pick the repository you want.
-
-1.  Navigate to Settings -> Services -> Slack
-
-1. Pick the triggers you want to activate
-
-1.  Fill in your Slack details
-    - Webhook: Paste the Webhook URL from the step above
-    - Username: Fill this in if you want to change the username of the bot
-    - Channel: Fill this in if you want to change the channel where the messages will be posted
-    - Mark it as active
-    
-1. Save your settings
-
-Have fun :)
-
-*P.S. You can set "branch,pushed,Compare changes" as highlight words on your Slack profile settings, so that you can be aware of new commits when somebody pushes them.*
+This document was moved to [project_services/slack.md](../project_services/slack.md).
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index fb2dd5827540aa7653e6606ea4fbdf842c4a24db..4ac81ab3ee7c5e5b307a7a39508cc974cc87f670 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -1,647 +1 @@
-# Markdown
-
-## Table of Contents
-
-**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
-
-* [Newlines](#newlines)
-* [Multiple underscores in words](#multiple-underscores-in-words)
-* [URL auto-linking](#url-auto-linking)
-* [Multiline Blockquote](#multiline-blockquote)
-* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
-* [Inline Diff](#inline-diff)
-* [Emoji](#emoji)
-* [Special GitLab references](#special-gitlab-references)
-* [Task Lists](#task-lists)
-
-**[Standard Markdown](#standard-markdown)**
-
-* [Headers](#headers)
-* [Emphasis](#emphasis)
-* [Lists](#lists)
-* [Links](#links)
-* [Images](#images)
-* [Blockquotes](#blockquotes)
-* [Inline HTML](#inline-html)
-* [Horizontal Rule](#horizontal-rule)
-* [Line Breaks](#line-breaks)
-* [Tables](#tables)
-
-**[References](#references)**
-
-## GitLab Flavored Markdown (GFM)
-
-_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._
-
-GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
-
-You can use GFM in
-
-- comments
-- issues
-- merge requests
-- milestones
-- wiki pages
-
-You can also use other rich text files in GitLab. You might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
-
-## Newlines
-
-GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
-
-A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
-Line-breaks, or softreturns, are rendered if you end a line with two or more spaces
-
-    Roses are red [followed by two or more spaces]
-    Violets are blue
-
-    Sugar is sweet
-
-Roses are red  
-Violets are blue
-
-Sugar is sweet
-
-## Multiple underscores in words
-
-It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words.
-
-    perform_complicated_task
-    do_this_and_do_that_and_another_thing
-
-perform_complicated_task
-do_this_and_do_that_and_another_thing
-
-## URL auto-linking
-
-GFM will autolink almost any URL you copy and paste into your text.
-
-    * https://www.google.com
-    * https://google.com/
-    * ftp://ftp.us.debian.org/debian/
-    * smb://foo/bar/baz
-    * irc://irc.freenode.net/gitlab
-    * http://localhost:3000
-
-* https://www.google.com
-* https://google.com/
-* ftp://ftp.us.debian.org/debian/
-* smb://foo/bar/baz
-* irc://irc.freenode.net/gitlab
-* http://localhost:3000
-
-## Multiline Blockquote
-
-On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
-GFM supports multiline blockquotes fenced by <code>>>></code>.
-
-```no-highlight
->>>
-If you paste a message from somewhere else
-
-that
-
-spans
-
-multiple lines,
-
-you can quote that without having to manually prepend `>` to every line!
->>>
-```
-
->>>
-If you paste a message from somewhere else
-
-that
-
-spans
-
-multiple lines,
-
-you can quote that without having to manually prepend `>` to every line!
->>>
-
-## Code and Syntax Highlighting
-
-_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
-list of supported languages visit the Rouge website._
-
-Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
-
-```no-highlight
-Inline `code` has `back-ticks around` it.
-```
-
-Inline `code` has `back-ticks around` it.
-
-Example:
-
-    ```javascript
-    var s = "JavaScript syntax highlighting";
-    alert(s);
-    ```
-
-    ```python
-    def function():
-        #indenting works just fine in the fenced code block
-        s = "Python syntax highlighting"
-        print s
-    ```
-
-    ```ruby
-    require 'redcarpet'
-    markdown = Redcarpet.new("Hello World!")
-    puts markdown.to_html
-    ```
-
-    ```
-    No language indicated, so no syntax highlighting.
-    s = "There is no highlighting for this."
-    But let's throw in a <b>tag</b>.
-    ```
-
-becomes:
-
-```javascript
-var s = "JavaScript syntax highlighting";
-alert(s);
-```
-
-```python
-def function():
-    #indenting works just fine in the fenced code block
-    s = "Python syntax highlighting"
-    print s
-```
-
-```ruby
-require 'redcarpet'
-markdown = Redcarpet.new("Hello World!")
-puts markdown.to_html
-```
-
-```
-No language indicated, so no syntax highlighting.
-s = "There is no highlighting for this."
-But let's throw in a <b>tag</b>.
-```
-
-## Inline Diff
-
-With inline diffs tags you can display {+ additions +} or [- deletions -].
-
-The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
-
-However the wrapping tags cannot be mixed as such:
-
-- {+ additions +]
-- [+ additions +}
-- {- deletions -]
-- [- deletions -}
-
-## Emoji
-
-	Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-
-	:zap: You can use emoji anywhere GFM is supported. :v:
-
-	You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-
-	If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-
-	Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-
-Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-
-:zap: You can use emoji anywhere GFM is supported. :v:
-
-You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-
-If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-
-Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-
-## Special GitLab References
-
-GFM recognizes special references.
-
-You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
-
-GFM will turn that reference into a link so you can navigate between them easily.
-
-GFM will recognize the following:
-
-| input                  | references                   |
-|:-----------------------|:---------------------------  |
-| `@user_name`           | specific user                |
-| `@group_name`          | specific group               |
-| `@all`                 | entire team                  |
-| `#123`                 | issue                        |
-| `!123`                 | merge request                |
-| `$123`                 | snippet                      |
-| `~123`                 | label by ID                  |
-| `~bug`                 | one-word label by name       |
-| `~"feature request"`   | multi-word label by name     |
-| `%123`                 | milestone by ID              |
-| `%v1.23`               | one-word milestone by name   |
-| `%"release candidate"` | multi-word milestone by name |
-| `9ba12248`             | specific commit              |
-| `9ba12248...b19a04f5`  | commit range comparison      |
-| `[README](doc/README)` | repository file references   |
-
-GFM also recognizes certain cross-project references:
-
-| input                                   | references              |
-|:----------------------------------------|:------------------------|
-| `namespace/project#123`                 | issue                   |
-| `namespace/project!123`                 | merge request           |
-| `namespace/project%123`                 | milestone               |
-| `namespace/project$123`                 | snippet                 |
-| `namespace/project@9ba12248`            | specific commit         |
-| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
-| `namespace/project~"Some label"`        | issues with given label |
-
-## Task Lists
-
-You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
-
-```no-highlight
-- [x] Completed task
-- [ ] Incomplete task
-    - [ ] Sub-task 1
-    - [x] Sub-task 2
-    - [ ] Sub-task 3
-```
-
-- [x] Completed task
-- [ ] Incomplete task
-    - [ ] Sub-task 1
-    - [x] Sub-task 2
-    - [ ] Sub-task 3
-
-Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
-
-# Standard Markdown
-
-## Headers
-
-```no-highlight
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-Alternatively, for H1 and H2, an underline-ish style:
-
-Alt-H1
-======
-
-Alt-H2
-------
-```
-
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-Alternatively, for H1 and H2, an underline-ish style:
-
-Alt-H1
-======
-
-Alt-H2
-------
-
-### Header IDs and links
-
-All Markdown-rendered headers automatically get IDs, except in comments.
-
-On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
-
-The IDs are generated from the content of the header according to the following rules:
-
-1. All text is converted to lowercase
-1. All non-word text (e.g., punctuation, HTML) is removed
-1. All spaces are converted to hyphens
-1. Two or more hyphens in a row are converted to one
-1. If a header with the same ID has already been generated, a unique
-   incrementing number is appended, starting at 1.
-
-For example:
-
-```
-# This header has spaces in it
-## This header has a :thumbsup: in it
-# This header has Unicode in it: 한글
-## This header has spaces in it
-### This header has spaces in it
-```
-
-Would generate the following link IDs:
-
-1. `this-header-has-spaces-in-it`
-1. `this-header-has-a-in-it`
-1. `this-header-has-unicode-in-it-한글`
-1. `this-header-has-spaces-in-it`
-1. `this-header-has-spaces-in-it-1`
-
-Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
-
-## Emphasis
-
-```no-highlight
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
-
-Strikethrough uses two tildes. ~~Scratch this.~~
-```
-
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
-
-Strikethrough uses two tildes. ~~Scratch this.~~
-
-## Lists
-
-```no-highlight
-1. First ordered list item
-2. Another item
-  * Unordered sub-list.
-1. Actual numbers don't matter, just that it's a number
-  1. Ordered sub-list
-4. And another item.
-
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
-```
-
-1. First ordered list item
-2. Another item
-  * Unordered sub-list.
-1. Actual numbers don't matter, just that it's a number
-  1. Ordered sub-list
-4. And another item.
-
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
-
-If a list item contains multiple paragraphs,
-each subsequent paragraph should be indented with four spaces.
-
-```no-highlight
-1.  First ordered list item
-
-    Second paragraph of first item.
-2.  Another item
-```
-
-1.  First ordered list item
-
-    Second paragraph of first item.
-2.  Another item
-
-If the second paragraph isn't indented with four spaces,
-the second list item will be incorrectly labeled as `1`.
-
-```no-highlight
-1. First ordered list item
-
-   Second paragraph of first item.
-2. Another item
-```
-
-1. First ordered list item
-
-   Second paragraph of first item.
-2. Another item
-
-## Links
-
-There are two ways to create links, inline-style and reference-style.
-
-    [I'm an inline-style link](https://www.google.com)
-
-    [I'm a reference-style link][Arbitrary case-insensitive reference text]
-
-    [I'm a relative reference to a repository file](LICENSE)
-
-    [You can use numbers for reference-style link definitions][1]
-
-    Or leave it empty and use the [link text itself][]
-
-    Some text to show that the reference links can follow later.
-
-    [arbitrary case-insensitive reference text]: https://www.mozilla.org
-    [1]: http://slashdot.org
-    [link text itself]: https://www.reddit.com
-
-[I'm an inline-style link](https://www.google.com)
-
-[I'm a reference-style link][Arbitrary case-insensitive reference text]
-
-[I'm a relative reference to a repository file](LICENSE)[^1]
-
-[You can use numbers for reference-style link definitions][1]
-
-Or leave it empty and use the [link text itself][]
-
-Some text to show that the reference links can follow later.
-
-[arbitrary case-insensitive reference text]: https://www.mozilla.org
-[1]: http://slashdot.org
-[link text itself]: https://www.reddit.com
-
-**Note**
-
-Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
-
-`[I'm a reference-style link](style)`
-
-will point the link to `wikis/style` when the link is inside of a wiki markdown file.
-
-## Images
-
-    Here's our logo (hover to see the title text):
-
-    Inline-style:
-    ![alt text](img/logo.png)
-
-    Reference-style:
-    ![alt text1][logo]
-
-    [logo]: img/logo.png
-
-Here's our logo:
-
-Inline-style:
-
-![alt text](img/logo.png)
-
-Reference-style:
-
-![alt text][logo]
-
-[logo]: img/logo.png
-
-## Blockquotes
-
-```no-highlight
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
-
-Quote break.
-
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
-```
-
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
-
-Quote break.
-
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
-
-## Inline HTML
-
-You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
-
-See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes.  In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements.
-
-```no-highlight
-<dl>
-  <dt>Definition list</dt>
-  <dd>Is something people use sometimes.</dd>
-
-  <dt>Markdown in HTML</dt>
-  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
-```
-
-<dl>
-  <dt>Definition list</dt>
-  <dd>Is something people use sometimes.</dd>
-
-  <dt>Markdown in HTML</dt>
-  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
-
-## Horizontal Rule
-
-```
-Three or more...
-
----
-
-Hyphens
-
-***
-
-Asterisks
-
-___
-
-Underscores
-```
-
-Three or more...
-
----
-
-Hyphens
-
-***
-
-Asterisks
-
-___
-
-Underscores
-
-## Line Breaks
-
-My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
-
-Here are some things to try out:
-
-```
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also a separate paragraph, but...
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-This line is also a separate paragraph, and...  
-This line is on its own line, because the previous line ends with two
-spaces.
-```
-
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also begins a separate paragraph, but...
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-This line is also a separate paragraph, and...  
-This line is on its own line, because the previous line ends with two
-spaces.
-
-## Tables
-
-Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
-
-```
-| header 1 | header 2 |
-| -------- | -------- |
-| cell 1   | cell 2   |
-| cell 3   | cell 4   |
-```
-
-Code above produces next output:
-
-| header 1 | header 2 |
-| -------- | -------- |
-| cell 1   | cell 2   |
-| cell 3   | cell 4   |
-
-**Note**
-
-The row of dashes between the table header and body must have at least three dashes in each column.
-
-By including colons in the header row, you can align the text within that column:
-
-```
-| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
-| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
-| Cell 1       | Cell 2   | Cell 3        | Cell 4       | Cell 5   | Cell 6        |
-| Cell 7       | Cell 8   | Cell 9        | Cell 10      | Cell 11  | Cell 12       |
-```
-
-| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
-| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
-| Cell 1       | Cell 2   | Cell 3        | Cell 4       | Cell 5   | Cell 6        |
-| Cell 7       | Cell 8   | Cell 9        | Cell 10      | Cell 11  | Cell 12       |
-
-## References
-
-- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
-- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
-- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
-
-[rouge]: http://rouge.jneen.net/ "Rouge website"
-[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
-[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
+This document was moved to [user/markdown.md](../user/markdown.md).
diff --git a/doc/project_services/img/slack_configuration.png b/doc/project_services/img/slack_configuration.png
new file mode 100644
index 0000000000000000000000000000000000000000..b8de8a56db7bcd9da6944f76238f9e6012951017
Binary files /dev/null and b/doc/project_services/img/slack_configuration.png differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index e15d5db3253e0860321b46de8fc8ad50cdf80119..4442b7c1742bfa02de32a84811b5237fefee6507 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -45,7 +45,7 @@ further configuration instructions and details. Contributions are welcome.
 | PivotalTracker | Project Management Software (Source Commits Endpoint) |
 | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
 | [Redmine](redmine.md) | Redmine issue tracker |
-| Slack | A team communication tool for the 21st century |
+| [Slack](slack.md) | A team communication tool for the 21st century |
 
 ## Services Templates
 
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
new file mode 100644
index 0000000000000000000000000000000000000000..3cfe77c9f851464350afc323ea9e4773e60b0ce9
--- /dev/null
+++ b/doc/project_services/slack.md
@@ -0,0 +1,50 @@
+# Slack Service
+
+## On Slack
+
+To enable Slack integration you must create an incoming webhook integration on
+Slack:
+
+1. [Sign in to Slack](https://slack.com/signin)
+1. Visit [Incoming WebHooks](https://my.slack.com/services/new/incoming-webhook/)
+1. Choose the channel name you want to send notifications to.
+1. Click **Add Incoming WebHooks Integration**
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
+
+## On GitLab
+
+After you set up Slack, it's time to set up GitLab.
+
+Go to your project's **Settings > Services > Slack** and you will see a
+checkbox with the following events that can be triggered:
+
+- Push
+- Issue
+- Merge request
+- Note
+- Tag push
+- Build
+- Wiki page
+
+Bellow each of these event checkboxes, you will have an input field to insert
+which Slack channel you want to send that event message, with `#general`
+being the default. Enter your preferred channel **without** the hash sign (`#`).
+
+At the end, fill in your Slack details:
+
+| Field | Description |
+| ----- | ----------- |
+| **Webhook**  | The [incoming webhook URL][slackhook] which you have to setup on Slack. |
+| **Username** | Optional username which can be on messages sent to slack. Fill this in if you want to change the username of the bot. |
+| **Notify only broken builds** | If you choose to enable the **Build** event and you want to be only notified about failed builds. |
+
+After you are all done, click **Save changes** for the changes to take effect.
+
+>**Note:**
+You can set "branch,pushed,Compare changes" as highlight words on your Slack
+profile settings, so that you can be aware of new commits when somebody pushes
+them.
+
+![Slack configuration](img/slack_configuration.png)
+
+[slackhook]: https://my.slack.com/services/new/incoming-webhook
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index fa976134341b6f2602ddec92a2cb55d79036a7c2..5fa96736d5914729c895fec9e03764d04a51d99a 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -382,6 +382,13 @@ backups using all your disk space.  To do this add the following lines to
 gitlab_rails['backup_keep_time'] = 604800
 ```
 
+Note that the `backup_keep_time` configuration option only manages local
+files. GitLab does not automatically prune old files stored in a third-party
+object storage (e.g. AWS S3) because the user may not have permission to list
+and delete files. We recommend that you configure the appropriate retention
+policy for your object storage. For example, you can configure [the S3 backup
+policy here as described here](http://stackoverflow.com/questions/37553070/gitlab-omnibus-delete-backup-from-amazon-s3).
+
 NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
 
 ## Alternative backup strategies
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 8fbcbb983e9edfd8f9e8b3d32c2fb0b33e15a7dd..cf891cd90adc5b63ddeeccc3d5a97aa7664e3789 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -2,7 +2,7 @@
 
 ## Remove garbage from filesystem. Important! Data loss!
 
-Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in GitLab database.
+Remove namespaces(dirs) from all repository storage paths if they don't exist in GitLab database.
 
 ```
 # omnibus-gitlab
@@ -12,7 +12,7 @@ sudo gitlab-rake gitlab:cleanup:dirs
 bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production
 ```
 
-Rename repositories from `/home/git/repositories` if they don't exist in GitLab database.
+Rename repositories from all repository storage paths if they don't exist in GitLab database.
 The repositories get a `+orphaned+TIMESTAMP` suffix so that they cannot block new repositories from being created.
 
 ```
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index d9dce2af480ca40ab9d3c1f6b5a8e89087e94acf..315cb56a089b6aa0aff79bf3a3c58e7a6e7aff1f 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -167,3 +167,22 @@ of those assets. Unless you are modifying the JavaScript / CSS code on your
 production machine after installing the package, there should be no reason to redo
 rake assets:precompile on the production machine. If you suspect that assets
 have been corrupted, you should reinstall the omnibus package.
+
+## Tracking Deployments
+
+GitLab provides a Rake task that lets you track deployments in GitLab
+Performance Monitoring. This Rake task simply stores the current GitLab version
+in the GitLab Performance Monitoring database.
+
+For Omnibus-packages:
+
+```
+sudo gitlab-rake gitlab:track_deployment
+```
+
+For installations from source:
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
+```
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
new file mode 100644
index 0000000000000000000000000000000000000000..25343d484bae3cbc31bf214367f57e0c5d6f8be4
--- /dev/null
+++ b/doc/update/8.10-to-8.11.md
@@ -0,0 +1,167 @@
+# From 8.10 to 8.11
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+    sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v3.2.1
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.8
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab-ssl origin/8-11-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab origin/8-11-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]:  https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/config/initializers/smtp_settings.rb.sample#L13?
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+    sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+    sudo service gitlab start
+    sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+    sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.10)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.9 to 8.10](8.9-to-8.10.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index 84065a84e5024b6ddb8f55fbede3e363b5363e8a..a057a423e61b6d83718552f986873f35850585b8 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
 ```bash
 cd /home/git/gitlab-shell
 sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.2.0
+sudo -u git -H git checkout v3.2.1
 ```
 
 ### 5. Update gitlab-workhorse
@@ -58,7 +58,7 @@ GitLab 8.1.
 ```bash
 cd /home/git/gitlab-workhorse
 sudo -u git -H git fetch --all
-sudo -u git -H git checkout v0.7.7
+sudo -u git -H git checkout v0.7.8
 sudo -u git -H make
 ```
 
diff --git a/doc/user/admin_area/settings/img/access_restrictions.png b/doc/user/admin_area/settings/img/access_restrictions.png
new file mode 100644
index 0000000000000000000000000000000000000000..8eea84320d79a08c60ed079eed84db6ce1e9e00a
Binary files /dev/null and b/doc/user/admin_area/settings/img/access_restrictions.png differ
diff --git a/doc/user/admin_area/settings/img/domain_blacklist.png b/doc/user/admin_area/settings/img/domain_blacklist.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd87b73cf9e774b23277f917977761dbdb855520
Binary files /dev/null and b/doc/user/admin_area/settings/img/domain_blacklist.png differ
diff --git a/doc/user/admin_area/settings/img/restricted_url.png b/doc/user/admin_area/settings/img/restricted_url.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b00a18320bd17d3890df200a4557f9bdcfdc2fd
Binary files /dev/null and b/doc/user/admin_area/settings/img/restricted_url.png differ
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
new file mode 100644
index 0000000000000000000000000000000000000000..4b540473a6ec7067dc0f6036d5d49edbf6d68c01
--- /dev/null
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -0,0 +1,22 @@
+# Sign-up restrictions
+
+## Blacklist email domains
+
+> [Introduced][ce-5259] in GitLab 8.10.
+
+With this feature enabled, you can block email addresses of a specific domain
+from creating an account on your GitLab server. This is particularly useful to
+prevent spam. Disposable email addresses are usually used by malicious users to
+create dummy accounts and spam issues.
+
+This feature can be activated via the **Application Settings** in the Admin area,
+and you have the option of entering the list manually, or uploading a file with
+the list.
+
+The blacklist accepts wildcards, so you can use `*.test.com` to block every
+`test.com` subdomain, or `*.io` to block all domains ending in `.io`. Domains
+should be separated by a whitespace, semicolon, comma, or a new line.
+
+![Domain Blacklist](img/domain_blacklist.png)
+
+[ce-5259]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5259
diff --git a/doc/administration/access_restrictions.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
similarity index 86%
rename from doc/administration/access_restrictions.md
rename to doc/user/admin_area/settings/visibility_and_access_controls.md
index 51d7996effd06e67322e9347c9292f12e5de530f..633f16a617ced19552e04c1fe4099c7ff49e9abd 100644
--- a/doc/administration/access_restrictions.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -1,6 +1,8 @@
-# Access Restrictions
+# Visibility and access controls
 
-> **Note:** This feature is only available on versions 8.10 and above.
+## Enabled Git access protocols
+
+> [Introduced][ce-4696] in GitLab 8.10.
 
 With GitLab's Access restrictions you can choose which Git access protocols you
 want your users to use to communicate with GitLab. This feature can be enabled
@@ -15,8 +17,6 @@ to choose between:
 
 ![Settings Overview](img/access_restrictions.png)
 
-## Enabled Protocol
-
 When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give
 your users the option to choose which protocol they would like to use.
 
@@ -35,4 +35,6 @@ not selected.
 > **Note:** Please keep in mind that disabling an access protocol does not actually
   block access to the server itself. The ports used for the protocol, be it SSH or
   HTTP, will still be accessible. What GitLab does is restrict access on the
-  application level.
\ No newline at end of file
+  application level.
+
+[ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
diff --git a/doc/markdown/img/logo.png b/doc/user/img/markdown_logo.png
similarity index 100%
rename from doc/markdown/img/logo.png
rename to doc/user/img/markdown_logo.png
diff --git a/doc/user/img/markdown_video.mp4 b/doc/user/img/markdown_video.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..1fc478842f51e7519866f474a02ad605235bc6a6
Binary files /dev/null and b/doc/user/img/markdown_video.mp4 differ
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
new file mode 100644
index 0000000000000000000000000000000000000000..7fe96e67dbbfd9028b8f6f0693acace82bbd68ed
--- /dev/null
+++ b/doc/user/markdown.md
@@ -0,0 +1,786 @@
+# Markdown
+
+## Table of Contents
+
+**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
+
+* [Newlines](#newlines)
+* [Multiple underscores in words](#multiple-underscores-in-words)
+* [URL auto-linking](#url-auto-linking)
+* [Multiline Blockquote](#multiline-blockquote)
+* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
+* [Inline Diff](#inline-diff)
+* [Emoji](#emoji)
+* [Special GitLab references](#special-gitlab-references)
+* [Task Lists](#task-lists)
+* [Videos](#videos)
+
+**[Standard Markdown](#standard-markdown)**
+
+* [Headers](#headers)
+* [Emphasis](#emphasis)
+* [Lists](#lists)
+* [Links](#links)
+* [Images](#images)
+* [Blockquotes](#blockquotes)
+* [Inline HTML](#inline-html)
+* [Horizontal Rule](#horizontal-rule)
+* [Line Breaks](#line-breaks)
+* [Tables](#tables)
+
+**[Wiki-Specific Markdown](#wiki-specific-markdown)**
+
+* [Wiki - Direct page link](#wiki-direct-page-link)
+* [Wiki - Direct file link](#wiki-direct-file-link)
+* [Wiki - Hierarchical link](#wiki-hierarchical-link)
+* [Wiki - Root link](#wiki-root-link)
+
+**[References](#references)**
+
+## GitLab Flavored Markdown (GFM)
+
+> **Note:**
+Not all of the GitLab-specific extensions to Markdown that are described in
+this document currently work on our documentation website.
+>
+For the best result, we encourage you to check this document out as rendered
+by GitLab: [markdown.md]
+
+_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._
+
+GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
+
+You can use GFM in the following areas:
+
+- comments
+- issues
+- merge requests
+- milestones
+- snippets (the snippet must be named with a `.md` extension)
+- wiki pages
+- markdown documents inside the repository
+
+You can also use other rich text files in GitLab. You might have to install a
+dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+
+## Newlines
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines
+
+GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
+
+A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
+Line-breaks, or softreturns, are rendered if you end a line with two or more spaces:
+
+    Roses are red [followed by two or more spaces]
+    Violets are blue
+
+    Sugar is sweet
+
+Roses are red  
+Violets are blue
+
+Sugar is sweet
+
+## Multiple underscores in words
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words
+
+It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words:
+
+    perform_complicated_task
+
+    do_this_and_do_that_and_another_thing
+
+perform_complicated_task
+
+do_this_and_do_that_and_another_thing
+
+## URL auto-linking
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking
+
+GFM will autolink almost any URL you copy and paste into your text:
+
+    * https://www.google.com
+    * https://google.com/
+    * ftp://ftp.us.debian.org/debian/
+    * smb://foo/bar/baz
+    * irc://irc.freenode.net/gitlab
+    * http://localhost:3000
+
+* https://www.google.com
+* https://google.com/
+* ftp://ftp.us.debian.org/debian/
+* smb://foo/bar/baz
+* irc://irc.freenode.net/gitlab
+* http://localhost:3000
+
+## Multiline Blockquote
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote
+
+On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
+GFM supports multiline blockquotes fenced by <code>>>></code>:
+
+```no-highlight
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+```
+
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+
+## Code and Syntax Highlighting
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting
+
+_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the Rouge website._
+
+Blocks of code are either fenced by lines with three back-ticks <code>```</code>,
+or are indented with four spaces. Only the fenced code blocks support syntax
+highlighting:
+
+```no-highlight
+Inline `code` has `back-ticks around` it.
+```
+
+Inline `code` has `back-ticks around` it.
+
+Example:
+
+    ```javascript
+    var s = "JavaScript syntax highlighting";
+    alert(s);
+    ```
+
+    ```python
+    def function():
+        #indenting works just fine in the fenced code block
+        s = "Python syntax highlighting"
+        print s
+    ```
+
+    ```ruby
+    require 'redcarpet'
+    markdown = Redcarpet.new("Hello World!")
+    puts markdown.to_html
+    ```
+
+    ```
+    No language indicated, so no syntax highlighting.
+    s = "There is no highlighting for this."
+    But let's throw in a <b>tag</b>.
+    ```
+
+becomes:
+
+```javascript
+var s = "JavaScript syntax highlighting";
+alert(s);
+```
+
+```python
+def function():
+    #indenting works just fine in the fenced code block
+    s = "Python syntax highlighting"
+    print s
+```
+
+```ruby
+require 'redcarpet'
+markdown = Redcarpet.new("Hello World!")
+puts markdown.to_html
+```
+
+```
+No language indicated, so no syntax highlighting.
+s = "There is no highlighting for this."
+But let's throw in a <b>tag</b>.
+```
+
+## Inline Diff
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff
+
+With inline diffs tags you can display {+ additions +} or [- deletions -].
+
+The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
+
+However the wrapping tags cannot be mixed as such:
+
+- {+ additions +]
+- [+ additions +}
+- {- deletions -]
+- [- deletions -}
+
+## Emoji
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji
+
+	Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
+
+	:zap: You can use emoji anywhere GFM is supported. :v:
+
+	You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
+
+	If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+
+	Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
+
+Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
+
+:zap: You can use emoji anywhere GFM is supported. :v:
+
+You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
+
+If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+
+Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
+
+## Special GitLab References
+
+GFM recognizes special references.
+
+You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
+
+GFM will turn that reference into a link so you can navigate between them easily.
+
+GFM will recognize the following:
+
+| input                  | references                   |
+|:-----------------------|:---------------------------  |
+| `@user_name`           | specific user                |
+| `@group_name`          | specific group               |
+| `@all`                 | entire team                  |
+| `#123`                 | issue                        |
+| `!123`                 | merge request                |
+| `$123`                 | snippet                      |
+| `~123`                 | label by ID                  |
+| `~bug`                 | one-word label by name       |
+| `~"feature request"`   | multi-word label by name     |
+| `%123`                 | milestone by ID              |
+| `%v1.23`               | one-word milestone by name   |
+| `%"release candidate"` | multi-word milestone by name |
+| `9ba12248`             | specific commit              |
+| `9ba12248...b19a04f5`  | commit range comparison      |
+| `[README](doc/README)` | repository file references   |
+
+GFM also recognizes certain cross-project references:
+
+| input                                   | references              |
+|:----------------------------------------|:------------------------|
+| `namespace/project#123`                 | issue                   |
+| `namespace/project!123`                 | merge request           |
+| `namespace/project%123`                 | milestone               |
+| `namespace/project$123`                 | snippet                 |
+| `namespace/project@9ba12248`            | specific commit         |
+| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
+| `namespace/project~"Some label"`        | issues with given label |
+
+## Task Lists
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists
+
+You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
+
+```no-highlight
+- [x] Completed task
+- [ ] Incomplete task
+    - [ ] Sub-task 1
+    - [x] Sub-task 2
+    - [ ] Sub-task 3
+```
+
+- [x] Completed task
+- [ ] Incomplete task
+    - [ ] Sub-task 1
+    - [x] Sub-task 2
+    - [ ] Sub-task 3
+
+Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
+
+## Videos
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos
+
+Image tags with a video extension are automatically converted to a video player.
+
+The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+
+    Here's a sample video:
+
+    ![Sample Video](img/markdown_video.mp4)
+
+Here's a sample video:
+
+![Sample Video](img/markdown_video.mp4)
+
+# Standard Markdown
+
+## Headers
+
+```no-highlight
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+```
+
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+
+### Header IDs and links
+
+All Markdown-rendered headers automatically get IDs, except in comments.
+
+On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
+
+The IDs are generated from the content of the header according to the following rules:
+
+1. All text is converted to lowercase
+1. All non-word text (e.g., punctuation, HTML) is removed
+1. All spaces are converted to hyphens
+1. Two or more hyphens in a row are converted to one
+1. If a header with the same ID has already been generated, a unique
+   incrementing number is appended, starting at 1.
+
+For example:
+
+```
+# This header has spaces in it
+## This header has a :thumbsup: in it
+# This header has Unicode in it: 한글
+## This header has spaces in it
+### This header has spaces in it
+```
+
+Would generate the following link IDs:
+
+1. `this-header-has-spaces-in-it`
+1. `this-header-has-a-in-it`
+1. `this-header-has-unicode-in-it-한글`
+1. `this-header-has-spaces-in-it`
+1. `this-header-has-spaces-in-it-1`
+
+Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
+
+## Emphasis
+
+```no-highlight
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+```
+
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+
+## Lists
+
+```no-highlight
+1. First ordered list item
+2. Another item
+  * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+  1. Ordered sub-list
+4. And another item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+```
+
+1. First ordered list item
+2. Another item
+  * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+  1. Ordered sub-list
+4. And another item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+
+If a list item contains multiple paragraphs,
+each subsequent paragraph should be indented with four spaces.
+
+```no-highlight
+1.  First ordered list item
+
+    Second paragraph of first item.
+2.  Another item
+```
+
+1.  First ordered list item
+
+    Second paragraph of first item.
+2.  Another item
+
+If the second paragraph isn't indented with four spaces,
+the second list item will be incorrectly labeled as `1`.
+
+```no-highlight
+1. First ordered list item
+
+   Second paragraph of first item.
+2. Another item
+```
+
+1. First ordered list item
+
+   Second paragraph of first item.
+2. Another item
+
+## Links
+
+There are two ways to create links, inline-style and reference-style.
+
+    [I'm an inline-style link](https://www.google.com)
+
+    [I'm a reference-style link][Arbitrary case-insensitive reference text]
+
+    [I'm a relative reference to a repository file](LICENSE)
+
+    [You can use numbers for reference-style link definitions][1]
+
+    Or leave it empty and use the [link text itself][]
+
+    Some text to show that the reference links can follow later.
+
+    [arbitrary case-insensitive reference text]: https://www.mozilla.org
+    [1]: http://slashdot.org
+    [link text itself]: https://www.reddit.com
+
+[I'm an inline-style link](https://www.google.com)
+
+[I'm a reference-style link][Arbitrary case-insensitive reference text]
+
+[I'm a relative reference to a repository file](LICENSE)[^1]
+
+[You can use numbers for reference-style link definitions][1]
+
+Or leave it empty and use the [link text itself][]
+
+Some text to show that the reference links can follow later.
+
+[arbitrary case-insensitive reference text]: https://www.mozilla.org
+[1]: http://slashdot.org
+[link text itself]: https://www.reddit.com
+
+**Note**
+
+Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
+
+`[I'm a reference-style link](style)`
+
+will point the link to `wikis/style` when the link is inside of a wiki markdown file.
+
+## Images
+
+    Here's our logo (hover to see the title text):
+
+    Inline-style:
+    ![alt text](img/markdown_logo.png)
+
+    Reference-style:
+    ![alt text1][logo]
+
+    [logo]: img/markdown_logo.png
+
+Here's our logo:
+
+Inline-style:
+
+![alt text](img/markdown_logo.png)
+
+Reference-style:
+
+![alt text][logo]
+
+[logo]: img/markdown_logo.png
+
+## Blockquotes
+
+```no-highlight
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+```
+
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+
+## Inline HTML
+
+You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+
+See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes.  In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements.
+
+```no-highlight
+<dl>
+  <dt>Definition list</dt>
+  <dd>Is something people use sometimes.</dd>
+
+  <dt>Markdown in HTML</dt>
+  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+```
+
+<dl>
+  <dt>Definition list</dt>
+  <dd>Is something people use sometimes.</dd>
+
+  <dt>Markdown in HTML</dt>
+  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+
+## Horizontal Rule
+
+```
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+```
+
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+
+## Line Breaks
+
+My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
+
+Here are some things to try out:
+
+```
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+```
+
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also begins a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+
+## Tables
+
+Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
+
+```
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1   | cell 2   |
+| cell 3   | cell 4   |
+```
+
+Code above produces next output:
+
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1   | cell 2   |
+| cell 3   | cell 4   |
+
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
+By including colons in the header row, you can align the text within that column:
+
+```
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1       | Cell 2   | Cell 3        | Cell 4       | Cell 5   | Cell 6        |
+| Cell 7       | Cell 8   | Cell 9        | Cell 10      | Cell 11  | Cell 12       |
+```
+
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1       | Cell 2   | Cell 3        | Cell 4       | Cell 5   | Cell 6        |
+| Cell 7       | Cell 8   | Cell 9        | Cell 10      | Cell 11  | Cell 12       |
+
+
+## Wiki-specific Markdown
+
+The following examples show how links inside wikis behave.
+
+### Wiki - Direct page link
+
+A link which just includes the slug for a page will point to that page,
+_at the base level of the wiki_.
+
+This snippet would link to a `documentation` page at the root of your wiki:
+
+```markdown
+[Link to Documentation](documentation)
+```
+
+### Wiki - Direct file link
+
+Links with a file extension point to that file, _relative to the current page_.
+
+If this snippet was placed on a page at `<your_wiki>/documentation/related`,
+it would link to `<your_wiki>/documentation/file.md`:
+
+```markdown
+[Link to File](file.md)
+```
+
+### Wiki - Hierarchical link
+
+A link can be constructed relative to the current wiki page using `./<page>`,
+`../<page>`, etc.
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/main`,
+  it would link to `<your_wiki>/documentation/related`:
+
+	```markdown
+	[Link to Related Page](./related)
+	```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
+  it would link to `<your_wiki>/documentation/main`:
+
+	```markdown
+	[Link to Related Page](../main)
+	```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/main`,
+  it would link to `<your_wiki>/documentation/related.md`:
+
+	```markdown
+	[Link to Related Page](./related.md)
+	```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
+  it would link to `<your_wiki>/documentation/main.md`:
+
+	```markdown
+	[Link to Related Page](../main.md)
+	```
+
+### Wiki - Root link
+
+A link starting with a `/` is relative to the wiki root.
+
+- This snippet links to `<wiki_root>/documentation`:
+
+	```markdown
+	[Link to Related Page](/documentation)
+	```
+
+- This snippet links to `<wiki_root>/miscellaneous.md`:
+
+	```markdown
+	[Link to Related Page](/miscellaneous.md)
+	```
+## References
+
+- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
+- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
+- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+
+[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md
+[rouge]: http://rouge.jneen.net/ "Rouge website"
+[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
+[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
diff --git a/doc/user/project/img/project_settings_list.png b/doc/user/project/img/project_settings_list.png
new file mode 100644
index 0000000000000000000000000000000000000000..57ca2ac5f9e56fceb89864c5074186fec562ded4
Binary files /dev/null and b/doc/user/project/img/project_settings_list.png differ
diff --git a/doc/user/project/img/protected_branches_choose_branch.png b/doc/user/project/img/protected_branches_choose_branch.png
new file mode 100644
index 0000000000000000000000000000000000000000..2632814371735016f89cc653548cda2bc9ef86a1
Binary files /dev/null and b/doc/user/project/img/protected_branches_choose_branch.png differ
diff --git a/doc/user/project/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c33db36586752a18cf9575fcf6c5385c3d2d1bd
Binary files /dev/null and b/doc/user/project/img/protected_branches_devs_can_push.png differ
diff --git a/doc/user/project/img/protected_branches_error_ui.png b/doc/user/project/img/protected_branches_error_ui.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc61df7ca972e00da67293dda0be572c7c853bd0
Binary files /dev/null and b/doc/user/project/img/protected_branches_error_ui.png differ
diff --git a/doc/user/project/img/protected_branches_list.png b/doc/user/project/img/protected_branches_list.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f070f7a208d94b6890227d7801f18c944d5f2e6
Binary files /dev/null and b/doc/user/project/img/protected_branches_list.png differ
diff --git a/doc/user/project/img/protected_branches_matches.png b/doc/user/project/img/protected_branches_matches.png
new file mode 100644
index 0000000000000000000000000000000000000000..30ce53f704e3479445469a15ea4ce70fb7940c90
Binary files /dev/null and b/doc/user/project/img/protected_branches_matches.png differ
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a8170b5ecb9dbd365c0a7ee5b8af1188a1ed067
--- /dev/null
+++ b/doc/user/project/protected_branches.md
@@ -0,0 +1,106 @@
+# Protected Branches
+
+[Permissions](../permissions.md) in GitLab are fundamentally defined around the
+idea of having read or write permission to the repository and branches. To
+prevent people from messing with history or pushing code without review, we've
+created protected branches.
+
+By default, a protected branch does four simple things:
+
+- it prevents its creation, if not already created, from everybody except users
+  with Master permission
+- it prevents pushes from everybody except users with Master permission
+- it prevents **anyone** from force pushing to the branch
+- it prevents **anyone** from deleting the branch
+
+See the [Changelog](#changelog) section for changes over time.
+
+## Configuring protected branches
+
+To protect a branch, you need to have at least Master permission level. Note
+that the `master` branch is protected by default.
+
+1. Navigate to the main page of the project.
+1. In the upper right corner, click the settings wheel and select **Protected branches**.
+
+    ![Project settings list](img/project_settings_list.png)
+
+1. From the **Branch** dropdown menu, select the branch you want to protect and
+   click **Protect**. In the screenshot below, we chose the `develop` branch.
+
+    ![Choose protected branch](img/protected_branches_choose_branch.png)
+
+1. Once done, the protected branch will appear in the "Already protected" list.
+
+    ![Protected branches list](img/protected_branches_list.png)
+
+
+Since GitLab 8.10, we added another layer of branch protection which provides
+more granular management of protected branches. You can now choose the option
+"Developers can merge" so that Developer users can merge a merge request but
+not directly push. In that case, your branches are protected from direct pushes,
+yet Developers don't need elevated permissions or wait for someone with a higher
+permission level to press merge.
+
+You can set this option while creating the protected branch or after its
+creation.
+
+## Wildcard protected branches
+
+>**Note:**
+This feature was [introduced][ce-4665] in GitLab 8.10.
+
+You can specify a wildcard protected branch, which will protect all branches
+matching the wildcard. For example:
+
+| Wildcard Protected Branch | Matching Branches                                      |
+|---------------------------+--------------------------------------------------------|
+| `*-stable`                | `production-stable`, `staging-stable`                  |
+| `production/*`            | `production/app-server`, `production/load-balancer`    |
+| `*gitlab*`                | `gitlab`, `gitlab/staging`, `master/gitlab/production` |
+
+Protected branch settings (like "Developers can push") apply to all matching
+branches.
+
+Two different wildcards can potentially match the same branch. For example,
+`*-stable` and `production-*` would both match a `production-stable` branch.
+In that case, if _any_ of these protected branches have a setting like
+"Allowed to push", then `production-stable` will also inherit this setting.
+
+If you click on a protected branch's name that is created using a wildcard,
+you will be presented with a list of all matching branches:
+
+![Protected branch matches](img/protected_branches_matches.png)
+
+## Restrict the creation of protected branches
+
+Creating a protected branch or a list of protected branches using the wildcard
+feature, not only you are restricting pushes to those branches, but also their
+creation if not already created.
+
+## Error messages when pushing to a protected branch
+
+A user with insufficient permissions will be presented with an error when
+creating or pushing to a branch that's prohibited, either through GitLab's UI:
+
+![Protected branch error GitLab UI](img/protected_branches_error_ui.png)
+
+or using Git from their terminal:
+
+```bash
+remote: GitLab: You are not allowed to push code to protected branches on this project.
+To https://gitlab.example.com/thedude/bowling.git
+ ! [remote rejected] staging-stable -> staging-stable (pre-receive hook declined)
+error: failed to push some refs to 'https://gitlab.example.com/thedude/bowling.git'
+```
+
+## Changelog
+
+**8.10.0**
+
+- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!5081][ce-4665]
+
+---
+
+[ce-4665]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4665 "Allow specifying protected branches using wildcards"
+[ce-5081]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081 "Allow creating protected branches that can't be pushed to"
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index ddb2f7281b1034e376ad8bb35a48a8484e36c3a4..49dec613716562bb6108952fcd3aaaad5d2ab27d 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -12,7 +12,7 @@
 - [Project Features](project_features.md)
 - [Project forking workflow](forking_workflow.md)
 - [Project users](add-user/add-user.md)
-- [Protected branches](protected_branches.md)
+- [Protected branches](../user/project/protected_branches.md)
 - [Sharing a project with a group](share_with_group.md)
 - [Share projects with other groups](share_projects_with_other_groups.md)
 - [Web Editor](web_editor.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index 0537ce0bcd4989ac313098ce14515bb56b505173..e541111d7b33b616be608821e37f4867b7163bc4 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -90,6 +90,9 @@ GitLab account using the same e-mail address the invitation was sent to.
 
 ## Request access to a project
 
+As a project owner you can enable or disable non members to request access to
+your project. Go to the project settings and click on **Allow users to request access**.
+
 As a user, you can request to be a member of a project. Go to the project you'd
 like to be a member of, and click the **Request Access** button on the right
 side of your screen.
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 9b50286b179dd4c4f4d9e31aefea11449b674768..a693cc3d0fda2a5b55e342c550ea9a3a995c2971 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -53,6 +53,9 @@ If necessary, you can increase the access level of an individual user for a spec
 
 ## Requesting access to a group
 
+As a group owner you can enable or disable non members to request access to
+your group. Go to the group settings and click on **Allow users to request access**.
+
 As a user, you can request to be a member of a group. Go to the group you'd
 like to be a member of, and click the **Request Access** button on the right
 side of your screen.
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index 5c1c7b47c8a7023836c7bfd1921288327321f68e..aa48b8f750eb2e38fd9b3dfa2a64eb80b7c4e121 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1,55 +1 @@
-# Protected Branches
-
-Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
-
-To prevent people from messing with history or pushing code without review, we've created protected branches.
-
-A protected branch does three simple things:
-
-* it prevents pushes from everybody except users with Master permission
-* it prevents anyone from force pushing to the branch
-* it prevents anyone from deleting the branch
-
-You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
-
-To protect a branch, user needs to have at least a Master permission level, see [permissions document](../user/permissions.md).
-
-![protected branches page](protected_branches/protected_branches1.png)
-
-Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect.
-
-Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch.
-
-Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request.
-
-However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful.
-
-For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box.
-
-On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
-
-![Developers can push](protected_branches/protected_branches2.png)
-
-## Wildcard Protected Branches
-
->**Note:**
-This feature was added in GitLab 8.10.
-
-1. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example:
-
-    | Wildcard Protected Branch | Matching Branches                                      |
-    |---------------------------+--------------------------------------------------------|
-    | `*-stable`                | `production-stable`, `staging-stable`                  |
-    | `production/*`            | `production/app-server`, `production/load-balancer`    |
-    | `*gitlab*`                | `gitlab`, `gitlab/staging`, `master/gitlab/production` |
-
-1. Protected branch settings (like "Developers Can Push") apply to all matching branches.
-
-1. Two different wildcards can potentially match the same branch. For example, `*-stable` and `production-*` would both match a `production-stable` branch.
-   >**Note:**
-   If _any_ of these protected branches have "Developers Can Push" set to true, then `production-stable` has it set to true.
-
-1. If you click on a protected branch's name, you will be presented with a list of all matching branches:
-
-    ![protected branch matches](protected_branches/protected_branches3.png)
-
+This document is moved to [user/project/protected_branches.md](../user/project/protected_branches.md)
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
deleted file mode 100644
index c00443803de5e8601137ffcf6597622822b43f26..0000000000000000000000000000000000000000
Binary files a/doc/workflow/protected_branches/protected_branches1.png and /dev/null differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
deleted file mode 100644
index a4f664d3b212e340605ce160f014ede14b4ce843..0000000000000000000000000000000000000000
Binary files a/doc/workflow/protected_branches/protected_branches2.png and /dev/null differ
diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png
deleted file mode 100644
index 2a50cb174bb7e8dd72f9f02990fdea674d542678..0000000000000000000000000000000000000000
Binary files a/doc/workflow/protected_branches/protected_branches3.png and /dev/null differ
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index d4811b1ff54d3a9c080067f72a1c99f54e072ec2..63ce3ccb536e4bd4c4dbe4e125b9ff04eecd2df7 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -8,6 +8,12 @@ Feature: Project Wiki
     Given I create the Wiki Home page
     Then I should see the newly created wiki page
 
+  Scenario: Add new page with errors
+    Given I create the Wiki Home page with no content
+    Then I should see a "Content can't be blank" error message
+    When I create the Wiki Home page
+    Then I should see the newly created wiki page
+
   Scenario: Pressing Cancel while editing a brand new Wiki
     Given I click on the Cancel button
     Then I should be redirected back to the Edit Home Wiki page
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 037f7494a77295b9434de023ac948d79fc81a415..03f87df7a60cf02657e813000516fea7dacef500 100644
--- a/features/steps/admin/settings.rb
+++ b/features/steps/admin/settings.rb
@@ -27,19 +27,19 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
 
   step 'I check all events and submit form' do
     page.check('Active')
-    page.check('Push events')
-    page.check('Tag push events')
-    page.check('Comments')
-    page.check('Issues events')
-    page.check('Merge Request events')
-    page.check('Build events')
+    page.check('Push')
+    page.check('Tag push')
+    page.check('Note')
+    page.check('Issue')
+    page.check('Merge request')
+    page.check('Build')
     click_on 'Save'
   end
 
   step 'I fill out Slack settings' do
     fill_in 'Webhook', with: 'http://localhost'
     fill_in 'Username', with: 'test_user'
-    fill_in 'Channel', with: '#test_channel'
+    fill_in 'service_push_channel', with: '#test_channel'
     page.check('Notify only broken builds')
   end
 
@@ -56,6 +56,6 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
   step 'I should see Slack settings saved' do
     expect(find_field('Webhook').value).to eq 'http://localhost'
     expect(find_field('Username').value).to eq 'test_user'
-    expect(find_field('Channel').value).to eq '#test_channel'
+    expect(find('#service_push_channel').value).to eq '#test_channel'
   end
 end
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 0a42931147dc1ee7f78562f97c6fe1172ec1fec1..4bfb7e92e99657f01c22b45170ca1a2ce3a2c185 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
 
   step 'project "Shop" has protected branches' do
     project = Project.find_by(name: "Shop")
-    project.protected_branches.create(name: "stable")
+    create(:protected_branch, project: project, name: "stable")
   end
 
   step 'I click new branch link' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 0fe046dcbf6616393e0e3793baa0d477266ac9da..9a8896acb158a687bb8f6e33ff52e43a881c4258 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -293,7 +293,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
     first('.js-project-refs-dropdown').click
 
     page.within '.project-refs-form' do
-      click_link 'test'
+      click_link "'test'"
     end
   end
 
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 3cbf832c728e8f514dd11558ef93c9d4ba0393c8..732dc5d0b9392901b28577b24a7bc2d93ae87115 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -19,6 +19,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
     click_on "Create page"
   end
 
+  step 'I create the Wiki Home page with no content' do
+    fill_in "wiki_content", with: ''
+    click_on "Create page"
+  end
+
   step 'I should see the newly created wiki page' do
     expect(page).to have_content "Home"
     expect(page).to have_content "link test"
@@ -125,15 +130,15 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
 
   step 'I create a New page with paths' do
     click_on 'New Page'
-    fill_in 'Page slug', with: 'one/two/three'
+    fill_in 'Page slug', with: 'one/two/three-test'
     click_on 'Create Page'
     fill_in "wiki_content", with: 'wiki content'
     click_on "Create page"
-    expect(current_path).to include 'one/two/three'
+    expect(current_path).to include 'one/two/three-test'
   end
 
   step 'I should see non-escaped link in the pages list' do
-    expect(page).to have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']")
+    expect(page).to have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three-test']")
   end
 
   step 'I edit the Wiki page with a path' do
@@ -142,7 +147,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
   end
 
   step 'I should see a non-escaped path' do
-    expect(current_path).to include 'one/two/three'
+    expect(current_path).to include 'one/two/three-test'
   end
 
   step 'I should see the Editing page' do
@@ -173,6 +178,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
     find('a[href*="?version_id"]')
   end
 
+  step 'I should see a "Content can\'t be blank" error message' do
+    expect(page).to have_content('The form contains the following error:')
+    expect(page).to have_content('Content can\'t be blank')
+  end
+
   def wiki
     @project_wiki = ProjectWiki.new(project, current_user)
   end
diff --git a/features/support/env.rb b/features/support/env.rb
index f0a3dd8d2d0c620330a669f1b38724d9981e568d..569fd444e8652858d763562332b03354a27fb10f 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,6 +1,5 @@
-if ENV['SIMPLECOV']
-  require 'simplecov'
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
 
 ENV['RAILS_ENV'] = 'test'
 require './config/environment'
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
index d3831d8045ba50b343901d7452837ef1844ca674..e2f47db0de20bc7bb46faddb2b76a638f0c7c813 100644
--- a/fixtures/emojis/aliases.json
+++ b/fixtures/emojis/aliases.json
@@ -1,16 +1,9 @@
 {
-   "northeast_pointing_airplane":"airplane_northeast",
    "small_airplane":"airplane_small",
-   "up_pointing_small_airplane":"airplane_small_up",
-   "up_pointing_airplane":"airplane_up",
-   "left_anger_bubble":"anger_left",
    "right_anger_bubble":"anger_right",
    "keycap_asterisk":"asterisk",
    "atom_symbol":"atom",
    "ballot_box_with_ballot":"ballot_box",
-   "ballot_box_with_bold_check":"ballot_box_check",
-   "ballot_box_with_script_x":"ballot_box_x",
-   "ballot_script_x":"ballot_x",
    "person_with_ball":"basketball_player",
    "person_with_ball_tone1":"basketball_player_tone1",
    "person_with_ball_tone2":"basketball_player_tone2",
@@ -21,51 +14,65 @@
    "umbrella_on_ground":"beach_umbrella",
    "bellhop_bell":"bellhop",
    "biohazard_sign":"biohazard",
-   "bouquet_of_flowers":"bouquet2",
    "archery":"bow_and_arrow",
-   "bullhorn_with_sound_waves":"bullhorn_waves",
-   "pocket calculator":"calculator",
+   "boxing_gloves":"boxing_glove",
    "spiral_calendar_pad":"calendar_spiral",
+   "call_me_hand":"call_me",
+   "call_me_hand_tone1":"call_me_tone1",
+   "call_me_hand_tone2":"call_me_tone2",
+   "call_me_hand_tone3":"call_me_tone3",
+   "call_me_hand_tone4":"call_me_tone4",
+   "call_me_hand_tone5":"call_me_tone5",
+   "kayak":"canoe",
    "card_file_box":"card_box",
-   "tape_cartridge":"cartridge",
+   "person_doing_cartwheel":"cartwheel",
+   "person_doing_cartwheel_tone1":"cartwheel_tone1",
+   "person_doing_cartwheel_tone2":"cartwheel_tone2",
+   "person_doing_cartwheel_tone3":"cartwheel_tone3",
+   "person_doing_cartwheel_tone4":"cartwheel_tone4",
+   "person_doing_cartwheel_tone5":"cartwheel_tone5",
    "bottle_with_popping_cork":"champagne",
+   "clinking_glass":"champagne_glass",
    "cheese_wedge":"cheese",
    "city_sunrise":"city_sunset",
    "mantlepiece_clock":"clock",
-   "clockwise_right_and_left_semicircle_arrows":"clockwise_arrows",
    "cloud_with_lightning":"cloud_lightning",
    "cloud_with_rain":"cloud_rain",
    "cloud_with_snow":"cloud_snow",
    "cloud_with_tornado":"cloud_tornado",
-   "old_personal_computer":"computer_old",
-   "building_construction":"contruction_site",
+   "clown_face":"clown",
+   "building_construction":"construction_site",
    "couch_and_lamp":"couch",
    "couple_with_heart_mm":"couple_mm",
    "couple_with_heart_ww":"couple_ww",
+   "face_with_cowboy_hat":"cowboy",
    "lower_left_crayon":"crayon",
    "cricket_bat_ball":"cricket",
    "latin_cross":"cross",
-   "heavy_latin_cross":"cross_heavy",
-   "white_latin_cross":"cross_white",
-   "black_skull_and_crossbones":"crossbones",
    "passenger_ship":"cruise_ship",
    "dagger_knife":"dagger",
    "desktop_computer":"desktop",
    "card_index_dividers":"dividers",
-   "document_with_text":"document_text",
    "dove_of_peace":"dove",
+   "drool":"drooling_face",
+   "drum_with_drumsticks":"drum",
    "email":"e-mail",
-   "back_of_envelope":"envelope_back",
-   "flying_envelope":"envelope_flying",
-   "stamped_envelope":"envelope_stamped",
-   "pen_over_stamped_envelope":"envelope_stamped_pen",
-   "white_down_pointing_left_hand_index":"finger_pointing_down",
-   "sideways_white_down_pointing_index":"finger_pointing_down2",
-   "sideways_white_left_pointing_index":"finger_pointing_left",
-   "sideways_white_right_pointing_index":"finger_pointing_right",
-   "sideways_white_up_pointing_index":"finger_pointing_up",
+   "eject_symbol":"eject",
+   "facepalm":"face_palm",
+   "facepalm_tone1":"face_palm_tone1",
+   "facepalm_tone2":"face_palm_tone2",
+   "facepalm_tone3":"face_palm_tone3",
+   "facepalm_tone4":"face_palm_tone4",
+   "facepalm_tone5":"face_palm_tone5",
+   "fencing":"fencer",
+   "hand_with_index_and_middle_finger_crossed":"fingers_crossed",
+   "hand_with_index_and_middle_fingers_crossed_tone1":"fingers_crossed_tone1",
+   "hand_with_index_and_middle_fingers_crossed_tone2":"fingers_crossed_tone2",
+   "hand_with_index_and_middle_fingers_crossed_tone3":"fingers_crossed_tone3",
+   "hand_with_index_and_middle_fingers_crossed_tone4":"fingers_crossed_tone4",
+   "hand_with_index_and_middle_fingers_crossed_tone5":"fingers_crossed_tone5",
    "flame":"fire",
-   "oncoming_fire_engine":"fire_engine_oncoming",
+   "first_place_medal":"first_place",
    "ac":"flag_ac",
    "ad":"flag_ad",
    "ae":"flag_ae",
@@ -326,44 +333,51 @@
    "za":"flag_za",
    "zm":"flag_zm",
    "zw":"flag_zw",
-   "clamshell_mobile_phone":"flip_phone",
-   "black_hard_shell_floppy_disk":"floppy_black",
-   "white_hard_shell_floppy_disk":"floppy_white",
-   "open_folder":"folder_open",
    "fork_and_knife_with_plate":"fork_knife_plate",
+   "fox_face":"fox",
    "frame_with_picture":"frame_photo",
-   "frame_with_tiles":"frame_tiles",
-   "frame_with_an_x":"frame_x",
+   "baguette_bread":"french_bread",
    "anguished":"frowning",
    "white_frowning_face":"frowning2",
+   "goal_net":"goal",
    "hammer_and_pick":"hammer_pick",
    "raised_hand_with_fingers_splayed":"hand_splayed",
-   "reversed_raised_hand_with_fingers_splayed":"hand_splayed_reverse",
    "raised_hand_with_fingers_splayed_tone1":"hand_splayed_tone1",
    "raised_hand_with_fingers_splayed_tone2":"hand_splayed_tone2",
    "raised_hand_with_fingers_splayed_tone3":"hand_splayed_tone3",
    "raised_hand_with_fingers_splayed_tone4":"hand_splayed_tone4",
    "raised_hand_with_fingers_splayed_tone5":"hand_splayed_tone5",
-   "reversed_victory_hand":"hand_victory",
+   "shaking_hands":"handshake",
+   "shaking_hands_tone1":"handshake_tone1",
+   "shaking_hands_tone2":"handshake_tone2",
+   "shaking_hands_tone3":"handshake_tone3",
+   "shaking_hands_tone4":"handshake_tone4",
+   "shaking_hands_tone5":"handshake_tone5",
    "face_with_head_bandage":"head_bandage",
    "heavy_heart_exclamation_mark_ornament":"heart_exclamation",
-   "heart_with_tip_on_the_left":"heart_tip",
    "helmet_with_white_cross":"helmet_with_cross",
    "house_buildings":"homes",
    "hot_dog":"hotdog",
    "derelict_house_building":"house_abandoned",
    "hugging_face":"hugging",
-   "circled_information_source":"info",
    "desert_island":"island",
-   "up_pointing_military_airplane":"jet_up",
+   "juggler":"juggling",
+   "juggler_tone1":"juggling_tone1",
+   "juggler_tone2":"juggling_tone2",
+   "juggler_tone3":"juggling_tone3",
+   "juggler_tone4":"juggling_tone4",
+   "juggler_tone5":"juggling_tone5",
    "old_key":"key2",
-   "wired_keyboard":"keyboard",
-   "keyboard_and_mouse":"keyboard_mouse",
-   "musical_keyboard_with_jacks":"keyboard_with_jacks",
    "couplekiss_mm":"kiss_mm",
    "couplekiss_ww":"kiss_ww",
+   "kiwifruit":"kiwi",
    "satisfied":"laughing",
-   "left_hand_telephone_receiver":"left_receiver",
+   "left_fist":"left_facing_fist",
+   "left_fist_tone1":"left_facing_fist_tone1",
+   "left_fist_tone2":"left_facing_fist_tone2",
+   "left_fist_tone3":"left_facing_fist_tone3",
+   "left_fist_tone4":"left_facing_fist_tone4",
+   "left_fist_tone5":"left_facing_fist_tone5",
    "man_in_business_suit_levitating":"levitate",
    "weight_lifter":"lifter",
    "weight_lifter_tone1":"lifter_tone1",
@@ -371,9 +385,21 @@
    "weight_lifter_tone3":"lifter_tone3",
    "weight_lifter_tone4":"lifter_tone4",
    "weight_lifter_tone5":"lifter_tone5",
-   "light_mark":"light_check_mark",
    "lion":"lion_face",
+   "liar":"lying_face",
+   "male_dancer":"man_dancing",
+   "male_dancer_tone1":"man_dancing_tone1",
+   "male_dancer_tone2":"man_dancing_tone2",
+   "male_dancer_tone3":"man_dancing_tone3",
+   "male_dancer_tone4":"man_dancing_tone4",
+   "male_dancer_tone5":"man_dancing_tone5",
+   "tuxedo_tone1":"man_in_tuxedo_tone1",
+   "tuxedo_tone2":"man_in_tuxedo_tone2",
+   "tuxedo_tone3":"man_in_tuxedo_tone3",
+   "tuxedo_tone4":"man_in_tuxedo_tone4",
+   "tuxedo_tone5":"man_in_tuxedo_tone5",
    "world_map":"map",
+   "karate_uniform":"martial_arts_uniform",
    "sports_medal":"medal",
    "sign_of_the_horns":"metal",
    "sign_of_the_horns_tone1":"metal_tone1",
@@ -388,21 +414,23 @@
    "reversed_hand_with_middle_finger_extended_tone3":"middle_finger_tone3",
    "reversed_hand_with_middle_finger_extended_tone4":"middle_finger_tone4",
    "reversed_hand_with_middle_finger_extended_tone5":"middle_finger_tone5",
+   "glass_of_milk":"milk",
    "money_mouth_face":"money_mouth",
-   "lightning_mood_bubble":"mood_bubble_lightning",
-   "lightning_mood":"mood_lightning",
+   "motorbike":"motor_scooter",
    "racing_motorcycle":"motorcycle",
    "snow_capped_mountain":"mountain_snow",
-   "one_button_mouse":"mouse_one",
    "three_button_mouse":"mouse_three_button",
+   "mother_christmas":"mrs_claus",
+   "mother_christmas_tone1":"mrs_claus_tone1",
+   "mother_christmas_tone2":"mrs_claus_tone2",
+   "mother_christmas_tone3":"mrs_claus_tone3",
+   "mother_christmas_tone4":"mrs_claus_tone4",
+   "mother_christmas_tone5":"mrs_claus_tone5",
+   "sick":"nauseated_face",
    "nerd_face":"nerd",
-   "three_networked_computers":"network",
    "rolled_up_newspaper":"newspaper2",
-   "note_page":"note",
-   "empty_note_page":"note_empty",
-   "note_pad":"notepad",
-   "empty_note_pad":"notepad_empty",
    "spiral_note_pad":"notepad_spiral",
+   "stop_sign":"octagonal_sign",
    "oil_drum":"oil",
    "grandma":"older_woman",
    "grandma_tone1":"older_woman_tone1",
@@ -410,57 +438,66 @@
    "grandma_tone3":"older_woman_tone3",
    "grandma_tone4":"older_woman_tone4",
    "grandma_tone5":"older_woman_tone5",
-   "optical_disc_icon":"optical_disk",
    "lower_left_paintbrush":"paintbrush",
    "linked_paperclips":"paperclips",
    "national_park":"park",
    "double_vertical_bar":"pause_button",
    "peace_symbol":"peace",
+   "shelled_peanut":"peanuts",
    "lower_left_ballpoint_pen":"pen_ballpoint",
    "lower_left_fountain_pen":"pen_fountain",
    "memo":"pencil",
-   "lower_left_pencil":"pencil3",
-   "black_pennant":"pennant_black",
-   "white_pennant":"pennant_white",
    "table_tennis":"ping_pong",
-   "no_piracy":"piracy",
    "worship_symbol":"place_of_worship",
    "shit":"poop",
    "hankey":"poop",
    "poo":"poop",
-   "prohibited_sign":"prohibited",
+   "expecting_woman":"pregnant_woman",
+   "expecting_woman_tone1":"pregnant_woman_tone1",
+   "expecting_woman_tone2":"pregnant_woman_tone2",
+   "expecting_woman_tone3":"pregnant_woman_tone3",
+   "expecting_woman_tone4":"pregnant_woman_tone4",
+   "expecting_woman_tone5":"pregnant_woman_tone5",
    "film_projector":"projector",
    "racing_car":"race_car",
    "radioactive_sign":"radioactive",
    "railroad_track":"railway_track",
-   "right_speaker_with_one_sound_wave":"right_speaker_one",
-   "right_speaker_with_three_sound_waves":"right_speaker_three",
+   "back_of_hand":"raised_back_of_hand",
+   "back_of_hand_tone1":"raised_back_of_hand_tone1",
+   "back_of_hand_tone2":"raised_back_of_hand_tone2",
+   "back_of_hand_tone3":"raised_back_of_hand_tone3",
+   "back_of_hand_tone4":"raised_back_of_hand_tone4",
+   "back_of_hand_tone5":"raised_back_of_hand_tone5",
+   "rhinoceros":"rhino",
+   "right_fist":"right_facing_fist",
+   "right_fist_tone1":"right_facing_fist_tone1",
+   "right_fist_tone2":"right_facing_fist_tone2",
+   "right_fist_tone3":"right_facing_fist_tone3",
+   "right_fist_tone4":"right_facing_fist_tone4",
+   "right_fist_tone5":"right_facing_fist_tone5",
    "robot_face":"robot",
+   "rolling_on_the_floor_laughing":"rofl",
    "face_with_rolling_eyes":"rolling_eyes",
+   "green_salad":"salad",
+   "second_place_medal":"second_place",
+   "paella":"shallow_pan_of_food",
+   "shopping_trolley":"shopping_cart",
    "skeleton":"skull",
    "skull_and_crossbones":"skull_crossbones",
    "slightly_frowning_face":"slight_frown",
    "slightly_smiling_face":"slight_smile",
+   "sneeze":"sneezing_face",
    "speaking_head_in_silhouette":"speaking_head",
-   "left_speech_bubble":"speech_left",
-   "right_speech_bubble":"speech_right",
-   "three_speech_bubbles":"speech_three",
-   "two_speech_bubbles":"speech_two",
    "sleuth_or_spy":"spy",
    "sleuth_or_spy_tone1":"spy_tone1",
    "sleuth_or_spy_tone2":"spy_tone2",
    "sleuth_or_spy_tone3":"spy_tone3",
    "sleuth_or_spy_tone4":"spy_tone4",
    "sleuth_or_spy_tone5":"spy_tone5",
-   "portable_stereo":"stereo",
-   "black_touchtone_telephone":"telephone_black",
-   "white_touchtone_telephone":"telephone_white",
+   "stuffed_pita":"stuffed_flatbread",
    "face_with_thermometer":"thermometer_face",
    "thinking_face":"thinking",
-   "left_thought_bubble":"thought_left",
-   "right_thought_bubble":"thought_right",
-   "reversed_thumbs_down_sign":"thumbs_down_reverse",
-   "reversed_thumbs_up_sign":"thumbs_up_reverse",
+   "third_place_medal":"third_place",
    "-1":"thumbsdown",
    "-1_tone1":"thumbsdown_tone1",
    "-1_tone2":"thumbsdown_tone2",
@@ -479,9 +516,7 @@
    "hammer_and_wrench":"tools",
    "next_track":"track_next",
    "previous_track":"track_previous",
-   "diesel_locomotive":"train_diesel",
-   "triangle_with_rounded_corners":"triangle_round",
-   "turned_ok_hand_sign":"turned_ok_hand",
+   "whisky":"tumbler_glass",
    "unicorn_face":"unicorn",
    "upside_down_face":"upside_down",
    "funeral_urn":"urn",
@@ -494,6 +529,12 @@
    "white_sun_behind_cloud":"white_sun_cloud",
    "white_sun_behind_cloud_with_rain":"white_sun_rain_cloud",
    "white_sun_with_small_cloud":"white_sun_small_cloud",
-   "left_writing_hand":"writing_hand",
+   "wilted_flower":"wilted_rose",
+   "wrestling":"wrestlers",
+   "wrestling_tone1":"wrestlers_tone1",
+   "wrestling_tone2":"wrestlers_tone2",
+   "wrestling_tone3":"wrestlers_tone3",
+   "wrestling_tone4":"wrestlers_tone4",
+   "wrestling_tone5":"wrestlers_tone5",
    "zipper_mouth_face":"zipper_mouth"
 }
diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json
index 50ee5089d8f9934b2782e0469a19cf1b66f5b021..078d3413f338eaefd3c001e84509ddb8bf3123ff 100644
--- a/fixtures/emojis/digests.json
+++ b/fixtures/emojis/digests.json
@@ -59,16 +59,6 @@
     "unicode": "1F6EB",
     "digest": "5544eace06b8e1b6ea91940e893e013d33d6b166e14e6d128a87f2cd2de88332"
   },
-  {
-    "name": "airplane_northeast",
-    "unicode": "1F6EA",
-    "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4"
-  },
-  {
-    "name": "northeast_pointing_airplane",
-    "unicode": "1F6EA",
-    "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4"
-  },
   {
     "name": "airplane_small",
     "unicode": "1F6E9",
@@ -79,26 +69,6 @@
     "unicode": "1F6E9",
     "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d"
   },
-  {
-    "name": "airplane_small_up",
-    "unicode": "1F6E8",
-    "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a"
-  },
-  {
-    "name": "up_pointing_small_airplane",
-    "unicode": "1F6E8",
-    "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a"
-  },
-  {
-    "name": "airplane_up",
-    "unicode": "1F6E7",
-    "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77"
-  },
-  {
-    "name": "up_pointing_airplane",
-    "unicode": "1F6E7",
-    "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77"
-  },
   {
     "name": "alarm_clock",
     "unicode": "23F0",
@@ -164,16 +134,6 @@
     "unicode": "1F4A2",
     "digest": "332493913891aa0eda2743b4bb16c4682400f249998bf34eb292246c9009e17f"
   },
-  {
-    "name": "anger_left",
-    "unicode": "1F5EE",
-    "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc"
-  },
-  {
-    "name": "left_anger_bubble",
-    "unicode": "1F5EE",
-    "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc"
-  },
   {
     "name": "anger_right",
     "unicode": "1F5EF",
@@ -324,11 +284,6 @@
     "unicode": "1F69B",
     "digest": "c115e6613ebd718268aa31d265e017138b9fb58bbb8201eb3f40de2380e460aa"
   },
-  {
-    "name": "ascending_notes",
-    "unicode": "1F39C",
-    "digest": "33432042771d456338dda5d98e49322d3600f2cc9049963480c7c38d9de1ef0a"
-  },
   {
     "name": "asterisk",
     "unicode": "002A-20E3",
@@ -364,6 +319,11 @@
     "unicode": "269B",
     "digest": "6b6bb83b00707a314e46ff8eefbda40978a291ec7881caba1b1ee273f49c1368"
   },
+  {
+    "name": "avocado",
+    "unicode": "1F951",
+    "digest": "bc1fb203d63b18985598400925de24050bb192afda1cbf0813f85cb139869eff"
+  },
   {
     "name": "b",
     "unicode": "1F171",
@@ -419,6 +379,11 @@
     "unicode": "1F519",
     "digest": "083e4e48b51092c28efb4532e840e1091b5d4b685c6e0f221aa0228f061cd91e"
   },
+  {
+    "name": "bacon",
+    "unicode": "1F953",
+    "digest": "18ad3817f1f88a69706db5727a58e763dde6c21a2d4f184c3d728c32dc5fa05a"
+  },
   {
     "name": "badminton",
     "unicode": "1F3F8",
@@ -444,41 +409,11 @@
     "unicode": "1F5F3",
     "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892"
   },
-  {
-    "name": "ballot_box_check",
-    "unicode": "1F5F9",
-    "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15"
-  },
-  {
-    "name": "ballot_box_with_bold_check",
-    "unicode": "1F5F9",
-    "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15"
-  },
   {
     "name": "ballot_box_with_check",
     "unicode": "2611",
     "digest": "c98d6f3588dd87e2f318bbfe6c646399a905450edfd814edae4e5b1bddef2134"
   },
-  {
-    "name": "ballot_box_x",
-    "unicode": "1F5F5",
-    "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928"
-  },
-  {
-    "name": "ballot_box_with_script_x",
-    "unicode": "1F5F5",
-    "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928"
-  },
-  {
-    "name": "ballot_x",
-    "unicode": "1F5F4",
-    "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1"
-  },
-  {
-    "name": "ballot_script_x",
-    "unicode": "1F5F4",
-    "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1"
-  },
   {
     "name": "bamboo",
     "unicode": "1F38D",
@@ -579,6 +514,11 @@
     "unicode": "26F9-1F3FF",
     "digest": "c631cefc5d2a0a31bdb9f0a0d97ea68b1c6928e565468998403034644572a0b0"
   },
+  {
+    "name": "bat",
+    "unicode": "1F987",
+    "digest": "8fc19e0d7d6f80906bdbc06d616a810de66180d96cf28070a53fa61b88904535"
+  },
   {
     "name": "bath",
     "unicode": "1F6C0",
@@ -759,6 +699,11 @@
     "unicode": "26AB",
     "digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e"
   },
+  {
+    "name": "black_heart",
+    "unicode": "1F5A4",
+    "digest": "f334679168d6dd7328c28e9ae3cb2b1fca0e9c2777938d586bfe623db2a688b9"
+  },
   {
     "name": "black_joker",
     "unicode": "1F0CF",
@@ -839,11 +784,6 @@
     "unicode": "1F4D6",
     "digest": "9d912a9d1bb10dc7f2645b345ed09e90461e83df0de275acb806f1f75cef1fcf"
   },
-  {
-    "name": "book2",
-    "unicode": "1F56E",
-    "digest": "26d6b66a1957e7750b3e22eb2e46d0cc85932977bbb81d3d8482ec1ec58ee12b"
-  },
   {
     "name": "bookmark",
     "unicode": "1F516",
@@ -874,16 +814,6 @@
     "unicode": "1F490",
     "digest": "b93751a27b40f6185a22b3e8b413f0fe09b6010d1057c672e1a23088e0b8286f"
   },
-  {
-    "name": "bouquet2",
-    "unicode": "1F395",
-    "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d"
-  },
-  {
-    "name": "bouquet_of_flowers",
-    "unicode": "1F395",
-    "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d"
-  },
   {
     "name": "bow",
     "unicode": "1F647",
@@ -929,6 +859,16 @@
     "unicode": "1F3B3",
     "digest": "737f2cdfa4ac964baade585a39771b18080bd5e9b55c8661d3518f468f344662"
   },
+  {
+    "name": "boxing_glove",
+    "unicode": "1F94A",
+    "digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
+  },
+  {
+    "name": "boxing_gloves",
+    "unicode": "1F94A",
+    "digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
+  },
   {
     "name": "boy",
     "unicode": "1F466",
@@ -959,11 +899,6 @@
     "unicode": "1F466-1F3FF",
     "digest": "0f76e97237203950da36c737dcc6f56dcd6c123401a8c817a0636376c7f38ef5"
   },
-  {
-    "name": "boys_symbol",
-    "unicode": "1F6C9",
-    "digest": "47fadbcb876ca436264ce2f3ebd1472bd68f55cc2b4833bf054335be9dc7a0f2"
-  },
   {
     "name": "bread",
     "unicode": "1F35E",
@@ -1034,21 +969,6 @@
     "unicode": "1F684",
     "digest": "96e74842e919716b7bbbab57339bfd70f099a9bcb4710dffd7c80cf38a7bbff7"
   },
-  {
-    "name": "bullhorn",
-    "unicode": "1F56B",
-    "digest": "a4ca5cbfe299e8ccd148d17055d2d395cf8515e416bf771044c9a670509a8254"
-  },
-  {
-    "name": "bullhorn_waves",
-    "unicode": "1F56C",
-    "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c"
-  },
-  {
-    "name": "bullhorn_with_sound_waves",
-    "unicode": "1F56C",
-    "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c"
-  },
   {
     "name": "burrito",
     "unicode": "1F32F",
@@ -1074,6 +994,11 @@
     "unicode": "1F465",
     "digest": "7fee96f1b68bb2c6002e47f2ed13c06baa6a3168441b9aca572db7ec45612f7b"
   },
+  {
+    "name": "butterfly",
+    "unicode": "1F98B",
+    "digest": "a91b6598c17b44a8dc8935a1d99e25f4483ea41470cdd2da343039a9eec29ef1"
+  },
   {
     "name": "cactus",
     "unicode": "1F335",
@@ -1084,16 +1009,6 @@
     "unicode": "1F370",
     "digest": "b928902df8084210d51c1da36f9119164a325393c391b28cd8ea914e0b95c17b"
   },
-  {
-    "name": "calculator",
-    "unicode": "1F5A9",
-    "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c"
-  },
-  {
-    "name": "pocket calculator",
-    "unicode": "1F5A9",
-    "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c"
-  },
   {
     "name": "calendar",
     "unicode": "1F4C6",
@@ -1109,6 +1024,66 @@
     "unicode": "1F5D3",
     "digest": "441a0750eade7ce33e28e58bec76958990c412b68409fcdde59ebad1f25361bb"
   },
+  {
+    "name": "call_me",
+    "unicode": "1F919",
+    "digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
+  },
+  {
+    "name": "call_me_hand",
+    "unicode": "1F919",
+    "digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
+  },
+  {
+    "name": "call_me_tone1",
+    "unicode": "1F919-1F3FB",
+    "digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
+  },
+  {
+    "name": "call_me_hand_tone1",
+    "unicode": "1F919-1F3FB",
+    "digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
+  },
+  {
+    "name": "call_me_tone2",
+    "unicode": "1F919-1F3FC",
+    "digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
+  },
+  {
+    "name": "call_me_hand_tone2",
+    "unicode": "1F919-1F3FC",
+    "digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
+  },
+  {
+    "name": "call_me_tone3",
+    "unicode": "1F919-1F3FD",
+    "digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
+  },
+  {
+    "name": "call_me_hand_tone3",
+    "unicode": "1F919-1F3FD",
+    "digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
+  },
+  {
+    "name": "call_me_tone4",
+    "unicode": "1F919-1F3FE",
+    "digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
+  },
+  {
+    "name": "call_me_hand_tone4",
+    "unicode": "1F919-1F3FE",
+    "digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
+  },
+  {
+    "name": "call_me_tone5",
+    "unicode": "1F919-1F3FF",
+    "digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
+  },
+  {
+    "name": "call_me_hand_tone5",
+    "unicode": "1F919-1F3FF",
+    "digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
+  },
   {
     "name": "calling",
     "unicode": "1F4F2",
@@ -1134,11 +1109,6 @@
     "unicode": "1F3D5",
     "digest": "a42a4ff9521affa72db7b0f01da169b4cb6afb9db1c5dfad47dd4c507bfc30d9"
   },
-  {
-    "name": "cancellation_x",
-    "unicode": "1F5D9",
-    "digest": "cea2f7a48543207615ee06755ded62c2a95a7eaf7d7b68a3fc25e74d94e2c92c"
-  },
   {
     "name": "cancer",
     "unicode": "264B",
@@ -1154,6 +1124,16 @@
     "unicode": "1F36C",
     "digest": "9cff4538918f60f770fceb96e964f5dc3ce31fd08ddd2ab3bfdf2981bfa74100"
   },
+  {
+    "name": "canoe",
+    "unicode": "1F6F6",
+    "digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
+  },
+  {
+    "name": "kayak",
+    "unicode": "1F6F6",
+    "digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
+  },
   {
     "name": "capital_abcd",
     "unicode": "1F520",
@@ -1185,14 +1165,69 @@
     "digest": "c0e7059efc39a64233f774c02ddb1ab51888fff180f906ce13a6e4f9509672fe"
   },
   {
-    "name": "cartridge",
-    "unicode": "1F5AD",
-    "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2"
+    "name": "carrot",
+    "unicode": "1F955",
+    "digest": "3a6fd98b63ee73d982a9cdacb08cf7b4014368cde8ffce6056b7df25a5a472b1"
+  },
+  {
+    "name": "cartwheel",
+    "unicode": "1F938",
+    "digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
+  },
+  {
+    "name": "person_doing_cartwheel",
+    "unicode": "1F938",
+    "digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
   },
   {
-    "name": "tape_cartridge",
-    "unicode": "1F5AD",
-    "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2"
+    "name": "cartwheel_tone1",
+    "unicode": "1F938-1F3FB",
+    "digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
+  },
+  {
+    "name": "person_doing_cartwheel_tone1",
+    "unicode": "1F938-1F3FB",
+    "digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
+  },
+  {
+    "name": "cartwheel_tone2",
+    "unicode": "1F938-1F3FC",
+    "digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
+  },
+  {
+    "name": "person_doing_cartwheel_tone2",
+    "unicode": "1F938-1F3FC",
+    "digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
+  },
+  {
+    "name": "cartwheel_tone3",
+    "unicode": "1F938-1F3FD",
+    "digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
+  },
+  {
+    "name": "person_doing_cartwheel_tone3",
+    "unicode": "1F938-1F3FD",
+    "digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
+  },
+  {
+    "name": "cartwheel_tone4",
+    "unicode": "1F938-1F3FE",
+    "digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
+  },
+  {
+    "name": "person_doing_cartwheel_tone4",
+    "unicode": "1F938-1F3FE",
+    "digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
+  },
+  {
+    "name": "cartwheel_tone5",
+    "unicode": "1F938-1F3FF",
+    "digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
+  },
+  {
+    "name": "person_doing_cartwheel_tone5",
+    "unicode": "1F938-1F3FF",
+    "digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
   },
   {
     "name": "cat",
@@ -1209,11 +1244,6 @@
     "unicode": "1F4BF",
     "digest": "16363d8a34b873c12df6354b99f575cae3d80e0d27100ed7eea70f0310953c7b"
   },
-  {
-    "name": "celtic_cross",
-    "unicode": "1F548",
-    "digest": "187aac988d7e02085a15f31c4cc0ff25127be5b088e354e65c7b1152bffb40ff"
-  },
   {
     "name": "chains",
     "unicode": "26D3",
@@ -1229,6 +1259,16 @@
     "unicode": "1F37E",
     "digest": "9e6e8987f30a37ae0f3d7dab2f5eeb50aa32b4f31402b29315eb2994afc72457"
   },
+  {
+    "name": "champagne_glass",
+    "unicode": "1F942",
+    "digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
+  },
+  {
+    "name": "clinking_glass",
+    "unicode": "1F942",
+    "digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
+  },
   {
     "name": "chart",
     "unicode": "1F4B9",
@@ -1514,16 +1554,6 @@
     "unicode": "1F564",
     "digest": "9fdef6a4939315c017b165e1dbac7710fb335df8c309be3fe2a011ef7fc28d74"
   },
-  {
-    "name": "clockwise_arrows",
-    "unicode": "1F5D8",
-    "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622"
-  },
-  {
-    "name": "clockwise_right_and_left_semicircle_arrows",
-    "unicode": "1F5D8",
-    "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622"
-  },
   {
     "name": "closed_book",
     "unicode": "1F4D5",
@@ -1584,6 +1614,16 @@
     "unicode": "1F32A",
     "digest": "7cbed2343c280ba3996082b3d0fb9d8cd57d6e62fe6c9ecb159f46b4a2e49151"
   },
+  {
+    "name": "clown",
+    "unicode": "1F921",
+    "digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
+  },
+  {
+    "name": "clown_face",
+    "unicode": "1F921",
+    "digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
+  },
   {
     "name": "clubs",
     "unicode": "2663",
@@ -1624,16 +1664,6 @@
     "unicode": "1F4BB",
     "digest": "c970ce76b5607434895b0407bdaa93140f887930781a17dd7dcf16f711451d93"
   },
-  {
-    "name": "computer_old",
-    "unicode": "1F5B3",
-    "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3"
-  },
-  {
-    "name": "old_personal_computer",
-    "unicode": "1F5B3",
-    "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3"
-  },
   {
     "name": "confetti_ball",
     "unicode": "1F38A",
@@ -1664,6 +1694,11 @@
     "unicode": "1F3D7",
     "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
   },
+  {
+    "name": "building_construction",
+    "unicode": "1F3D7",
+    "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
+  },
   {
     "name": "construction_worker",
     "unicode": "1F477",
@@ -1699,16 +1734,6 @@
     "unicode": "1F39B",
     "digest": "0d7f33ff7acc1cc3a81e6a786ff007df20da145e3070f338505dfed5100e9fcb"
   },
-  {
-    "name": "contruction_site",
-    "unicode": "1F3D7",
-    "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
-  },
-  {
-    "name": "building_construction",
-    "unicode": "1F3D7",
-    "digest": "c611f0a5de10f000a0756935f226845c7292f19ff5581d1f7a7554316338bbcb"
-  },
   {
     "name": "convenience_store",
     "unicode": "1F3EA",
@@ -1719,6 +1744,11 @@
     "unicode": "1F36A",
     "digest": "4bed3522bd50091ac5b68ca760661eb484d7f1b9c9d564d2097bd812b7f28ae4"
   },
+  {
+    "name": "cooking",
+    "unicode": "1F373",
+    "digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58"
+  },
   {
     "name": "cool",
     "unicode": "1F192",
@@ -1819,6 +1849,16 @@
     "unicode": "1F404",
     "digest": "e7a5131d7dee0f3356814b0ac1ea8ff280b12a7b580181e20ddb0b7eeb7e7339"
   },
+  {
+    "name": "cowboy",
+    "unicode": "1F920",
+    "digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
+  },
+  {
+    "name": "face_with_cowboy_hat",
+    "unicode": "1F920",
+    "digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
+  },
   {
     "name": "crab",
     "unicode": "1F980",
@@ -1859,6 +1899,11 @@
     "unicode": "1F40A",
     "digest": "59cb4164c50b6bc9ae311ce6f7610467c1aaafa848b5fff7614f064715f91992"
   },
+  {
+    "name": "croissant",
+    "unicode": "1F950",
+    "digest": "b751e287157a1e276617a841a5b5f7f1208ca226cfd8fa947f144390b65a5e16"
+  },
   {
     "name": "cross",
     "unicode": "271D",
@@ -1869,36 +1914,6 @@
     "unicode": "271D",
     "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653"
   },
-  {
-    "name": "cross_heavy",
-    "unicode": "1F547",
-    "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70"
-  },
-  {
-    "name": "heavy_latin_cross",
-    "unicode": "1F547",
-    "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70"
-  },
-  {
-    "name": "cross_white",
-    "unicode": "1F546",
-    "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6"
-  },
-  {
-    "name": "white_latin_cross",
-    "unicode": "1F546",
-    "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6"
-  },
-  {
-    "name": "crossbones",
-    "unicode": "1F571",
-    "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584"
-  },
-  {
-    "name": "black_skull_and_crossbones",
-    "unicode": "1F571",
-    "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584"
-  },
   {
     "name": "crossed_flags",
     "unicode": "1F38C",
@@ -1939,6 +1954,11 @@
     "unicode": "1F52E",
     "digest": "05f73b30b1e5b0fc66fb5dc6caddd2d547ee7b9d2f97513dc908ba1a2e352e30"
   },
+  {
+    "name": "cucumber",
+    "unicode": "1F952",
+    "digest": "d1196e23f2f155ef5c1330f8497f40957a7357cb177127f457c5c471f0a23727"
+  },
   {
     "name": "cupid",
     "unicode": "1F498",
@@ -2049,16 +2069,16 @@
     "unicode": "1F333",
     "digest": "3c70f1a77f2754f41c830e88d43b7d53c14311d64626ded164aa9ac7d2695790"
   },
+  {
+    "name": "deer",
+    "unicode": "1F98C",
+    "digest": "7f4302ca68fd121ee73be48d0a0a0fb9e7e2741071a491ad2b7b0eab9f11ad25"
+  },
   {
     "name": "department_store",
     "unicode": "1F3EC",
     "digest": "4be910d2efe74d8ce2c1f41d7753c8873579faca83fcf779a4887d8ab9e5923b"
   },
-  {
-    "name": "descending_notes",
-    "unicode": "1F39D",
-    "digest": "f09c6a2e094b13bf91cc07b7b776e43348ccef9f91247ca36cc02e7d91098af0"
-  },
   {
     "name": "desert",
     "unicode": "1F3DC",
@@ -2074,11 +2094,6 @@
     "unicode": "1F5A5",
     "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e"
   },
-  {
-    "name": "desktop_window",
-    "unicode": "1F5D4",
-    "digest": "d5b6c4a847e2a96f97f50fd353a22cb121915cb1d7bbc0f02df38769819b6b7e"
-  },
   {
     "name": "diamond_shape_with_a_dot_inside",
     "unicode": "1F4A0",
@@ -2124,21 +2139,6 @@
     "unicode": "1F6AF",
     "digest": "98b07fbbcdb438d1b8a755869fa2de8e180a77fce359ec830eb46d38ec3e67cb"
   },
-  {
-    "name": "document",
-    "unicode": "1F5CE",
-    "digest": "2cbca96cc69306a10f1a9b6505723e027239439d899f6b395dc43f3c37d2d777"
-  },
-  {
-    "name": "document_text",
-    "unicode": "1F5B9",
-    "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72"
-  },
-  {
-    "name": "document_with_text",
-    "unicode": "1F5B9",
-    "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72"
-  },
   {
     "name": "dog",
     "unicode": "1F436",
@@ -2204,11 +2204,36 @@
     "unicode": "1F42A",
     "digest": "e06ef69c29f0fb12481727c0b4124e700572d3d7955e173279320f43f286518d"
   },
+  {
+    "name": "drooling_face",
+    "unicode": "1F924",
+    "digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
+  },
+  {
+    "name": "drool",
+    "unicode": "1F924",
+    "digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
+  },
   {
     "name": "droplet",
     "unicode": "1F4A7",
     "digest": "6475b4a4460a672c436a68f282ac97fb31e2934db4b80620063ee816159aa7c3"
   },
+  {
+    "name": "drum",
+    "unicode": "1F941",
+    "digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
+  },
+  {
+    "name": "drum_with_drumsticks",
+    "unicode": "1F941",
+    "digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
+  },
+  {
+    "name": "duck",
+    "unicode": "1F986",
+    "digest": "8f8373798a7727368b32328e7a9a349727a949e7391ddd243b6456141a4f7e94"
+  },
   {
     "name": "dvd",
     "unicode": "1F4C0",
@@ -2224,6 +2249,11 @@
     "unicode": "1F4E7",
     "digest": "39b5a57a2376e4a1137e381be02a1775bd580e0371438f5297a401ea634f1830"
   },
+  {
+    "name": "eagle",
+    "unicode": "1F985",
+    "digest": "b44fd4f61b83c5114358a272343ac9b0eabbc70847f739bbdbf8aae3ade5bc1d"
+  },
   {
     "name": "ear",
     "unicode": "1F442",
@@ -2276,8 +2306,8 @@
   },
   {
     "name": "egg",
-    "unicode": "1F373",
-    "digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58"
+    "unicode": "1F95A",
+    "digest": "72b9c841af784e7cbccbbe48ba833df5cecdd284397c199cab079872e879d92f"
   },
   {
     "name": "eggplant",
@@ -2299,6 +2329,16 @@
     "unicode": "2733",
     "digest": "bb0758e7cc0e357285937671a91489bd32ce9d248eecdcc9c275a53a66325b26"
   },
+  {
+    "name": "eject",
+    "unicode": "23CF",
+    "digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
+  },
+  {
+    "name": "eject_symbol",
+    "unicode": "23CF",
+    "digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
+  },
   {
     "name": "electric_plug",
     "unicode": "1F50C",
@@ -2319,46 +2359,6 @@
     "unicode": "2709",
     "digest": "f5a512022a2f5280f372ff39c22cbda815f698710ca66f8f8c4d08418f98ca78"
   },
-  {
-    "name": "envelope_back",
-    "unicode": "1F582",
-    "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75"
-  },
-  {
-    "name": "back_of_envelope",
-    "unicode": "1F582",
-    "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75"
-  },
-  {
-    "name": "envelope_flying",
-    "unicode": "1F585",
-    "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214"
-  },
-  {
-    "name": "flying_envelope",
-    "unicode": "1F585",
-    "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214"
-  },
-  {
-    "name": "envelope_stamped",
-    "unicode": "1F583",
-    "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468"
-  },
-  {
-    "name": "stamped_envelope",
-    "unicode": "1F583",
-    "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468"
-  },
-  {
-    "name": "envelope_stamped_pen",
-    "unicode": "1F586",
-    "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33"
-  },
-  {
-    "name": "pen_over_stamped_envelope",
-    "unicode": "1F586",
-    "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33"
-  },
   {
     "name": "envelope_with_arrow",
     "unicode": "1F4E9",
@@ -2414,6 +2414,36 @@
     "unicode": "1F440",
     "digest": "1d5cae0b9b2e51e1de54295685d7f0c72ee794e2e6335a95b1d056c7e77260e8"
   },
+  {
+    "name": "face_palm",
+    "unicode": "1F926",
+    "digest": "4ec873048b34b1bb34430724cf28e4bee6c0a9eee88ce39b9d1565047dc92420"
+  },
+  {
+    "name": "face_palm_tone1",
+    "unicode": "1F926-1F3FB",
+    "digest": "e93ef92b4c01dbea6c400e708e23dd36da92ccfbf5eb4f177b3b20c3a46bdc19"
+  },
+  {
+    "name": "face_palm_tone2",
+    "unicode": "1F926-1F3FC",
+    "digest": "22c8bf9fd9fa2ed9dca7a6397ed00ba6cfe9aeef2b0fb7b516ee4dda0df050ea"
+  },
+  {
+    "name": "face_palm_tone3",
+    "unicode": "1F926-1F3FD",
+    "digest": "c0b8bb9d2423e6787b6bdf1ca5a13f52853e4f48a9a1af0f2d4af1364fff022e"
+  },
+  {
+    "name": "face_palm_tone4",
+    "unicode": "1F926-1F3FE",
+    "digest": "f522ab186adcbb4549ea2c03500cdd7a86add548e43ebf7a54d58cc24deea072"
+  },
+  {
+    "name": "face_palm_tone5",
+    "unicode": "1F926-1F3FF",
+    "digest": "363507ae7178b5ec583635f47bcab10c897346f48b85d8759b1004c32cd8ad65"
+  },
   {
     "name": "factory",
     "unicode": "1F3ED",
@@ -2519,6 +2549,16 @@
     "unicode": "1F43E",
     "digest": "45aca538d3a9831a0c7de491e5656c17705c07b8f4ac8e85254656b608976016"
   },
+  {
+    "name": "fencer",
+    "unicode": "1F93A",
+    "digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
+  },
+  {
+    "name": "fencing",
+    "unicode": "1F93A",
+    "digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
+  },
   {
     "name": "ferris_wheel",
     "unicode": "1F3A1",
@@ -2550,54 +2590,64 @@
     "digest": "4da212148cadb9c4ea91e60d2d8316e38cea99ef4f14afc023711dd7c54ade5a"
   },
   {
-    "name": "finger_pointing_down",
-    "unicode": "1F597",
-    "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9"
+    "name": "fingers_crossed",
+    "unicode": "1F91E",
+    "digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
   },
   {
-    "name": "white_down_pointing_left_hand_index",
-    "unicode": "1F597",
-    "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9"
+    "name": "hand_with_index_and_middle_finger_crossed",
+    "unicode": "1F91E",
+    "digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
   },
   {
-    "name": "finger_pointing_down2",
-    "unicode": "1F59F",
-    "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee"
+    "name": "fingers_crossed_tone1",
+    "unicode": "1F91E-1F3FB",
+    "digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
   },
   {
-    "name": "sideways_white_down_pointing_index",
-    "unicode": "1F59F",
-    "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee"
+    "name": "hand_with_index_and_middle_fingers_crossed_tone1",
+    "unicode": "1F91E-1F3FB",
+    "digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
   },
   {
-    "name": "finger_pointing_left",
-    "unicode": "1F598",
-    "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e"
+    "name": "fingers_crossed_tone2",
+    "unicode": "1F91E-1F3FC",
+    "digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
   },
   {
-    "name": "sideways_white_left_pointing_index",
-    "unicode": "1F598",
-    "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e"
+    "name": "hand_with_index_and_middle_fingers_crossed_tone2",
+    "unicode": "1F91E-1F3FC",
+    "digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
   },
   {
-    "name": "finger_pointing_right",
-    "unicode": "1F599",
-    "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d"
+    "name": "fingers_crossed_tone3",
+    "unicode": "1F91E-1F3FD",
+    "digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
   },
   {
-    "name": "sideways_white_right_pointing_index",
-    "unicode": "1F599",
-    "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d"
+    "name": "hand_with_index_and_middle_fingers_crossed_tone3",
+    "unicode": "1F91E-1F3FD",
+    "digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
   },
   {
-    "name": "finger_pointing_up",
-    "unicode": "1F59E",
-    "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d"
+    "name": "fingers_crossed_tone4",
+    "unicode": "1F91E-1F3FE",
+    "digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
   },
   {
-    "name": "sideways_white_up_pointing_index",
-    "unicode": "1F59E",
-    "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d"
+    "name": "hand_with_index_and_middle_fingers_crossed_tone4",
+    "unicode": "1F91E-1F3FE",
+    "digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
+  },
+  {
+    "name": "fingers_crossed_tone5",
+    "unicode": "1F91E-1F3FF",
+    "digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
+  },
+  {
+    "name": "hand_with_index_and_middle_fingers_crossed_tone5",
+    "unicode": "1F91E-1F3FF",
+    "digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
   },
   {
     "name": "fire",
@@ -2615,19 +2665,19 @@
     "digest": "c3a518f27d625e3b62dffa227eb82764bf0a147f10ec0e7f4f43f3f96751af20"
   },
   {
-    "name": "fire_engine_oncoming",
-    "unicode": "1F6F1",
-    "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6"
+    "name": "fireworks",
+    "unicode": "1F386",
+    "digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65"
   },
   {
-    "name": "oncoming_fire_engine",
-    "unicode": "1F6F1",
-    "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6"
+    "name": "first_place",
+    "unicode": "1F947",
+    "digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
   },
   {
-    "name": "fireworks",
-    "unicode": "1F386",
-    "digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65"
+    "name": "first_place_medal",
+    "unicode": "1F947",
+    "digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
   },
   {
     "name": "first_quarter_moon",
@@ -5299,41 +5349,11 @@
     "unicode": "269C",
     "digest": "ebf49007f367dc05580e9dab942e93e9dda12fa1dc2caa410ac7f8d8cd55d2a3"
   },
-  {
-    "name": "flip_phone",
-    "unicode": "1F581",
-    "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697"
-  },
-  {
-    "name": "clamshell_mobile_phone",
-    "unicode": "1F581",
-    "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697"
-  },
-  {
-    "name": "floppy_black",
-    "unicode": "1F5AA",
-    "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea"
-  },
-  {
-    "name": "black_hard_shell_floppy_disk",
-    "unicode": "1F5AA",
-    "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea"
-  },
   {
     "name": "floppy_disk",
     "unicode": "1F4BE",
     "digest": "4ee0b5bba41b9e301ed125d3ee1c263bef171ca499e6e1b89276b09af2bc03a0"
   },
-  {
-    "name": "floppy_white",
-    "unicode": "1F5AB",
-    "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0"
-  },
-  {
-    "name": "white_hard_shell_floppy_disk",
-    "unicode": "1F5AB",
-    "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0"
-  },
   {
     "name": "flower_playing_cards",
     "unicode": "1F3B4",
@@ -5354,21 +5374,6 @@
     "unicode": "1F301",
     "digest": "bc3631a4e9e8473b92e842008937add2cd9ffad5b7d772ce759fb5ff6c0e3dca"
   },
-  {
-    "name": "folder",
-    "unicode": "1F5C0",
-    "digest": "8932141321911032ce8469ba85fe309b78384545c3b9946978b383670b956644"
-  },
-  {
-    "name": "folder_open",
-    "unicode": "1F5C1",
-    "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685"
-  },
-  {
-    "name": "open_folder",
-    "unicode": "1F5C1",
-    "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685"
-  },
   {
     "name": "football",
     "unicode": "1F3C8",
@@ -5409,6 +5414,16 @@
     "unicode": "1F340",
     "digest": "ebee16e86bc9be843dfc72ab5372fb462f06be4486b5b25d7d4cac9b2c8b01c8"
   },
+  {
+    "name": "fox",
+    "unicode": "1F98A",
+    "digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
+  },
+  {
+    "name": "fox_face",
+    "unicode": "1F98A",
+    "digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
+  },
   {
     "name": "frame_photo",
     "unicode": "1F5BC",
@@ -5420,29 +5435,19 @@
     "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c"
   },
   {
-    "name": "frame_tiles",
-    "unicode": "1F5BD",
-    "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0"
-  },
-  {
-    "name": "frame_with_tiles",
-    "unicode": "1F5BD",
-    "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0"
-  },
-  {
-    "name": "frame_x",
-    "unicode": "1F5BE",
-    "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15"
+    "name": "free",
+    "unicode": "1F193",
+    "digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa"
   },
   {
-    "name": "frame_with_an_x",
-    "unicode": "1F5BE",
-    "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15"
+    "name": "french_bread",
+    "unicode": "1F956",
+    "digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
   },
   {
-    "name": "free",
-    "unicode": "1F193",
-    "digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa"
+    "name": "baguette_bread",
+    "unicode": "1F956",
+    "digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
   },
   {
     "name": "fried_shrimp",
@@ -5559,16 +5564,21 @@
     "unicode": "1F467-1F3FF",
     "digest": "f55e4b16a41b6f5e3c817a301420360ba4486e4e82e1092a56a3e3cc4069087d"
   },
-  {
-    "name": "girls_symbol",
-    "unicode": "1F6CA",
-    "digest": "2c55aee81defd7a1620ffeaad8d9bcc1835f19237c72c79633aec45671ddb9ff"
-  },
   {
     "name": "globe_with_meridians",
     "unicode": "1F310",
     "digest": "725bebeb3c09a9e3701ebe49e672dcfbf2b73575e05f0821263511577b013b75"
   },
+  {
+    "name": "goal",
+    "unicode": "1F945",
+    "digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
+  },
+  {
+    "name": "goal_net",
+    "unicode": "1F945",
+    "digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
+  },
   {
     "name": "goat",
     "unicode": "1F410",
@@ -5584,6 +5594,11 @@
     "unicode": "1F3CC",
     "digest": "7d7ecc6e226596f646030a4109c2b0001ef0cc690e4863e450bf5d29e7a90344"
   },
+  {
+    "name": "gorilla",
+    "unicode": "1F98D",
+    "digest": "4a564dc14f8ae5450d094f6410ec7f099a7f07dc5254b6395f44a35527bdb4b7"
+  },
   {
     "name": "grapes",
     "unicode": "1F347",
@@ -5734,16 +5749,6 @@
     "unicode": "1F590",
     "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15"
   },
-  {
-    "name": "hand_splayed_reverse",
-    "unicode": "1F591",
-    "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db"
-  },
-  {
-    "name": "reversed_raised_hand_with_fingers_splayed",
-    "unicode": "1F591",
-    "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db"
-  },
   {
     "name": "hand_splayed_tone1",
     "unicode": "1F590-1F3FB",
@@ -5795,24 +5800,99 @@
     "digest": "4b3a0aba7829772fec09f26d6facc19a2f822d2998015297b18b5cab85190ee2"
   },
   {
-    "name": "hand_victory",
-    "unicode": "1F594",
-    "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735"
+    "name": "handbag",
+    "unicode": "1F45C",
+    "digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45"
   },
   {
-    "name": "reversed_victory_hand",
-    "unicode": "1F594",
-    "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735"
+    "name": "handball",
+    "unicode": "1F93E",
+    "digest": "94ceb28024eb3259d8b137cafd7438773e717fbc04f5da810f85e43ca0fa9e00"
   },
   {
-    "name": "handbag",
-    "unicode": "1F45C",
-    "digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45"
+    "name": "handball_tone1",
+    "unicode": "1F93E-1F3FB",
+    "digest": "8bec4de0d05c80e335e44d65598d186ca92696977353c9fd9c2a5efa122cb842"
+  },
+  {
+    "name": "handball_tone2",
+    "unicode": "1F93E-1F3FC",
+    "digest": "2ff4131e1e2f089b315d8e176c9348877c26c2bd03706fb75d41bc61bc99bf93"
+  },
+  {
+    "name": "handball_tone3",
+    "unicode": "1F93E-1F3FD",
+    "digest": "224a71f94dd37d3729325d11412334667a81422e21f6d7c008730ff350f51a80"
   },
   {
-    "name": "hard_disk",
-    "unicode": "1F5B4",
-    "digest": "df8549d4281f5ae70fb6792a02c078e651764b0276aa43b7407236bd38fc21b4"
+    "name": "handball_tone4",
+    "unicode": "1F93E-1F3FE",
+    "digest": "a5f7a9db790565981bad2d0d9e09554c8c509a8179b4705a418300d58a7894b4"
+  },
+  {
+    "name": "handball_tone5",
+    "unicode": "1F93E-1F3FF",
+    "digest": "00404572d4683f2e8e8a494aa733e96fbec1723634d0a8cb8d75f2829a789d27"
+  },
+  {
+    "name": "handshake",
+    "unicode": "1F91D",
+    "digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
+  },
+  {
+    "name": "shaking_hands",
+    "unicode": "1F91D",
+    "digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
+  },
+  {
+    "name": "handshake_tone1",
+    "unicode": "1F91D-1F3FB",
+    "digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
+  },
+  {
+    "name": "shaking_hands_tone1",
+    "unicode": "1F91D-1F3FB",
+    "digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
+  },
+  {
+    "name": "handshake_tone2",
+    "unicode": "1F91D-1F3FC",
+    "digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
+  },
+  {
+    "name": "shaking_hands_tone2",
+    "unicode": "1F91D-1F3FC",
+    "digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
+  },
+  {
+    "name": "handshake_tone3",
+    "unicode": "1F91D-1F3FD",
+    "digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
+  },
+  {
+    "name": "shaking_hands_tone3",
+    "unicode": "1F91D-1F3FD",
+    "digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
+  },
+  {
+    "name": "handshake_tone4",
+    "unicode": "1F91D-1F3FE",
+    "digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
+  },
+  {
+    "name": "shaking_hands_tone4",
+    "unicode": "1F91D-1F3FE",
+    "digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
+  },
+  {
+    "name": "handshake_tone5",
+    "unicode": "1F91D-1F3FF",
+    "digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
+  },
+  {
+    "name": "shaking_hands_tone5",
+    "unicode": "1F91D-1F3FF",
+    "digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
   },
   {
     "name": "hash",
@@ -5879,16 +5959,6 @@
     "unicode": "1F63B",
     "digest": "8a1f28b97d661ca4cff5ee13889ca61b5fa745ccb590e80832b7d7701df101d6"
   },
-  {
-    "name": "heart_tip",
-    "unicode": "1F394",
-    "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204"
-  },
-  {
-    "name": "heart_with_tip_on_the_left",
-    "unicode": "1F394",
-    "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204"
-  },
   {
     "name": "heartbeat",
     "unicode": "1F493",
@@ -6144,16 +6214,6 @@
     "unicode": "1F4E8",
     "digest": "310b7bdcca93452fe10c72c03d0aafa12b98e5d3408896d275d06d3693812c7a"
   },
-  {
-    "name": "info",
-    "unicode": "1F6C8",
-    "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca"
-  },
-  {
-    "name": "circled_information_source",
-    "unicode": "1F6C8",
-    "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca"
-  },
   {
     "name": "information_desk_person",
     "unicode": "1F481",
@@ -6249,16 +6309,6 @@
     "unicode": "1F456",
     "digest": "f986ad32e419cca81c995f8371f0189d1490172a97ebbeac60054a1af08949c5"
   },
-  {
-    "name": "jet_up",
-    "unicode": "1F6E6",
-    "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948"
-  },
-  {
-    "name": "up_pointing_military_airplane",
-    "unicode": "1F6E6",
-    "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948"
-  },
   {
     "name": "joy",
     "unicode": "1F602",
@@ -6274,6 +6324,66 @@
     "unicode": "1F579",
     "digest": "671ee588f397a96f27056a67e6a06d6e8d22c2109ec57b2859badb5fec9cf8dd"
   },
+  {
+    "name": "juggling",
+    "unicode": "1F939",
+    "digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
+  },
+  {
+    "name": "juggler",
+    "unicode": "1F939",
+    "digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
+  },
+  {
+    "name": "juggling_tone1",
+    "unicode": "1F939-1F3FB",
+    "digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
+  },
+  {
+    "name": "juggler_tone1",
+    "unicode": "1F939-1F3FB",
+    "digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
+  },
+  {
+    "name": "juggling_tone2",
+    "unicode": "1F939-1F3FC",
+    "digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
+  },
+  {
+    "name": "juggler_tone2",
+    "unicode": "1F939-1F3FC",
+    "digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
+  },
+  {
+    "name": "juggling_tone3",
+    "unicode": "1F939-1F3FD",
+    "digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
+  },
+  {
+    "name": "juggler_tone3",
+    "unicode": "1F939-1F3FD",
+    "digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
+  },
+  {
+    "name": "juggling_tone4",
+    "unicode": "1F939-1F3FE",
+    "digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
+  },
+  {
+    "name": "juggler_tone4",
+    "unicode": "1F939-1F3FE",
+    "digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
+  },
+  {
+    "name": "juggling_tone5",
+    "unicode": "1F939-1F3FF",
+    "digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
+  },
+  {
+    "name": "juggler_tone5",
+    "unicode": "1F939-1F3FF",
+    "digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
+  },
   {
     "name": "kaaba",
     "unicode": "1F54B",
@@ -6296,38 +6406,8 @@
   },
   {
     "name": "keyboard",
-    "unicode": "1F5AE",
-    "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16"
-  },
-  {
-    "name": "wired_keyboard",
-    "unicode": "1F5AE",
-    "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16"
-  },
-  {
-    "name": "keyboard_mouse",
-    "unicode": "1F5A6",
-    "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3"
-  },
-  {
-    "name": "keyboard_and_mouse",
-    "unicode": "1F5A6",
-    "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3"
-  },
-  {
-    "name": "keyboard_with_jacks",
-    "unicode": "1F398",
-    "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee"
-  },
-  {
-    "name": "musical_keyboard_with_jacks",
-    "unicode": "1F398",
-    "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee"
-  },
-  {
-    "name": "keycap_ten",
-    "unicode": "1F51F",
-    "digest": "c7c9491021740d2c17edddb856f79579b0b943d8dc85a2f48dbaac84f35b8a40"
+    "unicode": "2328",
+    "digest": "34da8ff62ca964142f9281b80123dbba74deaac8d77fa61758c30cfb36c31386"
   },
   {
     "name": "kimono",
@@ -6384,6 +6464,16 @@
     "unicode": "1F619",
     "digest": "f0f8636cb1a02b93cc72ce1b194b890fca823d91e35926b889be3ecfae79207f"
   },
+  {
+    "name": "kiwi",
+    "unicode": "1F95D",
+    "digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
+  },
+  {
+    "name": "kiwifruit",
+    "unicode": "1F95D",
+    "digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
+  },
   {
     "name": "knife",
     "unicode": "1F52A",
@@ -6450,19 +6540,69 @@
     "digest": "e58cb714353e96a2891a5d97910ff79660e637af909b81c49c919d3735db55b4"
   },
   {
-    "name": "left_luggage",
-    "unicode": "1F6C5",
-    "digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf"
+    "name": "left_facing_fist",
+    "unicode": "1F91B",
+    "digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
+  },
+  {
+    "name": "left_fist",
+    "unicode": "1F91B",
+    "digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
+  },
+  {
+    "name": "left_facing_fist_tone1",
+    "unicode": "1F91B-1F3FB",
+    "digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
+  },
+  {
+    "name": "left_fist_tone1",
+    "unicode": "1F91B-1F3FB",
+    "digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
+  },
+  {
+    "name": "left_facing_fist_tone2",
+    "unicode": "1F91B-1F3FC",
+    "digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
+  },
+  {
+    "name": "left_fist_tone2",
+    "unicode": "1F91B-1F3FC",
+    "digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
+  },
+  {
+    "name": "left_facing_fist_tone3",
+    "unicode": "1F91B-1F3FD",
+    "digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
+  },
+  {
+    "name": "left_fist_tone3",
+    "unicode": "1F91B-1F3FD",
+    "digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
+  },
+  {
+    "name": "left_facing_fist_tone4",
+    "unicode": "1F91B-1F3FE",
+    "digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
   },
   {
-    "name": "left_receiver",
-    "unicode": "1F57B",
-    "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3"
+    "name": "left_fist_tone4",
+    "unicode": "1F91B-1F3FE",
+    "digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
   },
   {
-    "name": "left_hand_telephone_receiver",
-    "unicode": "1F57B",
-    "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3"
+    "name": "left_facing_fist_tone5",
+    "unicode": "1F91B-1F3FF",
+    "digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
+  },
+  {
+    "name": "left_fist_tone5",
+    "unicode": "1F91B-1F3FF",
+    "digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
+  },
+  {
+    "name": "left_luggage",
+    "unicode": "1F6C5",
+    "digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf"
   },
   {
     "name": "left_right_arrow",
@@ -6569,16 +6709,6 @@
     "unicode": "1F3CB-1F3FF",
     "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55"
   },
-  {
-    "name": "light_check_mark",
-    "unicode": "1F5F8",
-    "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b"
-  },
-  {
-    "name": "light_mark",
-    "unicode": "1F5F8",
-    "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b"
-  },
   {
     "name": "light_rail",
     "unicode": "1F688",
@@ -6604,16 +6734,16 @@
     "unicode": "1F444",
     "digest": "8740d8086525c7a836d64625a6915cc1c59af69ba143456dbb59e0179276895e"
   },
-  {
-    "name": "lips2",
-    "unicode": "1F5E2",
-    "digest": "c6ba915982ac47d8aaf14ad3605949df95588acfb4e147bf608f8c1714cdf19b"
-  },
   {
     "name": "lipstick",
     "unicode": "1F484",
     "digest": "751dcb22706a796033b13a2ccb94304236ec13207ad4d011e02d230ae33ab5c1"
   },
+  {
+    "name": "lizard",
+    "unicode": "1F98E",
+    "digest": "fb9191f9eab58b8403d4c4626ccbb14ba05c1f6944011751a8edcc4dd03c66e6"
+  },
   {
     "name": "lock",
     "unicode": "1F512",
@@ -6659,6 +6789,16 @@
     "unicode": "1F505",
     "digest": "a065d00a416e297c168b0a675cafcf492fedf94865cb21801a1be5a3914593d4"
   },
+  {
+    "name": "lying_face",
+    "unicode": "1F925",
+    "digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
+  },
+  {
+    "name": "liar",
+    "unicode": "1F925",
+    "digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
+  },
   {
     "name": "m",
     "unicode": "24C2",
@@ -6704,6 +6844,121 @@
     "unicode": "1F468",
     "digest": "42b882d2c6aa095f1afcf901203838d95c1908bdc725519779186b9c33c728d7"
   },
+  {
+    "name": "man_dancing",
+    "unicode": "1F57A",
+    "digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
+  },
+  {
+    "name": "male_dancer",
+    "unicode": "1F57A",
+    "digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
+  },
+  {
+    "name": "man_dancing_tone1",
+    "unicode": "1F57A-1F3FB",
+    "digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
+  },
+  {
+    "name": "male_dancer_tone1",
+    "unicode": "1F57A-1F3FB",
+    "digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
+  },
+  {
+    "name": "man_dancing_tone2",
+    "unicode": "1F57A-1F3FC",
+    "digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
+  },
+  {
+    "name": "male_dancer_tone2",
+    "unicode": "1F57A-1F3FC",
+    "digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
+  },
+  {
+    "name": "man_dancing_tone3",
+    "unicode": "1F57A-1F3FD",
+    "digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
+  },
+  {
+    "name": "male_dancer_tone3",
+    "unicode": "1F57A-1F3FD",
+    "digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
+  },
+  {
+    "name": "man_dancing_tone4",
+    "unicode": "1F57A-1F3FE",
+    "digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
+  },
+  {
+    "name": "male_dancer_tone4",
+    "unicode": "1F57A-1F3FE",
+    "digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
+  },
+  {
+    "name": "man_dancing_tone5",
+    "unicode": "1F57A-1F3FF",
+    "digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
+  },
+  {
+    "name": "male_dancer_tone5",
+    "unicode": "1F57A-1F3FF",
+    "digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
+  },
+  {
+    "name": "man_in_tuxedo",
+    "unicode": "1F935",
+    "digest": "4d451a971dfefedc4830ba78e19b123f250e09ae65baddccdc56c0f8aa3a9b50"
+  },
+  {
+    "name": "man_in_tuxedo_tone1",
+    "unicode": "1F935-1F3FB",
+    "digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
+  },
+  {
+    "name": "tuxedo_tone1",
+    "unicode": "1F935-1F3FB",
+    "digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
+  },
+  {
+    "name": "man_in_tuxedo_tone2",
+    "unicode": "1F935-1F3FC",
+    "digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
+  },
+  {
+    "name": "tuxedo_tone2",
+    "unicode": "1F935-1F3FC",
+    "digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
+  },
+  {
+    "name": "man_in_tuxedo_tone3",
+    "unicode": "1F935-1F3FD",
+    "digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
+  },
+  {
+    "name": "tuxedo_tone3",
+    "unicode": "1F935-1F3FD",
+    "digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
+  },
+  {
+    "name": "man_in_tuxedo_tone4",
+    "unicode": "1F935-1F3FE",
+    "digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
+  },
+  {
+    "name": "tuxedo_tone4",
+    "unicode": "1F935-1F3FE",
+    "digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
+  },
+  {
+    "name": "man_in_tuxedo_tone5",
+    "unicode": "1F935-1F3FF",
+    "digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
+  },
+  {
+    "name": "tuxedo_tone5",
+    "unicode": "1F935-1F3FF",
+    "digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
+  },
   {
     "name": "man_tone1",
     "unicode": "1F468-1F3FB",
@@ -6809,6 +7064,16 @@
     "unicode": "1F341",
     "digest": "72629a205e33f89337815ad7e51bb5c73947d1a9f98afe5072bdf4846827ae72"
   },
+  {
+    "name": "martial_arts_uniform",
+    "unicode": "1F94B",
+    "digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
+  },
+  {
+    "name": "karate_uniform",
+    "unicode": "1F94B",
+    "digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
+  },
   {
     "name": "mask",
     "unicode": "1F637",
@@ -7029,6 +7294,16 @@
     "unicode": "1F396",
     "digest": "5da18351dc14b66cfc070148c83b7c8e67e6b1e3f515ae501133c38ee5c28d3d"
   },
+  {
+    "name": "milk",
+    "unicode": "1F95B",
+    "digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
+  },
+  {
+    "name": "glass_of_milk",
+    "unicode": "1F95B",
+    "digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
+  },
   {
     "name": "milky_way",
     "unicode": "1F30C",
@@ -7084,31 +7359,6 @@
     "unicode": "1F69D",
     "digest": "2c9f185babcb4001fcef2b8dfc4a32126729843084d0076c3e3ccdc845ab23ad"
   },
-  {
-    "name": "mood_bubble",
-    "unicode": "1F5F0",
-    "digest": "1df7061217e478d43ab9a87d4f351c4ca56705acd6b4e0b0bedfdece77635f1b"
-  },
-  {
-    "name": "mood_bubble_lightning",
-    "unicode": "1F5F1",
-    "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9"
-  },
-  {
-    "name": "lightning_mood_bubble",
-    "unicode": "1F5F1",
-    "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9"
-  },
-  {
-    "name": "mood_lightning",
-    "unicode": "1F5F2",
-    "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f"
-  },
-  {
-    "name": "lightning_mood",
-    "unicode": "1F5F2",
-    "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f"
-  },
   {
     "name": "mortar_board",
     "unicode": "1F393",
@@ -7119,6 +7369,16 @@
     "unicode": "1F54C",
     "digest": "5f3d3de7feac953a70a318113531c2857d760a516c3d8d6f42d2a3b3b67ed196"
   },
+  {
+    "name": "motor_scooter",
+    "unicode": "1F6F5",
+    "digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
+  },
+  {
+    "name": "motorbike",
+    "unicode": "1F6F5",
+    "digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
+  },
   {
     "name": "motorboat",
     "unicode": "1F6E5",
@@ -7209,16 +7469,6 @@
     "unicode": "1F401",
     "digest": "f3ed37b639b7c16aae49502bd423f9fdeabaf15bc6f0f74063954b189e176b5d"
   },
-  {
-    "name": "mouse_one",
-    "unicode": "1F5AF",
-    "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0"
-  },
-  {
-    "name": "one_button_mouse",
-    "unicode": "1F5AF",
-    "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0"
-  },
   {
     "name": "mouse_three_button",
     "unicode": "1F5B1",
@@ -7239,6 +7489,66 @@
     "unicode": "1F5FF",
     "digest": "2c1d0662c95928936e6b9ab5a40c6110ff1cea5339f2803c7b63aabc76115afb"
   },
+  {
+    "name": "mrs_claus",
+    "unicode": "1F936",
+    "digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
+  },
+  {
+    "name": "mother_christmas",
+    "unicode": "1F936",
+    "digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
+  },
+  {
+    "name": "mrs_claus_tone1",
+    "unicode": "1F936-1F3FB",
+    "digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
+  },
+  {
+    "name": "mother_christmas_tone1",
+    "unicode": "1F936-1F3FB",
+    "digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
+  },
+  {
+    "name": "mrs_claus_tone2",
+    "unicode": "1F936-1F3FC",
+    "digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
+  },
+  {
+    "name": "mother_christmas_tone2",
+    "unicode": "1F936-1F3FC",
+    "digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
+  },
+  {
+    "name": "mrs_claus_tone3",
+    "unicode": "1F936-1F3FD",
+    "digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
+  },
+  {
+    "name": "mother_christmas_tone3",
+    "unicode": "1F936-1F3FD",
+    "digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
+  },
+  {
+    "name": "mrs_claus_tone4",
+    "unicode": "1F936-1F3FE",
+    "digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
+  },
+  {
+    "name": "mother_christmas_tone4",
+    "unicode": "1F936-1F3FE",
+    "digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
+  },
+  {
+    "name": "mrs_claus_tone5",
+    "unicode": "1F936-1F3FF",
+    "digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
+  },
+  {
+    "name": "mother_christmas_tone5",
+    "unicode": "1F936-1F3FF",
+    "digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
+  },
   {
     "name": "muscle",
     "unicode": "1F4AA",
@@ -7329,6 +7639,16 @@
     "unicode": "1F4DB",
     "digest": "f9f6a4895ff0be8fb2ccc7ad195b94e9650f742f66ead999e90724cfb77af628"
   },
+  {
+    "name": "nauseated_face",
+    "unicode": "1F922",
+    "digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
+  },
+  {
+    "name": "sick",
+    "unicode": "1F922",
+    "digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
+  },
   {
     "name": "necktie",
     "unicode": "1F454",
@@ -7349,16 +7669,6 @@
     "unicode": "1F913",
     "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66"
   },
-  {
-    "name": "network",
-    "unicode": "1F5A7",
-    "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06"
-  },
-  {
-    "name": "three_networked_computers",
-    "unicode": "1F5A7",
-    "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06"
-  },
   {
     "name": "neutral_face",
     "unicode": "1F610",
@@ -7514,26 +7824,6 @@
     "unicode": "1F443-1F3FF",
     "digest": "1e0f9842e0f8ad5805eabd3f35a6038b7a2e49d566a1f5c17271f9cdf467ca60"
   },
-  {
-    "name": "note",
-    "unicode": "1F5C9",
-    "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af"
-  },
-  {
-    "name": "note_page",
-    "unicode": "1F5C9",
-    "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af"
-  },
-  {
-    "name": "note_empty",
-    "unicode": "1F5C6",
-    "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be"
-  },
-  {
-    "name": "empty_note_page",
-    "unicode": "1F5C6",
-    "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be"
-  },
   {
     "name": "notebook",
     "unicode": "1F4D3",
@@ -7544,26 +7834,6 @@
     "unicode": "1F4D4",
     "digest": "d822eda4b49cbfa399b36f134c1a0b8dcfdd27ed89f12c50bc18f6f0a9aa56ef"
   },
-  {
-    "name": "notepad",
-    "unicode": "1F5CA",
-    "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e"
-  },
-  {
-    "name": "note_pad",
-    "unicode": "1F5CA",
-    "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e"
-  },
-  {
-    "name": "notepad_empty",
-    "unicode": "1F5C7",
-    "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af"
-  },
-  {
-    "name": "empty_note_pad",
-    "unicode": "1F5C7",
-    "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af"
-  },
   {
     "name": "notepad_spiral",
     "unicode": "1F5D2",
@@ -7599,6 +7869,16 @@
     "unicode": "1F30A",
     "digest": "1a9ca9848d4fb75852addfc10bf84eccf7caa5339714b90e3de4cb6f2518465e"
   },
+  {
+    "name": "octagonal_sign",
+    "unicode": "1F6D1",
+    "digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
+  },
+  {
+    "name": "stop_sign",
+    "unicode": "1F6D1",
+    "digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
+  },
   {
     "name": "octopus",
     "unicode": "1F419",
@@ -7859,16 +8139,6 @@
     "unicode": "26CE",
     "digest": "6112e2a1656b1cb8bd9a8b0dfa6cbf66d30cae671710a9ef75c821de344aab2b"
   },
-  {
-    "name": "optical_disk",
-    "unicode": "1F5B8",
-    "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d"
-  },
-  {
-    "name": "optical_disc_icon",
-    "unicode": "1F5B8",
-    "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d"
-  },
   {
     "name": "orange_book",
     "unicode": "1F4D9",
@@ -7884,6 +8154,11 @@
     "unicode": "1F4E4",
     "digest": "e47cb481a0ffcb39996f32fd313e19b362a91d8dda15ffca48ac23a3b5bb5baf"
   },
+  {
+    "name": "owl",
+    "unicode": "1F989",
+    "digest": "f62ec1ad23ad9038966eea8d8b79660ac212f291af2e89bcdb0fdc683caf41e5"
+  },
   {
     "name": "ox",
     "unicode": "1F402",
@@ -7894,11 +8169,6 @@
     "unicode": "1F4E6",
     "digest": "e82bf5accebb65136e897c15607eef635fb79fd7b2d8c8e19a9eb00b6786918c"
   },
-  {
-    "name": "page",
-    "unicode": "1F5CF",
-    "digest": "cc745056525f59d9128d1d03b14770376bb09ab64b8ef4ac994ab7f38efd4783"
-  },
   {
     "name": "page_facing_up",
     "unicode": "1F4C4",
@@ -7914,11 +8184,6 @@
     "unicode": "1F4DF",
     "digest": "e21c756cc1c58ebc1b37ebcd38e22a25b31e2e81306c6f18285d6a7671f9eb12"
   },
-  {
-    "name": "pages",
-    "unicode": "1F5D0",
-    "digest": "05bd47b78f089389356d9d839c736843f56b959ab4277056606ffcbb013390bc"
-  },
   {
     "name": "paintbrush",
     "unicode": "1F58C",
@@ -7934,6 +8199,11 @@
     "unicode": "1F334",
     "digest": "90fedafd62fe0abf51325174d0f293ebb9a4794913b9ba93b12f2d0119056df1"
   },
+  {
+    "name": "pancakes",
+    "unicode": "1F95E",
+    "digest": "5256b4832431e8a88555796b1a9726f12d909a26fb2bdc3a0abff76412c45903"
+  },
   {
     "name": "panda_face",
     "unicode": "1F43C",
@@ -8009,6 +8279,16 @@
     "unicode": "1F351",
     "digest": "768d1f4f29e1e06aff5abb29043be83087ded16427ce6a2d0f682814e665e311"
   },
+  {
+    "name": "peanuts",
+    "unicode": "1F95C",
+    "digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
+  },
+  {
+    "name": "shelled_peanut",
+    "unicode": "1F95C",
+    "digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
+  },
   {
     "name": "pear",
     "unicode": "1F350",
@@ -8049,41 +8329,11 @@
     "unicode": "270F",
     "digest": "9ca1b56b5726f472b1f1b23050ed163e213916dac379d22e38e4c8358fe871e0"
   },
-  {
-    "name": "pencil3",
-    "unicode": "1F589",
-    "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c"
-  },
-  {
-    "name": "lower_left_pencil",
-    "unicode": "1F589",
-    "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c"
-  },
   {
     "name": "penguin",
     "unicode": "1F427",
     "digest": "a1800ab931d6dc84a9c89bfab2c815198025c276d952509c55b18dd20bd9d316"
   },
-  {
-    "name": "pennant_black",
-    "unicode": "1F3F2",
-    "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269"
-  },
-  {
-    "name": "black_pennant",
-    "unicode": "1F3F2",
-    "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269"
-  },
-  {
-    "name": "pennant_white",
-    "unicode": "1F3F1",
-    "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0"
-  },
-  {
-    "name": "white_pennant",
-    "unicode": "1F3F1",
-    "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0"
-  },
   {
     "name": "pensive",
     "unicode": "1F614",
@@ -8226,18 +8476,8 @@
   },
   {
     "name": "table_tennis",
-    "unicode": "1F3D3",
-    "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1"
-  },
-  {
-    "name": "piracy",
-    "unicode": "1F572",
-    "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9"
-  },
-  {
-    "name": "no_piracy",
-    "unicode": "1F572",
-    "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9"
+    "unicode": "1F3D3",
+    "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1"
   },
   {
     "name": "pisces",
@@ -8469,6 +8709,11 @@
     "unicode": "1F6B0",
     "digest": "dbe80d9637837377cc2a290da2e895f81a3108cc18b049e3d87212402c1c2098"
   },
+  {
+    "name": "potato",
+    "unicode": "1F954",
+    "digest": "a56a69f36f3a0793f278726d92c0cea2960554f3062ef1a0904526a04511d8e1"
+  },
   {
     "name": "pouch",
     "unicode": "1F45D",
@@ -8524,6 +8769,96 @@
     "unicode": "1F4FF",
     "digest": "80177091264430cbcf7c994fbe5ee17319d1a58d933636cc752a54dafcf98a05"
   },
+  {
+    "name": "pregnant_woman",
+    "unicode": "1F930",
+    "digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
+  },
+  {
+    "name": "expecting_woman",
+    "unicode": "1F930",
+    "digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
+  },
+  {
+    "name": "pregnant_woman_tone1",
+    "unicode": "1F930-1F3FB",
+    "digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
+  },
+  {
+    "name": "expecting_woman_tone1",
+    "unicode": "1F930-1F3FB",
+    "digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
+  },
+  {
+    "name": "pregnant_woman_tone2",
+    "unicode": "1F930-1F3FC",
+    "digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
+  },
+  {
+    "name": "expecting_woman_tone2",
+    "unicode": "1F930-1F3FC",
+    "digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
+  },
+  {
+    "name": "pregnant_woman_tone3",
+    "unicode": "1F930-1F3FD",
+    "digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
+  },
+  {
+    "name": "expecting_woman_tone3",
+    "unicode": "1F930-1F3FD",
+    "digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
+  },
+  {
+    "name": "pregnant_woman_tone4",
+    "unicode": "1F930-1F3FE",
+    "digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
+  },
+  {
+    "name": "expecting_woman_tone4",
+    "unicode": "1F930-1F3FE",
+    "digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
+  },
+  {
+    "name": "pregnant_woman_tone5",
+    "unicode": "1F930-1F3FF",
+    "digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
+  },
+  {
+    "name": "expecting_woman_tone5",
+    "unicode": "1F930-1F3FF",
+    "digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
+  },
+  {
+    "name": "prince",
+    "unicode": "1F934",
+    "digest": "34a0e0625f0a9825d3674192d6233b6cae4d8130451293df09f91a6a4165869c"
+  },
+  {
+    "name": "prince_tone1",
+    "unicode": "1F934-1F3FB",
+    "digest": "ccecdfeccb2ab1fceceae14f3fba875c8c7099785a4c40131c08a697b5b675fc"
+  },
+  {
+    "name": "prince_tone2",
+    "unicode": "1F934-1F3FC",
+    "digest": "c373fd3e0c1798415e3d8d88fab6c98c1bbdedcbe6f52f3a3899f6e2124a768d"
+  },
+  {
+    "name": "prince_tone3",
+    "unicode": "1F934-1F3FD",
+    "digest": "71d15695ca954d55aa69d3c753c7d31a8ba5329713a8ddbc90dafc11e524c4ef"
+  },
+  {
+    "name": "prince_tone4",
+    "unicode": "1F934-1F3FE",
+    "digest": "08f6cb32424f15cc3aaf83c31a5dac7c01a6be2f37ea8f13aed579ce6fb4db19"
+  },
+  {
+    "name": "prince_tone5",
+    "unicode": "1F934-1F3FF",
+    "digest": "77d521148efa33fa4d3409693d050fecfd948411e807327484f174e289834649"
+  },
   {
     "name": "princess",
     "unicode": "1F478",
@@ -8559,16 +8894,6 @@
     "unicode": "1F5A8",
     "digest": "5e5307e3dc7ec4e16c9978fb00934c99c4adefca7d32732a244d1f2de71ce6f8"
   },
-  {
-    "name": "prohibited",
-    "unicode": "1F6C7",
-    "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f"
-  },
-  {
-    "name": "prohibited_sign",
-    "unicode": "1F6C7",
-    "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f"
-  },
   {
     "name": "projector",
     "unicode": "1F4FD",
@@ -8624,11 +8949,6 @@
     "unicode": "1F4CC",
     "digest": "c3f7d7008be6bab8dc02284d4d759abf7aafbb3dbbe3a53f0f5b2ff685af88f8"
   },
-  {
-    "name": "pushpin_black",
-    "unicode": "1F588",
-    "digest": "80ebac74edb9e8e1f8a219b32a676d318ed73b359cd8193b91b493d775307f63"
-  },
   {
     "name": "put_litter_in_its_place",
     "unicode": "1F6AE",
@@ -8709,6 +9029,66 @@
     "unicode": "1F308",
     "digest": "a93aceb54e965f35e397e8c8716b1831614933308d026012d5464ee42783ed4d"
   },
+  {
+    "name": "raised_back_of_hand",
+    "unicode": "1F91A",
+    "digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
+  },
+  {
+    "name": "back_of_hand",
+    "unicode": "1F91A",
+    "digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
+  },
+  {
+    "name": "raised_back_of_hand_tone1",
+    "unicode": "1F91A-1F3FB",
+    "digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
+  },
+  {
+    "name": "back_of_hand_tone1",
+    "unicode": "1F91A-1F3FB",
+    "digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
+  },
+  {
+    "name": "raised_back_of_hand_tone2",
+    "unicode": "1F91A-1F3FC",
+    "digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
+  },
+  {
+    "name": "back_of_hand_tone2",
+    "unicode": "1F91A-1F3FC",
+    "digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
+  },
+  {
+    "name": "raised_back_of_hand_tone3",
+    "unicode": "1F91A-1F3FD",
+    "digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
+  },
+  {
+    "name": "back_of_hand_tone3",
+    "unicode": "1F91A-1F3FD",
+    "digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
+  },
+  {
+    "name": "raised_back_of_hand_tone4",
+    "unicode": "1F91A-1F3FE",
+    "digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
+  },
+  {
+    "name": "back_of_hand_tone4",
+    "unicode": "1F91A-1F3FE",
+    "digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
+  },
+  {
+    "name": "raised_back_of_hand_tone5",
+    "unicode": "1F91A-1F3FF",
+    "digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
+  },
+  {
+    "name": "back_of_hand_tone5",
+    "unicode": "1F91A-1F3FF",
+    "digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
+  },
   {
     "name": "raised_hand",
     "unicode": "270B",
@@ -8879,6 +9259,16 @@
     "unicode": "23EA",
     "digest": "d20c918c1e528ff0947312738501ca9a6fb6ff4016aad07db7a8125d00fd65cd"
   },
+  {
+    "name": "rhino",
+    "unicode": "1F98F",
+    "digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
+  },
+  {
+    "name": "rhinoceros",
+    "unicode": "1F98F",
+    "digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
+  },
   {
     "name": "ribbon",
     "unicode": "1F380",
@@ -8905,40 +9295,70 @@
     "digest": "b942a06d3da0570aca59bab0af57cd8c16863934f12a38f70339fd0a36f675f5"
   },
   {
-    "name": "right_speaker",
-    "unicode": "1F568",
-    "digest": "d268bb84be863c0884620dfc6d2a764b0c7466d2f9810549b138e21ac70add4e"
+    "name": "right_facing_fist",
+    "unicode": "1F91C",
+    "digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
+  },
+  {
+    "name": "right_fist",
+    "unicode": "1F91C",
+    "digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
+  },
+  {
+    "name": "right_facing_fist_tone1",
+    "unicode": "1F91C-1F3FB",
+    "digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
+  },
+  {
+    "name": "right_fist_tone1",
+    "unicode": "1F91C-1F3FB",
+    "digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
   },
   {
-    "name": "right_speaker_one",
-    "unicode": "1F569",
-    "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375"
+    "name": "right_facing_fist_tone2",
+    "unicode": "1F91C-1F3FC",
+    "digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
   },
   {
-    "name": "right_speaker_with_one_sound_wave",
-    "unicode": "1F569",
-    "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375"
+    "name": "right_fist_tone2",
+    "unicode": "1F91C-1F3FC",
+    "digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
   },
   {
-    "name": "right_speaker_three",
-    "unicode": "1F56A",
-    "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80"
+    "name": "right_facing_fist_tone3",
+    "unicode": "1F91C-1F3FD",
+    "digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
   },
   {
-    "name": "right_speaker_with_three_sound_waves",
-    "unicode": "1F56A",
-    "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80"
+    "name": "right_fist_tone3",
+    "unicode": "1F91C-1F3FD",
+    "digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
+  },
+  {
+    "name": "right_facing_fist_tone4",
+    "unicode": "1F91C-1F3FE",
+    "digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
+  },
+  {
+    "name": "right_fist_tone4",
+    "unicode": "1F91C-1F3FE",
+    "digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
+  },
+  {
+    "name": "right_facing_fist_tone5",
+    "unicode": "1F91C-1F3FF",
+    "digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
+  },
+  {
+    "name": "right_fist_tone5",
+    "unicode": "1F91C-1F3FF",
+    "digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
   },
   {
     "name": "ring",
     "unicode": "1F48D",
     "digest": "b5322907222797b5e1786209cda88513e76cd397a40f0a7da24847245c95ef9d"
   },
-  {
-    "name": "ringing_bell",
-    "unicode": "1F56D",
-    "digest": "d71ab7fa937fc4af507b5b07ea58a4f31e875d9e8304ef2b850d7cebe0e9cd66"
-  },
   {
     "name": "robot",
     "unicode": "1F916",
@@ -8954,6 +9374,16 @@
     "unicode": "1F680",
     "digest": "b82e68a95aa89a6de344d6e256fef86a848ebc91de560b043b3e1f7fd072d57d"
   },
+  {
+    "name": "rofl",
+    "unicode": "1F923",
+    "digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
+  },
+  {
+    "name": "rolling_on_the_floor_laughing",
+    "unicode": "1F923",
+    "digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
+  },
   {
     "name": "roller_coaster",
     "unicode": "1F3A2",
@@ -8984,11 +9414,6 @@
     "unicode": "1F3F5",
     "digest": "2537def4deef422d4e669b28b1a0675259306ab38601019df3ec3482b14e52d5"
   },
-  {
-    "name": "rosette_black",
-    "unicode": "1F3F6",
-    "digest": "ae8675891c88f9d98463d35178445950c39b0deb0f0e8b3f341228a6e0d0e477"
-  },
   {
     "name": "rotating_light",
     "unicode": "1F6A8",
@@ -9089,6 +9514,16 @@
     "unicode": "1F376",
     "digest": "0a786075f3d9da48ae91afccf6ae0d097888da9509d354ee1d3cb99afcc88fe4"
   },
+  {
+    "name": "salad",
+    "unicode": "1F957",
+    "digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
+  },
+  {
+    "name": "green_salad",
+    "unicode": "1F957",
+    "digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
+  },
   {
     "name": "sandal",
     "unicode": "1F461",
@@ -9159,6 +9594,11 @@
     "unicode": "2702",
     "digest": "95225be28f05d8b5a6b6e6bf58d973f61f183ad4fef55a558dc1b810796b85c8"
   },
+  {
+    "name": "scooter",
+    "unicode": "1F6F4",
+    "digest": "4a7db148880398db75e059711cb53edefb6b8fa9d442009f52856b887ab1dde4"
+  },
   {
     "name": "scorpion",
     "unicode": "1F982",
@@ -9189,6 +9629,16 @@
     "unicode": "1F4BA",
     "digest": "ae68d86fc2a07cae332451b23bd1ceba3f6526a6c56d8c1089777fa4632850e1"
   },
+  {
+    "name": "second_place",
+    "unicode": "1F948",
+    "digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
+  },
+  {
+    "name": "second_place_medal",
+    "unicode": "1F948",
+    "digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
+  },
   {
     "name": "secret",
     "unicode": "3299",
@@ -9204,16 +9654,61 @@
     "unicode": "1F331",
     "digest": "c0ec5e6d20e1afdc4e78eeddb1301c8b708ad6278e7287a4e4e825417c858e75"
   },
+  {
+    "name": "selfie",
+    "unicode": "1F933",
+    "digest": "2a1bc9f18ad4d6fb893d91c88ef1b2d9bd063dc2bb1a4b08c248c30f52545d4e"
+  },
+  {
+    "name": "selfie_tone1",
+    "unicode": "1F933-1F3FB",
+    "digest": "26dc212ffed30c276bd6a66a72bc4513e68098a2205fb4ca5b51ccfa1de5b544"
+  },
+  {
+    "name": "selfie_tone2",
+    "unicode": "1F933-1F3FC",
+    "digest": "71eceaefda46e3521f374f76693e7fa8f215067498067900080e2925ca94d7de"
+  },
+  {
+    "name": "selfie_tone3",
+    "unicode": "1F933-1F3FD",
+    "digest": "53eabbd4f6b8ebbd2f7af7bf5cd64309c4039ac1c5b2180290a547deaafcebdf"
+  },
+  {
+    "name": "selfie_tone4",
+    "unicode": "1F933-1F3FE",
+    "digest": "0baad378b09652b99c5d458db2e03b4db14a1557db4ea0969806a0ca1d33d40c"
+  },
+  {
+    "name": "selfie_tone5",
+    "unicode": "1F933-1F3FF",
+    "digest": "9a07608f34ec4dad48764a855f83f3965709d7b2fd2342e6dc9ed61f23f4adfd"
+  },
   {
     "name": "seven",
     "unicode": "0037-20E3",
     "digest": "ae85172d2c76c44afb4e3b45d277d400abb2dc895244b9abfbd1dac1cd7c53c2"
   },
+  {
+    "name": "shallow_pan_of_food",
+    "unicode": "1F958",
+    "digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
+  },
+  {
+    "name": "paella",
+    "unicode": "1F958",
+    "digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
+  },
   {
     "name": "shamrock",
     "unicode": "2618",
     "digest": "68ed70c26e04a818439a1742d2da6bc169edd02db86b6e6f8014b651f3235488"
   },
+  {
+    "name": "shark",
+    "unicode": "1F988",
+    "digest": "23a2364b6356e7bbb84c138e9cf58e2c68cd8caabb337a0c4d365ce87bf5d2da"
+  },
   {
     "name": "shaved_ice",
     "unicode": "1F367",
@@ -9254,11 +9749,56 @@
     "unicode": "1F6CD",
     "digest": "95a3f03c675207bb1354270d02a630c204455c47b3edca23c48523a40cf3ea3b"
   },
+  {
+    "name": "shopping_cart",
+    "unicode": "1F6D2",
+    "digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
+  },
+  {
+    "name": "shopping_trolley",
+    "unicode": "1F6D2",
+    "digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
+  },
   {
     "name": "shower",
     "unicode": "1F6BF",
     "digest": "6b3c767c0eb472d4861c6c3cc2735a5e2c09681872ef42a11dc89f3c80b9da01"
   },
+  {
+    "name": "shrimp",
+    "unicode": "1F990",
+    "digest": "b3651f3be3767125076a013fe903854f5b456a8afae865cb219cf528e0f44caa"
+  },
+  {
+    "name": "shrug",
+    "unicode": "1F937",
+    "digest": "6e264243cc3b6e396069dea4357a958bdcd4081cb1af0ed6aa47235bef88cf27"
+  },
+  {
+    "name": "shrug_tone1",
+    "unicode": "1F937-1F3FB",
+    "digest": "0567b9fd95c8a857914003a5465a500ca79c8111811d45b865021b1b1d92d0b1"
+  },
+  {
+    "name": "shrug_tone2",
+    "unicode": "1F937-1F3FC",
+    "digest": "1557c2f5e3d4599c806d74c0b78afcca940678787534b6862bb89a20601bac8a"
+  },
+  {
+    "name": "shrug_tone3",
+    "unicode": "1F937-1F3FD",
+    "digest": "f02754541a7bf74ba7eebe6c27daf1e3e1dac25172c35b8ba45641e278dfda3d"
+  },
+  {
+    "name": "shrug_tone4",
+    "unicode": "1F937-1F3FE",
+    "digest": "2b5121164cb5f4e253d8fb31f6445cf8afaf30dba41732edc511440cdb78d15c"
+  },
+  {
+    "name": "shrug_tone5",
+    "unicode": "1F937-1F3FF",
+    "digest": "62d99a26bbad479f574f66208c41b9960cd41fb9d79d3a13fbdaa44682077115"
+  },
   {
     "name": "signal_strength",
     "unicode": "1F4F6",
@@ -9414,6 +9954,16 @@
     "unicode": "1F40D",
     "digest": "18da2d97c771149ef5454dd23470e900903a62ab93f9e2ce301aad5a8181d773"
   },
+  {
+    "name": "sneezing_face",
+    "unicode": "1F927",
+    "digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
+  },
+  {
+    "name": "sneeze",
+    "unicode": "1F927",
+    "digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
+  },
   {
     "name": "snowboarder",
     "unicode": "1F3C2",
@@ -9519,46 +10069,6 @@
     "unicode": "1F4AC",
     "digest": "817100d9979456e7d2f253ac22e13b7a2302dc1590566214915b003e403c53ca"
   },
-  {
-    "name": "speech_left",
-    "unicode": "1F5E8",
-    "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0"
-  },
-  {
-    "name": "left_speech_bubble",
-    "unicode": "1F5E8",
-    "digest": "912797107d574f5665411498b6e349dbdec69846f085b6dc356548c4155e90b0"
-  },
-  {
-    "name": "speech_right",
-    "unicode": "1F5E9",
-    "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a"
-  },
-  {
-    "name": "right_speech_bubble",
-    "unicode": "1F5E9",
-    "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a"
-  },
-  {
-    "name": "speech_three",
-    "unicode": "1F5EB",
-    "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9"
-  },
-  {
-    "name": "three_speech_bubbles",
-    "unicode": "1F5EB",
-    "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9"
-  },
-  {
-    "name": "speech_two",
-    "unicode": "1F5EA",
-    "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983"
-  },
-  {
-    "name": "two_speech_bubbles",
-    "unicode": "1F5EA",
-    "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983"
-  },
   {
     "name": "speedboat",
     "unicode": "1F6A4",
@@ -9574,6 +10084,11 @@
     "unicode": "1F578",
     "digest": "2434bdfbe56dcc4a43699dd59b638af431486b52fb1d6d685451f3b231b2be23"
   },
+  {
+    "name": "spoon",
+    "unicode": "1F944",
+    "digest": "4fa31d59e5bffd2c45a8e01fcd5652e78a5691cbfa744e69882bc67173ddea05"
+  },
   {
     "name": "spy",
     "unicode": "1F575",
@@ -9634,6 +10149,11 @@
     "unicode": "1F575-1F3FF",
     "digest": "ffc6fefd9a537124ebf0a9ddf387414dce1291335026064644f6cf9315591129"
   },
+  {
+    "name": "squid",
+    "unicode": "1F991",
+    "digest": "65a1b318c2c506b9d26cfd8282a5cf9922109595c8d12e92c3f7481ac7c08c49"
+  },
   {
     "name": "stadium",
     "unicode": "1F3DF",
@@ -9679,26 +10199,11 @@
     "unicode": "1F682",
     "digest": "52ad0073f37b978faf3884fb193046f2b0614e1557bbcc9de1b020e42aff2dba"
   },
-  {
-    "name": "stereo",
-    "unicode": "1F4FE",
-    "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5"
-  },
-  {
-    "name": "portable_stereo",
-    "unicode": "1F4FE",
-    "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5"
-  },
   {
     "name": "stew",
     "unicode": "1F372",
     "digest": "c16f61236db314ad8d9f2dd241ec1e15c8d64e5872cce93ec4d0996490dd39df"
   },
-  {
-    "name": "stock_chart",
-    "unicode": "1F5E0",
-    "digest": "4a0fbf54d19b0b5626f91c932a24e6ac12a65b4fc276d852ff4356c8c579d28a"
-  },
   {
     "name": "stop_button",
     "unicode": "23F9",
@@ -9734,6 +10239,16 @@
     "unicode": "1F61C",
     "digest": "dbacd6428a2a2933212e6a4dc0c7f302177fb23b963626ccb26f27f91737f03d"
   },
+  {
+    "name": "stuffed_flatbread",
+    "unicode": "1F959",
+    "digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
+  },
+  {
+    "name": "stuffed_pita",
+    "unicode": "1F959",
+    "digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
+  },
   {
     "name": "sun_with_face",
     "unicode": "1F31E",
@@ -9909,31 +10424,11 @@
     "unicode": "260E",
     "digest": "3a53851e641f8ad938ce3597b1afca2ea63c9314ff81f62563b99937496a13d7"
   },
-  {
-    "name": "telephone_black",
-    "unicode": "1F57F",
-    "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db"
-  },
-  {
-    "name": "black_touchtone_telephone",
-    "unicode": "1F57F",
-    "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db"
-  },
   {
     "name": "telephone_receiver",
     "unicode": "1F4DE",
     "digest": "1614d67f3d8814b0d75f39d55f9149e4d28ef57b343498625e62fcfff8365046"
   },
-  {
-    "name": "telephone_white",
-    "unicode": "1F57E",
-    "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581"
-  },
-  {
-    "name": "white_touchtone_telephone",
-    "unicode": "1F57E",
-    "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581"
-  },
   {
     "name": "telescope",
     "unicode": "1F52D",
@@ -9980,55 +10475,25 @@
     "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3"
   },
   {
-    "name": "thought_balloon",
-    "unicode": "1F4AD",
-    "digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e"
-  },
-  {
-    "name": "thought_left",
-    "unicode": "1F5EC",
-    "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46"
-  },
-  {
-    "name": "left_thought_bubble",
-    "unicode": "1F5EC",
-    "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46"
+    "name": "third_place",
+    "unicode": "1F949",
+    "digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
   },
   {
-    "name": "thought_right",
-    "unicode": "1F5ED",
-    "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae"
+    "name": "third_place_medal",
+    "unicode": "1F949",
+    "digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
   },
   {
-    "name": "right_thought_bubble",
-    "unicode": "1F5ED",
-    "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae"
+    "name": "thought_balloon",
+    "unicode": "1F4AD",
+    "digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e"
   },
   {
     "name": "three",
     "unicode": "0033-20E3",
     "digest": "d3f85828787799c769655c38a519cad0743ab799ab276c7606e6e6894cc442e6"
   },
-  {
-    "name": "thumbs_down_reverse",
-    "unicode": "1F593",
-    "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958"
-  },
-  {
-    "name": "reversed_thumbs_down_sign",
-    "unicode": "1F593",
-    "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958"
-  },
-  {
-    "name": "thumbs_up_reverse",
-    "unicode": "1F592",
-    "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837"
-  },
-  {
-    "name": "reversed_thumbs_up_sign",
-    "unicode": "1F592",
-    "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837"
-  },
   {
     "name": "thumbsdown",
     "unicode": "1F44E",
@@ -10314,31 +10779,11 @@
     "unicode": "1F686",
     "digest": "06e65d549e771632f3c64287a38ba67236f9800ccb6a23c3b592bc010e24e122"
   },
-  {
-    "name": "train_diesel",
-    "unicode": "1F6F2",
-    "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c"
-  },
-  {
-    "name": "diesel_locomotive",
-    "unicode": "1F6F2",
-    "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c"
-  },
   {
     "name": "tram",
     "unicode": "1F68A",
     "digest": "21a7699f1a94f06dcb4d1e896448b98a4205f8efe902a8ac169a5005d11ab100"
   },
-  {
-    "name": "triangle_round",
-    "unicode": "1F6C6",
-    "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5"
-  },
-  {
-    "name": "triangle_with_rounded_corners",
-    "unicode": "1F6C6",
-    "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5"
-  },
   {
     "name": "triangular_flag_on_post",
     "unicode": "1F6A9",
@@ -10395,19 +10840,19 @@
     "digest": "e744e8dbbdc6b126bd5b15aad56b524191de5a604189f4ab6d96730dfef4d086"
   },
   {
-    "name": "turkey",
-    "unicode": "1F983",
-    "digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4"
+    "name": "tumbler_glass",
+    "unicode": "1F943",
+    "digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
   },
   {
-    "name": "turned_ok_hand",
-    "unicode": "1F58F",
-    "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246"
+    "name": "whisky",
+    "unicode": "1F943",
+    "digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
   },
   {
-    "name": "turned_ok_hand_sign",
-    "unicode": "1F58F",
-    "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246"
+    "name": "turkey",
+    "unicode": "1F983",
+    "digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4"
   },
   {
     "name": "turtle",
@@ -10759,6 +11204,36 @@
     "unicode": "1F403",
     "digest": "ba6a840d4f57f8f9f3e9f29b8a030faf02a3a3d912e3e31b067616b2ac48a3d1"
   },
+  {
+    "name": "water_polo",
+    "unicode": "1F93D",
+    "digest": "fc77e1d2a84a9f4cf0cf19c1ea10cf137cf0940b9103a523121eda87677ad148"
+  },
+  {
+    "name": "water_polo_tone1",
+    "unicode": "1F93D-1F3FB",
+    "digest": "3be28384edd29ada8109f07720d601a9d5866ed63e6234efe9ee1a194ed5d0c5"
+  },
+  {
+    "name": "water_polo_tone2",
+    "unicode": "1F93D-1F3FC",
+    "digest": "afcd3f28c6719f869ca79a6fd1ccade2ea976ade844fbc1081fc72865bcb652f"
+  },
+  {
+    "name": "water_polo_tone3",
+    "unicode": "1F93D-1F3FD",
+    "digest": "d19481c9b82d9413e99c2652e020fd763f2b54408dedaffec8dfe80973ded407"
+  },
+  {
+    "name": "water_polo_tone4",
+    "unicode": "1F93D-1F3FE",
+    "digest": "375972d882b627e8d525e632e58b30346fc3e01858d7d08d62a9d3bf8132bbc7"
+  },
+  {
+    "name": "water_polo_tone5",
+    "unicode": "1F93D-1F3FF",
+    "digest": "a8e1ced1c5382a8147a1d1801a133cada9a0e52e41de6272e56c3c1f426f6048"
+  },
   {
     "name": "watermelon",
     "unicode": "1F349",
@@ -10914,6 +11389,16 @@
     "unicode": "1F324",
     "digest": "0a6164cdadf2413555b7ef47b95f823f5a010f36d2dacfb1a38335a0f59e9601"
   },
+  {
+    "name": "wilted_rose",
+    "unicode": "1F940",
+    "digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
+  },
+  {
+    "name": "wilted_flower",
+    "unicode": "1F940",
+    "digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
+  },
   {
     "name": "wind_blowing_face",
     "unicode": "1F32C",
@@ -10995,14 +11480,69 @@
     "digest": "81aae53bc892035b905bf3ec5b442a8ecc95027c5fa9eb51b7c3e7d8fad3f3f4"
   },
   {
-    "name": "writing_hand",
-    "unicode": "1F58E",
-    "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb"
+    "name": "wrestlers",
+    "unicode": "1F93C",
+    "digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
+  },
+  {
+    "name": "wrestling",
+    "unicode": "1F93C",
+    "digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
+  },
+  {
+    "name": "wrestlers_tone1",
+    "unicode": "1F93C-1F3FB",
+    "digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
+  },
+  {
+    "name": "wrestling_tone1",
+    "unicode": "1F93C-1F3FB",
+    "digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
+  },
+  {
+    "name": "wrestlers_tone2",
+    "unicode": "1F93C-1F3FC",
+    "digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
+  },
+  {
+    "name": "wrestling_tone2",
+    "unicode": "1F93C-1F3FC",
+    "digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
+  },
+  {
+    "name": "wrestlers_tone3",
+    "unicode": "1F93C-1F3FD",
+    "digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
+  },
+  {
+    "name": "wrestling_tone3",
+    "unicode": "1F93C-1F3FD",
+    "digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
+  },
+  {
+    "name": "wrestlers_tone4",
+    "unicode": "1F93C-1F3FE",
+    "digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
+  },
+  {
+    "name": "wrestling_tone4",
+    "unicode": "1F93C-1F3FE",
+    "digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
+  },
+  {
+    "name": "wrestlers_tone5",
+    "unicode": "1F93C-1F3FF",
+    "digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
+  },
+  {
+    "name": "wrestling_tone5",
+    "unicode": "1F93C-1F3FF",
+    "digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
   },
   {
-    "name": "left_writing_hand",
-    "unicode": "1F58E",
-    "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb"
+    "name": "writing_hand",
+    "unicode": "270D",
+    "digest": "110517ae4da5587e8b0662881658e27da4120bfacec54734fd6657831d4d782f"
   },
   {
     "name": "writing_hand_tone1",
diff --git a/fixtures/emojis/index.json b/fixtures/emojis/index.json
index 7f204c1a8e0c3137931e7d3b2b745f642d3bb6c3..2a990913b9cc0d2da7d377dc09a12f66c4d24e36 100644
--- a/fixtures/emojis/index.json
+++ b/fixtures/emojis/index.json
@@ -4,7 +4,7 @@
     "unicode_alternates": [],
     "name": "hundred points symbol",
     "shortname": ":100:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15,12 +15,14 @@
       "percent",
       "a",
       "plus",
-      "perfect",
       "school",
       "quiz",
-      "score",
       "test",
-      "exam"
+      "exam",
+      "symbol",
+      "wow",
+      "win",
+      "parties"
     ],
     "moji": "💯"
   },
@@ -29,12 +31,13 @@
     "unicode_alternates": [],
     "name": "input symbol for numbers",
     "shortname": ":1234:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "numbers"
+      "numbers",
+      "symbol"
     ],
     "moji": "🔢"
   },
@@ -43,16 +46,20 @@
     "unicode_alternates": [],
     "name": "billiards",
     "shortname": ":8ball:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "pool",
       "billiards",
       "eight ball",
-      "pool",
       "pocket ball",
-      "cue"
+      "cue",
+      "game",
+      "ball",
+      "sport",
+      "luck",
+      "boys night"
     ],
     "moji": "🎱"
   },
@@ -61,13 +68,14 @@
     "unicode_alternates": [],
     "name": "negative squared latin capital letter a",
     "shortname": ":a:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "letter",
-      "red-square"
+      "red-square",
+      "symbol"
     ],
     "moji": "🅰"
   },
@@ -76,12 +84,13 @@
     "unicode_alternates": [],
     "name": "negative squared ab",
     "shortname": ":ab:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
-      "red-square"
+      "red-square",
+      "symbol"
     ],
     "moji": "🆎"
   },
@@ -90,12 +99,13 @@
     "unicode_alternates": [],
     "name": "input symbol for latin letters",
     "shortname": ":abc:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "🔤"
   },
@@ -104,12 +114,13 @@
     "unicode_alternates": [],
     "name": "input symbol for latin small letters",
     "shortname": ":abcd:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "🔡"
   },
@@ -118,7 +129,7 @@
     "unicode_alternates": [],
     "name": "circled ideograph accept",
     "shortname": ":accept:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -127,7 +138,8 @@
       "good",
       "kanji",
       "ok",
-      "yes"
+      "yes",
+      "symbol"
     ],
     "moji": "🉑"
   },
@@ -136,7 +148,7 @@
     "unicode_alternates": [],
     "name": "aerial tramway",
     "shortname": ":aerial_tramway:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -146,7 +158,9 @@
       "tram",
       "tramway",
       "cable",
-      "transport"
+      "transport",
+      "travel",
+      "train"
     ],
     "moji": "🚡"
   },
@@ -157,7 +171,7 @@
     ],
     "name": "airplane",
     "shortname": ":airplane:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -173,7 +187,8 @@
       "jet",
       "jumbo",
       "boeing",
-      "airbus"
+      "airbus",
+      "vacation"
     ],
     "moji": "✈"
   },
@@ -182,7 +197,7 @@
     "unicode_alternates": [],
     "name": "airplane arriving",
     "shortname": ":airplane_arriving:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -197,15 +212,17 @@
       "jet",
       "jumbo",
       "boeing",
-      "airbus"
-    ]
+      "airbus",
+      "vacation"
+    ],
+    "moji": "🛬"
   },
   "airplane_departure": {
     "unicode": "1F6EB",
     "unicode_alternates": [],
     "name": "airplane departure",
     "shortname": ":airplane_departure:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -221,30 +238,17 @@
       "jumbo",
       "boeing",
       "airbus",
-      "leaving"
-    ]
-  },
-  "airplane_northeast": {
-    "unicode": "1F6EA",
-    "unicode_alternates": [],
-    "name": "northeast-pointing airplane",
-    "shortname": ":airplane_northeast:",
-    "category": "travel_places",
-    "aliases": [
-      ":northeast_pointing_airplane:"
+      "leaving",
+      "vacation"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "plane",
-      "travel"
-    ]
+    "moji": "🛫"
   },
   "airplane_small": {
     "unicode": "1F6E9",
     "unicode_alternates": [],
     "name": "small airplane",
     "shortname": ":airplane_small:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":small_airplane:"
     ],
@@ -261,38 +265,10 @@
       "jet",
       "jumbo",
       "boeing",
-      "airbus"
-    ]
-  },
-  "airplane_small_up": {
-    "unicode": "1F6E8",
-    "unicode_alternates": [],
-    "name": "up-pointing small airplane",
-    "shortname": ":airplane_small_up:",
-    "category": "travel_places",
-    "aliases": [
-      ":up_pointing_small_airplane:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "plane",
-      "travel"
-    ]
-  },
-  "airplane_up": {
-    "unicode": "1F6E7",
-    "unicode_alternates": [],
-    "name": "up-pointing airplane",
-    "shortname": ":airplane_up:",
-    "category": "travel_places",
-    "aliases": [
-      ":up_pointing_airplane:"
+      "airbus",
+      "vacation"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "plane",
-      "travel"
-    ]
+    "moji": "🛩"
   },
   "alarm_clock": {
     "unicode": "23F0",
@@ -304,13 +280,14 @@
     "aliases_ascii": [],
     "keywords": [
       "time",
-      "wake"
+      "wake",
+      "object"
     ],
     "moji": "⏰"
   },
   "alembic": {
     "unicode": "2697",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "alembic",
     "shortname": ":alembic:",
     "category": "objects",
@@ -319,22 +296,27 @@
     "keywords": [
       "chemistry",
       "object",
-      "tool"
-    ]
+      "tool",
+      "science"
+    ],
+    "moji": "⚗"
   },
   "alien": {
     "unicode": "1F47D",
     "unicode_alternates": [],
     "name": "extraterrestrial alien",
     "shortname": ":alien:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "UFO",
       "paul",
       "alien",
-      "ufo"
+      "ufo",
+      "space",
+      "monster",
+      "scientology"
     ],
     "moji": "👽"
   },
@@ -343,7 +325,7 @@
     "unicode_alternates": [],
     "name": "ambulance",
     "shortname": ":ambulance:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -353,19 +335,23 @@
       "emergency",
       "medical",
       "help",
-      "assistance"
+      "assistance",
+      "transportation"
     ],
     "moji": "🚑"
   },
   "amphora": {
     "unicode": "1F3FA",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "amphora",
     "shortname": ":amphora:",
     "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "object"
+    ],
+    "moji": "🏺"
   },
   "anchor": {
     "unicode": "2693",
@@ -374,21 +360,23 @@
     ],
     "name": "anchor",
     "shortname": ":anchor:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "ferry",
       "ship",
       "anchor",
-      "ship",
       "boat",
       "ocean",
       "harbor",
       "marina",
       "shipyard",
       "sailor",
-      "tattoo"
+      "tattoo",
+      "object",
+      "travel",
+      "vacation"
     ],
     "moji": "⚓"
   },
@@ -397,7 +385,7 @@
     "unicode_alternates": [],
     "name": "baby angel",
     "shortname": ":angel:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -406,16 +394,17 @@
       "halo",
       "cupid",
       "wings",
-      "halo",
       "heaven",
-      "wings",
-      "jesus"
+      "jesus",
+      "people",
+      "diversity",
+      "omg"
     ],
     "moji": "👼"
   },
   "angel_tone1": {
     "unicode": "1F47C-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby angel tone 1",
     "shortname": ":angel_tone1:",
     "category": "people",
@@ -427,11 +416,12 @@
       "heaven",
       "wings",
       "jesus"
-    ]
+    ],
+    "moji": "👼🏻"
   },
   "angel_tone2": {
     "unicode": "1F47C-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby angel tone 2",
     "shortname": ":angel_tone2:",
     "category": "people",
@@ -443,11 +433,12 @@
       "heaven",
       "wings",
       "jesus"
-    ]
+    ],
+    "moji": "👼🏼"
   },
   "angel_tone3": {
     "unicode": "1F47C-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby angel tone 3",
     "shortname": ":angel_tone3:",
     "category": "people",
@@ -459,11 +450,12 @@
       "heaven",
       "wings",
       "jesus"
-    ]
+    ],
+    "moji": "👼🏽"
   },
   "angel_tone4": {
     "unicode": "1F47C-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby angel tone 4",
     "shortname": ":angel_tone4:",
     "category": "people",
@@ -475,11 +467,12 @@
       "heaven",
       "wings",
       "jesus"
-    ]
+    ],
+    "moji": "👼🏾"
   },
   "angel_tone5": {
     "unicode": "1F47C-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby angel tone 5",
     "shortname": ":angel_tone5:",
     "category": "people",
@@ -491,50 +484,31 @@
       "heaven",
       "wings",
       "jesus"
-    ]
+    ],
+    "moji": "👼🏿"
   },
   "anger": {
     "unicode": "1F4A2",
     "unicode_alternates": [],
     "name": "anger symbol",
     "shortname": ":anger:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "anger",
       "angry",
-      "mad"
+      "mad",
+      "symbol"
     ],
     "moji": "💢"
   },
-  "anger_left": {
-    "unicode": "1F5EE",
-    "unicode_alternates": [],
-    "name": "left anger bubble",
-    "shortname": ":anger_left:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":left_anger_bubble:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "speech",
-      "balloon",
-      "talk",
-      "mood",
-      "conversation",
-      "communication",
-      "comic",
-      "angry"
-    ]
-  },
   "anger_right": {
     "unicode": "1F5EF",
     "unicode_alternates": [],
     "name": "right anger bubble",
     "shortname": ":anger_right:",
-    "category": "objects_symbols",
+    "category": "symbols",
     "aliases": [
       ":right_anger_bubble:"
     ],
@@ -547,15 +521,17 @@
       "conversation",
       "communication",
       "comic",
-      "angry"
-    ]
+      "angry",
+      "symbol"
+    ],
+    "moji": "🗯"
   },
   "angry": {
     "unicode": "1F620",
     "unicode_alternates": [],
     "name": "angry face",
     "shortname": ":angry:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ">:(",
@@ -571,7 +547,8 @@
       "annoyed",
       "face",
       "frustrated",
-      "mad"
+      "smiley",
+      "emotion"
     ],
     "moji": "😠"
   },
@@ -580,7 +557,7 @@
     "unicode_alternates": [],
     "name": "anguished face",
     "shortname": ":anguished:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -592,7 +569,11 @@
       "ouch",
       "misery",
       "distress",
-      "grief"
+      "grief",
+      "sad",
+      "smiley",
+      "surprised",
+      "emotion"
     ],
     "moji": "😧"
   },
@@ -609,8 +590,8 @@
       "insect",
       "ant",
       "queen",
-      "insect",
-      "team"
+      "team",
+      "insects"
     ],
     "moji": "🐜"
   },
@@ -619,20 +600,21 @@
     "unicode_alternates": [],
     "name": "red apple",
     "shortname": ":apple:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fruit",
       "mac",
       "apple",
-      "fruit",
       "electronics",
       "red",
       "doctor",
       "teacher",
       "school",
-      "core"
+      "core",
+      "food",
+      "creationism"
     ],
     "moji": "🍎"
   },
@@ -643,7 +625,7 @@
     ],
     "name": "aquarius",
     "shortname": ":aquarius:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -657,9 +639,8 @@
       "zodiac",
       "sign",
       "purple-square",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♒"
   },
@@ -670,7 +651,7 @@
     ],
     "name": "aries",
     "shortname": ":aries:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -683,9 +664,8 @@
       "zodiac",
       "sign",
       "purple-square",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♈"
   },
@@ -696,12 +676,14 @@
     ],
     "name": "black left-pointing triangle",
     "shortname": ":arrow_backward:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol",
+      "triangle"
     ],
     "moji": "◀"
   },
@@ -710,12 +692,13 @@
     "unicode_alternates": [],
     "name": "black down-pointing double triangle",
     "shortname": ":arrow_double_down:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "⏬"
   },
@@ -724,12 +707,13 @@
     "unicode_alternates": [],
     "name": "black up-pointing double triangle",
     "shortname": ":arrow_double_up:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "⏫"
   },
@@ -740,12 +724,13 @@
     ],
     "name": "downwards black arrow",
     "shortname": ":arrow_down:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "⬇"
   },
@@ -754,12 +739,14 @@
     "unicode_alternates": [],
     "name": "down-pointing small red triangle",
     "shortname": ":arrow_down_small:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol",
+      "triangle"
     ],
     "moji": "🔽"
   },
@@ -770,12 +757,14 @@
     ],
     "name": "black right-pointing triangle",
     "shortname": ":arrow_forward:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol",
+      "triangle"
     ],
     "moji": "▶"
   },
@@ -786,12 +775,13 @@
     ],
     "name": "arrow pointing rightwards then curving downwards",
     "shortname": ":arrow_heading_down:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "⤵"
   },
@@ -802,12 +792,13 @@
     ],
     "name": "arrow pointing rightwards then curving upwards",
     "shortname": ":arrow_heading_up:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "⤴"
   },
@@ -818,13 +809,14 @@
     ],
     "name": "leftwards black arrow",
     "shortname": ":arrow_left:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
       "blue-square",
-      "previous"
+      "previous",
+      "symbol"
     ],
     "moji": "⬅"
   },
@@ -835,12 +827,13 @@
     ],
     "name": "south west arrow",
     "shortname": ":arrow_lower_left:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "↙"
   },
@@ -851,12 +844,13 @@
     ],
     "name": "south east arrow",
     "shortname": ":arrow_lower_right:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "↘"
   },
@@ -867,12 +861,14 @@
     ],
     "name": "black rightwards arrow",
     "shortname": ":arrow_right:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "next"
+      "next",
+      "arrow",
+      "symbol"
     ],
     "moji": "➡"
   },
@@ -883,11 +879,13 @@
     ],
     "name": "rightwards arrow with hook",
     "shortname": ":arrow_right_hook:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "↪"
   },
@@ -898,11 +896,13 @@
     ],
     "name": "upwards black arrow",
     "shortname": ":arrow_up:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "⬆"
   },
@@ -913,11 +913,13 @@
     ],
     "name": "up down arrow",
     "shortname": ":arrow_up_down:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "↕"
   },
@@ -926,11 +928,14 @@
     "unicode_alternates": [],
     "name": "up-pointing small red triangle",
     "shortname": ":arrow_up_small:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol",
+      "triangle"
     ],
     "moji": "🔼"
   },
@@ -941,11 +946,13 @@
     ],
     "name": "north west arrow",
     "shortname": ":arrow_upper_left:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "↖"
   },
@@ -956,11 +963,13 @@
     ],
     "name": "north east arrow",
     "shortname": ":arrow_upper_right:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "↗"
   },
@@ -969,11 +978,13 @@
     "unicode_alternates": [],
     "name": "clockwise downwards and upwards open circle arrows",
     "shortname": ":arrows_clockwise:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "sync"
+      "sync",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔃"
   },
@@ -982,12 +993,14 @@
     "unicode_alternates": [],
     "name": "anticlockwise downwards and upwards open circle ar",
     "shortname": ":arrows_counterclockwise:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "sync"
+      "sync",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔄"
   },
@@ -996,7 +1009,7 @@
     "unicode_alternates": [],
     "name": "artist palette",
     "shortname": ":art:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1007,8 +1020,6 @@
       "palette",
       "art",
       "colors",
-      "paint",
-      "draw",
       "brush",
       "pastels",
       "oils"
@@ -1020,7 +1031,7 @@
     "unicode_alternates": [],
     "name": "articulated lorry",
     "shortname": ":articulated_lorry:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1035,24 +1046,11 @@
     ],
     "moji": "🚛"
   },
-  "ascending_notes": {
-    "unicode": "1F39C",
-    "unicode_alternates": [],
-    "name": "beamed ascending musical notes",
-    "shortname": ":ascending_notes:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "score",
-      "music",
-      "sound",
-      "tone"
-    ]
-  },
   "asterisk": {
     "unicode": "002A-20E3",
-    "unicode_alternates": "002a-fe0f-20e3",
+    "unicode_alternates": [
+      "002A-FE0F-20E3"
+    ],
     "name": "keycap asterisk",
     "shortname": ":asterisk:",
     "category": "symbols",
@@ -1064,14 +1062,15 @@
       "*",
       "star",
       "symbol"
-    ]
+    ],
+    "moji": "*⃣"
   },
   "astonished": {
     "unicode": "1F632",
     "unicode_alternates": [],
     "name": "astonished face",
     "shortname": ":astonished:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1079,7 +1078,12 @@
       "xox",
       "shocked",
       "surprise",
-      "astonished"
+      "astonished",
+      "smiley",
+      "surprised",
+      "wow",
+      "emotion",
+      "omg"
     ],
     "moji": "😲"
   },
@@ -1088,12 +1092,16 @@
     "unicode_alternates": [],
     "name": "athletic shoe",
     "shortname": ":athletic_shoe:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "shoes",
-      "sports"
+      "sports",
+      "fashion",
+      "shoe",
+      "accessories",
+      "boys night"
     ],
     "moji": "👟"
   },
@@ -1102,7 +1110,7 @@
     "unicode_alternates": [],
     "name": "automated teller machine",
     "shortname": ":atm:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1115,17 +1123,16 @@
       "bank",
       "adam",
       "payday",
-      "bank",
       "blue-square",
-      "cash",
-      "money",
-      "payment"
+      "payment",
+      "electronics",
+      "symbol"
     ],
     "moji": "🏧"
   },
   "atom": {
     "unicode": "269B",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "atom symbol",
     "shortname": ":atom:",
     "category": "symbols",
@@ -1134,21 +1141,36 @@
     ],
     "aliases_ascii": [],
     "keywords": [
-      "atheist"
-    ]
+      "atheist",
+      "symbol",
+      "science"
+    ],
+    "moji": "⚛"
+  },
+  "avocado": {
+    "unicode": "1F951",
+    "unicode_alternates": [],
+    "name": "avocado",
+    "shortname": ":avocado:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥑"
   },
   "b": {
     "unicode": "1F171",
     "unicode_alternates": [],
     "name": "negative squared latin capital letter b",
     "shortname": ":b:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "letter",
-      "red-square"
+      "red-square",
+      "symbol"
     ],
     "moji": "🅱"
   },
@@ -1157,13 +1179,16 @@
     "unicode_alternates": [],
     "name": "baby",
     "shortname": ":baby:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "boy",
       "child",
-      "infant"
+      "infant",
+      "people",
+      "baby",
+      "diversity"
     ],
     "moji": "👶"
   },
@@ -1172,7 +1197,7 @@
     "unicode_alternates": [],
     "name": "baby bottle",
     "shortname": ":baby_bottle:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1184,7 +1209,9 @@
       "mother",
       "nipple",
       "newborn",
-      "formula"
+      "formula",
+      "drink",
+      "object"
     ],
     "moji": "🍼"
   },
@@ -1202,7 +1229,6 @@
       "chick",
       "baby",
       "bird",
-      "chicken",
       "young",
       "woman",
       "cute"
@@ -1214,7 +1240,7 @@
     "unicode_alternates": [],
     "name": "baby symbol",
     "shortname": ":baby_symbol:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1226,13 +1252,14 @@
       "human",
       "diaper",
       "small",
-      "babe"
+      "babe",
+      "symbol"
     ],
     "moji": "🚼"
   },
   "baby_tone1": {
     "unicode": "1F476-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby tone 1",
     "shortname": ":baby_tone1:",
     "category": "people",
@@ -1242,11 +1269,12 @@
       "child",
       "infant",
       "toddler"
-    ]
+    ],
+    "moji": "👶🏻"
   },
   "baby_tone2": {
     "unicode": "1F476-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby tone 2",
     "shortname": ":baby_tone2:",
     "category": "people",
@@ -1256,11 +1284,12 @@
       "child",
       "infant",
       "toddler"
-    ]
+    ],
+    "moji": "👶🏼"
   },
   "baby_tone3": {
     "unicode": "1F476-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby tone 3",
     "shortname": ":baby_tone3:",
     "category": "people",
@@ -1270,11 +1299,12 @@
       "child",
       "infant",
       "toddler"
-    ]
+    ],
+    "moji": "👶🏽"
   },
   "baby_tone4": {
     "unicode": "1F476-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby tone 4",
     "shortname": ":baby_tone4:",
     "category": "people",
@@ -1284,11 +1314,12 @@
       "child",
       "infant",
       "toddler"
-    ]
+    ],
+    "moji": "👶🏾"
   },
   "baby_tone5": {
     "unicode": "1F476-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "baby tone 5",
     "shortname": ":baby_tone5:",
     "category": "people",
@@ -1298,37 +1329,57 @@
       "child",
       "infant",
       "toddler"
-    ]
+    ],
+    "moji": "👶🏿"
   },
   "back": {
     "unicode": "1F519",
     "unicode_alternates": [],
     "name": "back with leftwards arrow above",
     "shortname": ":back:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "arrow"
+      "arrow",
+      "symbol"
     ],
     "moji": "🔙"
   },
+  "bacon": {
+    "unicode": "1F953",
+    "unicode_alternates": [],
+    "name": "bacon",
+    "shortname": ":bacon:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [
+      "pig"
+    ],
+    "moji": "🥓"
+  },
   "badminton": {
     "unicode": "1F3F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "badminton racquet",
     "shortname": ":badminton:",
     "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "game",
+      "sport",
+      "badminton"
+    ],
+    "moji": "🏸"
   },
   "baggage_claim": {
     "unicode": "1F6C4",
     "unicode_alternates": [],
     "name": "baggage claim",
     "shortname": ":baggage_claim:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1338,7 +1389,8 @@
       "bag",
       "baggage",
       "luggage",
-      "travel"
+      "travel",
+      "symbol"
     ],
     "moji": "🛄"
   },
@@ -1355,11 +1407,13 @@
       "party",
       "balloon",
       "birthday",
-      "celebration",
       "helium",
       "gas",
       "children",
-      "float"
+      "float",
+      "object",
+      "good",
+      "parties"
     ],
     "moji": "🎈"
   },
@@ -1368,29 +1422,17 @@
     "unicode_alternates": [],
     "name": "ballot box with ballot",
     "shortname": ":ballot_box:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":ballot_box_with_ballot:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "vote"
-    ]
-  },
-  "ballot_box_check": {
-    "unicode": "1F5F9",
-    "unicode_alternates": [],
-    "name": "ballot box with bold check",
-    "shortname": ":ballot_box_check:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":ballot_box_with_bold_check:"
+      "vote",
+      "object",
+      "office"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "mark",
-      "vote"
-    ]
+    "moji": "🗳"
   },
   "ballot_box_with_check": {
     "unicode": "2611",
@@ -1399,51 +1441,22 @@
     ],
     "name": "ballot box with check",
     "shortname": ":ballot_box_with_check:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "agree",
-      "ok"
+      "ok",
+      "symbol"
     ],
     "moji": "☑"
   },
-  "ballot_box_x": {
-    "unicode": "1F5F5",
-    "unicode_alternates": [],
-    "name": "ballot box with script x",
-    "shortname": ":ballot_box_x:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":ballot_box_with_script_x:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "mark",
-      "vote"
-    ]
-  },
-  "ballot_x": {
-    "unicode": "1F5F4",
-    "unicode_alternates": [],
-    "name": "ballot script x",
-    "shortname": ":ballot_x:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":ballot_script_x:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "mark",
-      "vote"
-    ]
-  },
   "bamboo": {
     "unicode": "1F38D",
     "unicode_alternates": [],
     "name": "pine decoration",
     "shortname": ":bamboo:",
-    "category": "objects",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1472,7 +1485,7 @@
     "unicode_alternates": [],
     "name": "banana",
     "shortname": ":banana:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1480,7 +1493,8 @@
       "fruit",
       "banana",
       "peel",
-      "bunch"
+      "bunch",
+      "penis"
     ],
     "moji": "🍌"
   },
@@ -1491,12 +1505,14 @@
     ],
     "name": "double exclamation mark",
     "shortname": ":bangbang:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "exclamation",
-      "surprise"
+      "surprise",
+      "symbol",
+      "punctuation"
     ],
     "moji": "‼"
   },
@@ -1505,11 +1521,12 @@
     "unicode_alternates": [],
     "name": "bank",
     "shortname": ":bank:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "building"
+      "building",
+      "places"
     ],
     "moji": "🏦"
   },
@@ -1524,7 +1541,9 @@
     "keywords": [
       "graph",
       "presentation",
-      "stats"
+      "stats",
+      "work",
+      "office"
     ],
     "moji": "📊"
   },
@@ -1533,13 +1552,14 @@
     "unicode_alternates": [],
     "name": "barber pole",
     "shortname": ":barber:",
-    "category": "places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "hair",
       "salon",
-      "style"
+      "style",
+      "object"
     ],
     "moji": "💈"
   },
@@ -1550,13 +1570,17 @@
     ],
     "name": "baseball",
     "shortname": ":baseball:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "MLB",
       "balls",
-      "sports"
+      "sports",
+      "game",
+      "ball",
+      "sport",
+      "baseball"
     ],
     "moji": "⚾"
   },
@@ -1565,7 +1589,7 @@
     "unicode_alternates": [],
     "name": "basketball and hoop",
     "shortname": ":basketball:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1578,13 +1602,16 @@
       "hoop",
       "net",
       "swish",
-      "rip city"
+      "rip city",
+      "game",
+      "ball",
+      "sport"
     ],
     "moji": "🏀"
   },
   "basketball_player": {
     "unicode": "26F9",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball",
     "shortname": ":basketball_player:",
     "category": "activity",
@@ -1594,12 +1621,18 @@
     "aliases_ascii": [],
     "keywords": [
       "sport",
-      "travel"
-    ]
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball",
+      "diversity"
+    ],
+    "moji": "⛹"
   },
   "basketball_player_tone1": {
     "unicode": "26F9-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball tone 1",
     "shortname": ":basketball_player_tone1:",
     "category": "activity",
@@ -1607,11 +1640,19 @@
       ":person_with_ball_tone1:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "sport",
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball"
+    ],
+    "moji": "⛹🏻"
   },
   "basketball_player_tone2": {
     "unicode": "26F9-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball tone 2",
     "shortname": ":basketball_player_tone2:",
     "category": "activity",
@@ -1619,11 +1660,19 @@
       ":person_with_ball_tone2:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "sport",
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball"
+    ],
+    "moji": "⛹🏼"
   },
   "basketball_player_tone3": {
     "unicode": "26F9-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball tone 3",
     "shortname": ":basketball_player_tone3:",
     "category": "activity",
@@ -1631,11 +1680,19 @@
       ":person_with_ball_tone3:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "sport",
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball"
+    ],
+    "moji": "⛹🏽"
   },
   "basketball_player_tone4": {
     "unicode": "26F9-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball tone 4",
     "shortname": ":basketball_player_tone4:",
     "category": "activity",
@@ -1643,11 +1700,19 @@
       ":person_with_ball_tone4:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "sport",
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball"
+    ],
+    "moji": "⛹🏾"
   },
   "basketball_player_tone5": {
     "unicode": "26F9-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with ball tone 5",
     "shortname": ":basketball_player_tone5:",
     "category": "activity",
@@ -1655,14 +1720,33 @@
       ":person_with_ball_tone5:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "sport",
+      "travel",
+      "men",
+      "game",
+      "ball",
+      "basketball"
+    ],
+    "moji": "⛹🏿"
+  },
+  "bat": {
+    "unicode": "1F987",
+    "unicode_alternates": [],
+    "name": "bat",
+    "shortname": ":bat:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦇"
   },
   "bath": {
     "unicode": "1F6C0",
     "unicode_alternates": [],
     "name": "bath",
     "shortname": ":bath:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1677,16 +1761,17 @@
       "bathroom",
       "soap",
       "water",
-      "clean",
       "shampoo",
       "lather",
-      "water"
+      "tired",
+      "diversity",
+      "steam"
     ],
     "moji": "🛀"
   },
   "bath_tone1": {
     "unicode": "1F6C0-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bath tone 1",
     "shortname": ":bath_tone1:",
     "category": "activity",
@@ -1705,11 +1790,12 @@
       "clean",
       "shampoo",
       "lather"
-    ]
+    ],
+    "moji": "🛀🏻"
   },
   "bath_tone2": {
     "unicode": "1F6C0-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bath tone 2",
     "shortname": ":bath_tone2:",
     "category": "activity",
@@ -1728,11 +1814,12 @@
       "clean",
       "shampoo",
       "lather"
-    ]
+    ],
+    "moji": "🛀🏼"
   },
   "bath_tone3": {
     "unicode": "1F6C0-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bath tone 3",
     "shortname": ":bath_tone3:",
     "category": "activity",
@@ -1751,11 +1838,12 @@
       "clean",
       "shampoo",
       "lather"
-    ]
+    ],
+    "moji": "🛀🏽"
   },
   "bath_tone4": {
     "unicode": "1F6C0-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bath tone 4",
     "shortname": ":bath_tone4:",
     "category": "activity",
@@ -1774,11 +1862,12 @@
       "clean",
       "shampoo",
       "lather"
-    ]
+    ],
+    "moji": "🛀🏾"
   },
   "bath_tone5": {
     "unicode": "1F6C0-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bath tone 5",
     "shortname": ":bath_tone5:",
     "category": "activity",
@@ -1797,7 +1886,8 @@
       "clean",
       "shampoo",
       "lather"
-    ]
+    ],
+    "moji": "🛀🏿"
   },
   "bathtub": {
     "unicode": "1F6C1",
@@ -1819,10 +1909,11 @@
       "bathroom",
       "soap",
       "water",
-      "clean",
       "shampoo",
       "lather",
-      "water"
+      "object",
+      "tired",
+      "steam"
     ],
     "moji": "🛁"
   },
@@ -1837,7 +1928,8 @@
     "keywords": [
       "energy",
       "power",
-      "sustain"
+      "sustain",
+      "object"
     ],
     "moji": "🔋"
   },
@@ -1846,7 +1938,7 @@
     "unicode_alternates": [],
     "name": "beach with umbrella",
     "shortname": ":beach:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":beach_with_umbrella:"
     ],
@@ -1859,12 +1951,18 @@
       "relaxation",
       "tanning",
       "tan",
-      "swimming"
-    ]
+      "swimming",
+      "places",
+      "travel",
+      "tropical",
+      "beach",
+      "swim"
+    ],
+    "moji": "🏖"
   },
   "beach_umbrella": {
     "unicode": "26F1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "umbrella on ground",
     "shortname": ":beach_umbrella:",
     "category": "objects",
@@ -1877,8 +1975,11 @@
       "rain",
       "sun",
       "travel",
-      "weather"
-    ]
+      "weather",
+      "vacation",
+      "tropical"
+    ],
+    "moji": "⛱"
   },
   "bear": {
     "unicode": "1F43B",
@@ -1890,7 +1991,9 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife",
+      "roar"
     ],
     "moji": "🐻"
   },
@@ -1899,7 +2002,7 @@
     "unicode_alternates": [],
     "name": "bed",
     "shortname": ":bed:",
-    "category": "travel_places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1909,8 +2012,11 @@
       "full",
       "twin",
       "king",
-      "mattress"
-    ]
+      "mattress",
+      "object",
+      "tired"
+    ],
+    "moji": "🛏"
   },
   "bee": {
     "unicode": "1F41D",
@@ -1932,7 +2038,8 @@
       "honey",
       "hive",
       "bumble",
-      "pollination"
+      "pollination",
+      "insects"
     ],
     "moji": "🐝"
   },
@@ -1941,7 +2048,7 @@
     "unicode_alternates": [],
     "name": "beer mug",
     "shortname": ":beer:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1962,7 +2069,9 @@
       "brewery",
       "micro",
       "pint",
-      "boot"
+      "boot",
+      "alcohol",
+      "parties"
     ],
     "moji": "🍺"
   },
@@ -1971,7 +2080,7 @@
     "unicode_alternates": [],
     "name": "clinking beer mugs",
     "shortname": ":beers:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -1987,11 +2096,14 @@
       "mug",
       "toast",
       "celebrate",
-      "pub",
       "bar",
       "jolly",
       "hops",
-      "clink"
+      "clink",
+      "alcohol",
+      "thank you",
+      "boys night",
+      "parties"
     ],
     "moji": "🍻"
   },
@@ -2013,8 +2125,9 @@
       "beetle",
       "cow",
       "lady cow",
-      "insect",
-      "endearment"
+      "endearment",
+      "insects",
+      "animal"
     ],
     "moji": "🐞"
   },
@@ -2023,12 +2136,13 @@
     "unicode_alternates": [],
     "name": "japanese symbol for beginner",
     "shortname": ":beginner:",
-    "category": "places",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "badge",
-      "shield"
+      "shield",
+      "symbol"
     ],
     "moji": "🔰"
   },
@@ -2037,7 +2151,7 @@
     "unicode_alternates": [],
     "name": "bell",
     "shortname": ":bell:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2045,7 +2159,10 @@
       "christmas",
       "notification",
       "sound",
-      "xmas"
+      "xmas",
+      "object",
+      "alarm",
+      "symbol"
     ],
     "moji": "🔔"
   },
@@ -2054,7 +2171,7 @@
     "unicode_alternates": [],
     "name": "bellhop bell",
     "shortname": ":bellhop:",
-    "category": "travel_places",
+    "category": "objects",
     "aliases": [
       ":bellhop_bell:"
     ],
@@ -2062,15 +2179,17 @@
     "keywords": [
       "hotel",
       "porter",
-      "ding"
-    ]
+      "ding",
+      "object"
+    ],
+    "moji": "🛎"
   },
   "bento": {
     "unicode": "1F371",
     "unicode_alternates": [],
     "name": "bento box",
     "shortname": ":bento:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2078,13 +2197,14 @@
       "food",
       "japanese",
       "bento",
-      "japanese",
       "rice",
       "meal",
-      "box",
       "obento",
       "convenient",
-      "lunchbox"
+      "lunchbox",
+      "object",
+      "sushi",
+      "japan"
     ],
     "moji": "🍱"
   },
@@ -2093,7 +2213,7 @@
     "unicode_alternates": [],
     "name": "bicyclist",
     "shortname": ":bicyclist:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2103,16 +2223,19 @@
       "sports",
       "bicyclist",
       "road",
-      "bike",
       "pedal",
       "bicycle",
-      "transportation"
+      "transportation",
+      "men",
+      "workout",
+      "sport",
+      "diversity"
     ],
     "moji": "🚴"
   },
   "bicyclist_tone1": {
     "unicode": "1F6B4-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bicyclist tone 1",
     "shortname": ":bicyclist_tone1:",
     "category": "activity",
@@ -2127,11 +2250,12 @@
       "pedal",
       "bicycle",
       "transportation"
-    ]
+    ],
+    "moji": "🚴🏻"
   },
   "bicyclist_tone2": {
     "unicode": "1F6B4-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bicyclist tone 2",
     "shortname": ":bicyclist_tone2:",
     "category": "activity",
@@ -2146,11 +2270,12 @@
       "pedal",
       "bicycle",
       "transportation"
-    ]
+    ],
+    "moji": "🚴🏼"
   },
   "bicyclist_tone3": {
     "unicode": "1F6B4-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bicyclist tone 3",
     "shortname": ":bicyclist_tone3:",
     "category": "activity",
@@ -2165,11 +2290,12 @@
       "pedal",
       "bicycle",
       "transportation"
-    ]
+    ],
+    "moji": "🚴🏽"
   },
   "bicyclist_tone4": {
     "unicode": "1F6B4-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bicyclist tone 4",
     "shortname": ":bicyclist_tone4:",
     "category": "activity",
@@ -2184,11 +2310,12 @@
       "pedal",
       "bicycle",
       "transportation"
-    ]
+    ],
+    "moji": "🚴🏾"
   },
   "bicyclist_tone5": {
     "unicode": "1F6B4-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bicyclist tone 5",
     "shortname": ":bicyclist_tone5:",
     "category": "activity",
@@ -2203,14 +2330,15 @@
       "pedal",
       "bicycle",
       "transportation"
-    ]
+    ],
+    "moji": "🚴🏿"
   },
   "bike": {
     "unicode": "1F6B2",
     "unicode_alternates": [],
     "name": "bicycle",
     "shortname": ":bike:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2220,8 +2348,8 @@
       "sports",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
+      "transportation",
+      "travel"
     ],
     "moji": "🚲"
   },
@@ -2230,7 +2358,7 @@
     "unicode_alternates": [],
     "name": "bikini",
     "shortname": ":bikini:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2239,13 +2367,18 @@
       "female",
       "girl",
       "swimming",
-      "woman"
+      "woman",
+      "women",
+      "sexy",
+      "vacation",
+      "tropical",
+      "swim"
     ],
     "moji": "👙"
   },
   "biohazard": {
     "unicode": "2623",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "biohazard sign",
     "shortname": ":biohazard:",
     "category": "symbols",
@@ -2254,8 +2387,10 @@
     ],
     "aliases_ascii": [],
     "keywords": [
-      "symbol"
-    ]
+      "symbol",
+      "science"
+    ],
+    "moji": "☣"
   },
   "bird": {
     "unicode": "1F426",
@@ -2269,7 +2404,8 @@
       "animal",
       "fly",
       "nature",
-      "tweet"
+      "tweet",
+      "wildlife"
     ],
     "moji": "🐦"
   },
@@ -2278,7 +2414,7 @@
     "unicode_alternates": [],
     "name": "birthday cake",
     "shortname": ":birthday:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2286,10 +2422,11 @@
       "party",
       "birthday",
       "birth",
-      "cake",
       "dessert",
       "wish",
-      "celebrate"
+      "celebrate",
+      "food",
+      "parties"
     ],
     "moji": "🎂"
   },
@@ -2300,26 +2437,42 @@
     ],
     "name": "medium black circle",
     "shortname": ":black_circle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "circle"
     ],
     "moji": "⚫"
   },
+  "black_heart": {
+    "unicode": "1F5A4",
+    "unicode_alternates": [],
+    "name": "black heart",
+    "shortname": ":black_heart:",
+    "category": "symbols",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🖤"
+  },
   "black_joker": {
     "unicode": "1F0CF",
     "unicode_alternates": [],
     "name": "playing card black joker",
     "shortname": ":black_joker:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cards",
       "game",
-      "poker"
+      "poker",
+      "object",
+      "symbol"
     ],
     "moji": "🃏"
   },
@@ -2330,11 +2483,14 @@
     ],
     "name": "black large square",
     "shortname": ":black_large_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "⬛"
   },
@@ -2345,10 +2501,14 @@
     ],
     "name": "black medium small square",
     "shortname": ":black_medium_small_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "shapes",
+      "symbol",
+      "square"
+    ],
     "moji": "◾"
   },
   "black_medium_square": {
@@ -2358,11 +2518,14 @@
     ],
     "name": "black medium square",
     "shortname": ":black_medium_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "◼"
   },
@@ -2378,7 +2541,10 @@
     "aliases_ascii": [],
     "keywords": [
       "pen",
-      "stationery"
+      "stationery",
+      "object",
+      "office",
+      "write"
     ],
     "moji": "✒"
   },
@@ -2389,10 +2555,14 @@
     ],
     "name": "black small square",
     "shortname": ":black_small_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "shapes",
+      "symbol",
+      "square"
+    ],
     "moji": "▪"
   },
   "black_square_button": {
@@ -2400,11 +2570,14 @@
     "unicode_alternates": [],
     "name": "black square button",
     "shortname": ":black_square_button:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "frame"
+      "frame",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "🔲"
   },
@@ -2422,7 +2595,8 @@
       "yellow",
       "blossom",
       "daisy",
-      "flower"
+      "flower",
+      "plant"
     ],
     "moji": "🌼"
   },
@@ -2445,7 +2619,9 @@
       "ballonfish",
       "toadfish",
       "fugu fish",
-      "sushi"
+      "sushi",
+      "wildlife",
+      "animal"
     ],
     "moji": "🐡"
   },
@@ -2460,7 +2636,11 @@
     "keywords": [
       "knowledge",
       "library",
-      "read"
+      "read",
+      "object",
+      "office",
+      "write",
+      "book"
     ],
     "moji": "📘"
   },
@@ -2469,15 +2649,16 @@
     "unicode_alternates": [],
     "name": "recreational vehicle",
     "shortname": ":blue_car:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "car",
       "suv",
-      "car",
       "wagon",
-      "automobile"
+      "automobile",
+      "transportation",
+      "travel"
     ],
     "moji": "🚙"
   },
@@ -2486,7 +2667,7 @@
     "unicode_alternates": [],
     "name": "blue heart",
     "shortname": ":blue_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2496,11 +2677,11 @@
       "valentines",
       "blue",
       "heart",
-      "love",
       "stability",
       "truth",
       "loyalty",
-      "trust"
+      "trust",
+      "symbol"
     ],
     "moji": "💙"
   },
@@ -2509,7 +2690,7 @@
     "unicode_alternates": [],
     "name": "smiling face with smiling eyes",
     "shortname": ":blush:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2521,8 +2702,10 @@
       "shy",
       "smile",
       "smiling",
-      "smile",
-      "smiley"
+      "smiley",
+      "emotion",
+      "good",
+      "beautiful"
     ],
     "moji": "😊"
   },
@@ -2536,7 +2719,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐗"
   },
@@ -2550,7 +2734,11 @@
     "aliases_ascii": [],
     "keywords": [
       "boom",
-      "explode"
+      "explode",
+      "object",
+      "weapon",
+      "dead",
+      "blast"
     ],
     "moji": "💣"
   },
@@ -2564,26 +2752,14 @@
     "aliases_ascii": [],
     "keywords": [
       "library",
-      "literature"
+      "literature",
+      "object",
+      "office",
+      "write",
+      "book"
     ],
     "moji": "📖"
   },
-  "book2": {
-    "unicode": "1F56E",
-    "unicode_alternates": [],
-    "name": "book",
-    "shortname": ":book2:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "library",
-      "literature",
-      "novel",
-      "reading",
-      "story"
-    ]
-  },
   "bookmark": {
     "unicode": "1F516",
     "unicode_alternates": [],
@@ -2593,7 +2769,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "favorite"
+      "favorite",
+      "object",
+      "book"
     ],
     "moji": "🔖"
   },
@@ -2606,7 +2784,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "favorite"
+      "favorite",
+      "office",
+      "write"
     ],
     "moji": "📑"
   },
@@ -2620,7 +2800,11 @@
     "aliases_ascii": [],
     "keywords": [
       "library",
-      "literature"
+      "literature",
+      "object",
+      "office",
+      "write",
+      "book"
     ],
     "moji": "📚"
   },
@@ -2629,7 +2813,7 @@
     "unicode_alternates": [],
     "name": "collision symbol",
     "shortname": ":boom:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2642,7 +2826,9 @@
       "fire",
       "emphasis",
       "wow",
-      "bam"
+      "bam",
+      "symbol",
+      "blast"
     ],
     "moji": "💥"
   },
@@ -2651,12 +2837,16 @@
     "unicode_alternates": [],
     "name": "womans boots",
     "shortname": ":boot:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fashion",
-      "shoes"
+      "shoes",
+      "women",
+      "shoe",
+      "sexy",
+      "accessories"
     ],
     "moji": "👢"
   },
@@ -2670,33 +2860,20 @@
     "aliases_ascii": [],
     "keywords": [
       "flowers",
-      "nature"
+      "nature",
+      "flower",
+      "plant",
+      "rip",
+      "condolence"
     ],
     "moji": "💐"
   },
-  "bouquet2": {
-    "unicode": "1F395",
-    "unicode_alternates": [],
-    "name": "bouquet of flowers",
-    "shortname": ":bouquet2:",
-    "category": "celebration",
-    "aliases": [
-      ":bouquet_of_flowers:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "nature",
-      "marriage",
-      "wedding",
-      "bride"
-    ]
-  },
   "bow": {
     "unicode": "1F647",
     "unicode_alternates": [],
     "name": "person bowing deeply",
     "shortname": ":bow:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2707,13 +2884,16 @@
       "bow",
       "respect",
       "curtsy",
-      "bend"
+      "bend",
+      "people",
+      "pray",
+      "diversity"
     ],
     "moji": "🙇"
   },
   "bow_and_arrow": {
     "unicode": "1F3F9",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bow and arrow",
     "shortname": ":bow_and_arrow:",
     "category": "activity",
@@ -2721,11 +2901,15 @@
       ":archery:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "weapon",
+      "sport"
+    ],
+    "moji": "🏹"
   },
   "bow_tone1": {
     "unicode": "1F647-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person bowing deeply tone 1",
     "shortname": ":bow_tone1:",
     "category": "people",
@@ -2739,11 +2923,12 @@
       "bow",
       "respect",
       "bend"
-    ]
+    ],
+    "moji": "🙇🏻"
   },
   "bow_tone2": {
     "unicode": "1F647-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person bowing deeply tone 2",
     "shortname": ":bow_tone2:",
     "category": "people",
@@ -2757,11 +2942,12 @@
       "bow",
       "respect",
       "bend"
-    ]
+    ],
+    "moji": "🙇🏼"
   },
   "bow_tone3": {
     "unicode": "1F647-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person bowing deeply tone 3",
     "shortname": ":bow_tone3:",
     "category": "people",
@@ -2775,11 +2961,12 @@
       "bow",
       "respect",
       "bend"
-    ]
+    ],
+    "moji": "🙇🏽"
   },
   "bow_tone4": {
     "unicode": "1F647-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person bowing deeply tone 4",
     "shortname": ":bow_tone4:",
     "category": "people",
@@ -2793,11 +2980,12 @@
       "bow",
       "respect",
       "bend"
-    ]
+    ],
+    "moji": "🙇🏾"
   },
   "bow_tone5": {
     "unicode": "1F647-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person bowing deeply tone 5",
     "shortname": ":bow_tone5:",
     "category": "people",
@@ -2811,14 +2999,15 @@
       "bow",
       "respect",
       "bend"
-    ]
+    ],
+    "moji": "🙇🏿"
   },
   "bowling": {
     "unicode": "1F3B3",
     "unicode_alternates": [],
     "name": "bowling",
     "shortname": ":bowling:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2831,28 +3020,46 @@
       "pin",
       "strike",
       "spare",
-      "game"
+      "game",
+      "sport",
+      "boys night"
     ],
     "moji": "🎳"
   },
+  "boxing_glove": {
+    "unicode": "1F94A",
+    "unicode_alternates": [],
+    "name": "boxing glove",
+    "shortname": ":boxing_glove:",
+    "category": "activity",
+    "aliases": [
+      ":boxing_gloves:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥊"
+  },
   "boy": {
     "unicode": "1F466",
     "unicode_alternates": [],
     "name": "boy",
     "shortname": ":boy:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "guy",
       "male",
-      "man"
+      "man",
+      "people",
+      "baby",
+      "diversity"
     ],
     "moji": "👦"
   },
   "boy_tone1": {
     "unicode": "1F466-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "boy tone 1",
     "shortname": ":boy_tone1:",
     "category": "people",
@@ -2862,11 +3069,12 @@
       "male",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👦🏻"
   },
   "boy_tone2": {
     "unicode": "1F466-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "boy tone 2",
     "shortname": ":boy_tone2:",
     "category": "people",
@@ -2876,11 +3084,12 @@
       "male",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👦🏼"
   },
   "boy_tone3": {
     "unicode": "1F466-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "boy tone 3",
     "shortname": ":boy_tone3:",
     "category": "people",
@@ -2890,11 +3099,12 @@
       "male",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👦🏽"
   },
   "boy_tone4": {
     "unicode": "1F466-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "boy tone 4",
     "shortname": ":boy_tone4:",
     "category": "people",
@@ -2904,11 +3114,12 @@
       "male",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👦🏾"
   },
   "boy_tone5": {
     "unicode": "1F466-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "boy tone 5",
     "shortname": ":boy_tone5:",
     "category": "people",
@@ -2918,27 +3129,15 @@
       "male",
       "kid",
       "child"
-    ]
-  },
-  "boys_symbol": {
-    "unicode": "1F6C9",
-    "unicode_alternates": [],
-    "name": "boys symbol",
-    "shortname": ":boys_symbol:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "male",
-      "child"
-    ]
+    ],
+    "moji": "👦🏿"
   },
   "bread": {
     "unicode": "1F35E",
     "unicode_alternates": [],
     "name": "bread",
     "shortname": ":bread:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2957,7 +3156,7 @@
     "unicode_alternates": [],
     "name": "bride with veil",
     "shortname": ":bride_with_veil:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -2965,19 +3164,21 @@
       "marriage",
       "wedding",
       "bride",
-      "wedding",
       "planning",
       "veil",
       "gown",
       "dress",
       "engagement",
-      "white"
+      "white",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "👰"
   },
   "bride_with_veil_tone1": {
     "unicode": "1F470-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bride with veil tone 1",
     "shortname": ":bride_with_veil_tone1:",
     "category": "people",
@@ -2987,17 +3188,17 @@
       "couple",
       "marriage",
       "wedding",
-      "wedding",
       "planning",
       "gown",
       "dress",
       "engagement",
       "white"
-    ]
+    ],
+    "moji": "👰🏻"
   },
   "bride_with_veil_tone2": {
     "unicode": "1F470-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bride with veil tone 2",
     "shortname": ":bride_with_veil_tone2:",
     "category": "people",
@@ -3007,17 +3208,17 @@
       "couple",
       "marriage",
       "wedding",
-      "wedding",
       "planning",
       "gown",
       "dress",
       "engagement",
       "white"
-    ]
+    ],
+    "moji": "👰🏼"
   },
   "bride_with_veil_tone3": {
     "unicode": "1F470-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bride with veil tone 3",
     "shortname": ":bride_with_veil_tone3:",
     "category": "people",
@@ -3027,17 +3228,17 @@
       "couple",
       "marriage",
       "wedding",
-      "wedding",
       "planning",
       "gown",
       "dress",
       "engagement",
       "white"
-    ]
+    ],
+    "moji": "👰🏽"
   },
   "bride_with_veil_tone4": {
     "unicode": "1F470-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bride with veil tone 4",
     "shortname": ":bride_with_veil_tone4:",
     "category": "people",
@@ -3047,17 +3248,17 @@
       "couple",
       "marriage",
       "wedding",
-      "wedding",
       "planning",
       "gown",
       "dress",
       "engagement",
       "white"
-    ]
+    ],
+    "moji": "👰🏾"
   },
   "bride_with_veil_tone5": {
     "unicode": "1F470-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bride with veil tone 5",
     "shortname": ":bride_with_veil_tone5:",
     "category": "people",
@@ -3067,20 +3268,20 @@
       "couple",
       "marriage",
       "wedding",
-      "wedding",
       "planning",
       "gown",
       "dress",
       "engagement",
       "white"
-    ]
+    ],
+    "moji": "👰🏿"
   },
   "bridge_at_night": {
     "unicode": "1F309",
     "unicode_alternates": [],
     "name": "bridge at night",
     "shortname": ":bridge_at_night:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3093,7 +3294,11 @@
       "evening",
       "suspension",
       "golden",
-      "gate"
+      "gate",
+      "places",
+      "travel",
+      "vacation",
+      "goodnight"
     ],
     "moji": "🌉"
   },
@@ -3102,13 +3307,17 @@
     "unicode_alternates": [],
     "name": "briefcase",
     "shortname": ":briefcase:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "business",
       "documents",
-      "work"
+      "work",
+      "bag",
+      "accessories",
+      "nutcase",
+      "job"
     ],
     "moji": "💼"
   },
@@ -3117,14 +3326,17 @@
     "unicode_alternates": [],
     "name": "broken heart",
     "shortname": ":broken_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [
       "</3"
     ],
     "keywords": [
       "sad",
-      "sorry"
+      "sorry",
+      "love",
+      "symbol",
+      "heartbreak"
     ],
     "moji": "💔"
   },
@@ -3140,9 +3352,10 @@
       "insect",
       "nature",
       "bug",
-      "insect",
       "virus",
-      "error"
+      "error",
+      "insects",
+      "animal"
     ],
     "moji": "🐛"
   },
@@ -3159,7 +3372,8 @@
       "light",
       "idea",
       "bulb",
-      "light"
+      "object",
+      "science"
     ],
     "moji": "💡"
   },
@@ -3168,14 +3382,15 @@
     "unicode_alternates": [],
     "name": "high-speed train with bullet nose",
     "shortname": ":bullettrain_front:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "transportation",
       "train",
       "bullet",
-      "rail"
+      "rail",
+      "travel"
     ],
     "moji": "🚅"
   },
@@ -3184,7 +3399,7 @@
     "unicode_alternates": [],
     "name": "high-speed train",
     "shortname": ":bullettrain_side:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3192,58 +3407,31 @@
       "vehicle",
       "train",
       "bullet",
-      "rail"
+      "rail",
+      "travel"
     ],
     "moji": "🚄"
   },
-  "bullhorn": {
-    "unicode": "1F56B",
+  "burrito": {
+    "unicode": "1F32F",
     "unicode_alternates": [],
-    "name": "bullhorn",
-    "shortname": ":bullhorn:",
-    "category": "objects_symbols",
+    "name": "burrito",
+    "shortname": ":burrito:",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "sound",
-      "noise",
-      "announcement",
-      "megaphone"
-    ]
+      "food",
+      "mexican"
+    ],
+    "moji": "🌯"
   },
-  "bullhorn_waves": {
-    "unicode": "1F56C",
-    "unicode_alternates": [],
-    "name": "bullhorn with sound waves",
-    "shortname": ":bullhorn_waves:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":bullhorn_with_sound_waves:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "sound",
-      "noise",
-      "announcement",
-      "megaphone"
-    ]
-  },
-  "burrito": {
-    "unicode": "1F32F",
-    "unicode_alternates": "",
-    "name": "burrito",
-    "shortname": ":burrito:",
-    "category": "foods",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": []
-  },
-  "bus": {
-    "unicode": "1F68C",
+  "bus": {
+    "unicode": "1F68C",
     "unicode_alternates": [],
     "name": "bus",
     "shortname": ":bus:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3253,8 +3441,8 @@
       "bus",
       "school",
       "city",
-      "transportation",
-      "public"
+      "public",
+      "office"
     ],
     "moji": "🚌"
   },
@@ -3263,7 +3451,7 @@
     "unicode_alternates": [],
     "name": "bus stop",
     "shortname": ":busstop:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3272,7 +3460,7 @@
       "stop",
       "city",
       "transport",
-      "transportation"
+      "object"
     ],
     "moji": "🚏"
   },
@@ -3281,7 +3469,7 @@
     "unicode_alternates": [],
     "name": "bust in silhouette",
     "shortname": ":bust_in_silhouette:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3290,8 +3478,6 @@
       "person",
       "user",
       "silhouette",
-      "person",
-      "user",
       "member",
       "account",
       "guest",
@@ -3300,7 +3486,8 @@
       "profile",
       "me",
       "myself",
-      "i"
+      "i",
+      "people"
     ],
     "moji": "👤"
   },
@@ -3309,7 +3496,7 @@
     "unicode_alternates": [],
     "name": "busts in silhouette",
     "shortname": ":busts_in_silhouette:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3322,7 +3509,6 @@
       "silhouette",
       "silhouettes",
       "people",
-      "user",
       "members",
       "accounts",
       "relationship",
@@ -3330,6 +3516,17 @@
     ],
     "moji": "👥"
   },
+  "butterfly": {
+    "unicode": "1F98B",
+    "unicode_alternates": [],
+    "name": "butterfly",
+    "shortname": ":butterfly:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦋"
+  },
   "cactus": {
     "unicode": "1F335",
     "unicode_alternates": [],
@@ -3346,7 +3543,8 @@
       "desert",
       "drought",
       "spike",
-      "poke"
+      "poke",
+      "trees"
     ],
     "moji": "🌵"
   },
@@ -3355,7 +3553,7 @@
     "unicode_alternates": [],
     "name": "shortcake",
     "shortname": ":cake:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3368,24 +3566,6 @@
     ],
     "moji": "🍰"
   },
-  "calculator": {
-    "unicode": "1F5A9",
-    "unicode_alternates": [],
-    "name": "pocket calculator",
-    "shortname": ":calculator:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":pocket calculator:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "add",
-      "subtract",
-      "multiple",
-      "divide",
-      "scientific"
-    ]
-  },
   "calendar": {
     "unicode": "1F4C6",
     "unicode_alternates": [],
@@ -3395,7 +3575,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "schedule"
+      "schedule",
+      "object",
+      "office"
     ],
     "moji": "📆"
   },
@@ -3404,7 +3586,7 @@
     "unicode_alternates": [],
     "name": "spiral calendar pad",
     "shortname": ":calendar_spiral:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":spiral_calendar_pad:"
     ],
@@ -3412,8 +3594,89 @@
     "keywords": [
       "schedule",
       "date",
-      "day"
-    ]
+      "day",
+      "object",
+      "office"
+    ],
+    "moji": "🗓"
+  },
+  "call_me": {
+    "unicode": "1F919",
+    "unicode_alternates": [],
+    "name": "call me hand",
+    "shortname": ":call_me:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙"
+  },
+  "call_me_tone1": {
+    "unicode": "1F919-1F3FB",
+    "unicode_alternates": [],
+    "name": "call me hand tone 1",
+    "shortname": ":call_me_tone1:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙🏻"
+  },
+  "call_me_tone2": {
+    "unicode": "1F919-1F3FC",
+    "unicode_alternates": [],
+    "name": "call me hand tone 2",
+    "shortname": ":call_me_tone2:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙🏼"
+  },
+  "call_me_tone3": {
+    "unicode": "1F919-1F3FD",
+    "unicode_alternates": [],
+    "name": "call me hand tone 3",
+    "shortname": ":call_me_tone3:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙🏽"
+  },
+  "call_me_tone4": {
+    "unicode": "1F919-1F3FE",
+    "unicode_alternates": [],
+    "name": "call me hand tone 4",
+    "shortname": ":call_me_tone4:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙🏾"
+  },
+  "call_me_tone5": {
+    "unicode": "1F919-1F3FF",
+    "unicode_alternates": [],
+    "name": "call me hand tone 5",
+    "shortname": ":call_me_tone5:",
+    "category": "people",
+    "aliases": [
+      ":call_me_hand_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤙🏿"
   },
   "calling": {
     "unicode": "1F4F2",
@@ -3425,7 +3688,10 @@
     "aliases_ascii": [],
     "keywords": [
       "incoming",
-      "iphone"
+      "iphone",
+      "electronics",
+      "phone",
+      "selfie"
     ],
     "moji": "📲"
   },
@@ -3447,11 +3713,11 @@
       "desert",
       "central asia",
       "heat",
-      "hot",
       "water",
       "hump day",
       "wednesday",
-      "sex"
+      "sex",
+      "wildlife"
     ],
     "moji": "🐫"
   },
@@ -3465,7 +3731,10 @@
     "aliases_ascii": [],
     "keywords": [
       "gadgets",
-      "photo"
+      "photo",
+      "electronics",
+      "camera",
+      "selfie"
     ],
     "moji": "📷"
   },
@@ -3474,20 +3743,23 @@
     "unicode_alternates": [],
     "name": "camera with flash",
     "shortname": ":camera_with_flash:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "photo",
-      "picture"
-    ]
+      "picture",
+      "electronics",
+      "camera"
+    ],
+    "moji": "📸"
   },
   "camping": {
     "unicode": "1F3D5",
     "unicode_alternates": [],
     "name": "camping",
     "shortname": ":camping:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3495,22 +3767,13 @@
       "nature",
       "wilderness",
       "roughing",
-      "activity"
-    ]
-  },
-  "cancellation_x": {
-    "unicode": "1F5D9",
-    "unicode_alternates": [],
-    "name": "cancellation x",
-    "shortname": ":cancellation_x:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "cancel",
-      "stop",
-      "delete"
-    ]
+      "activity",
+      "places",
+      "travel",
+      "vacation",
+      "camp"
+    ],
+    "moji": "🏕"
   },
   "cancer": {
     "unicode": "264B",
@@ -3519,7 +3782,7 @@
     ],
     "name": "cancer",
     "shortname": ":cancer:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3531,9 +3794,8 @@
       "stars",
       "zodiac",
       "sign",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♋"
   },
@@ -3542,20 +3804,22 @@
     "unicode_alternates": [],
     "name": "candle",
     "shortname": ":candle:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "light",
-      "wax"
-    ]
+      "wax",
+      "object"
+    ],
+    "moji": "🕯"
   },
   "candy": {
     "unicode": "1F36C",
     "unicode_alternates": [],
     "name": "candy",
     "shortname": ":candy:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3564,22 +3828,38 @@
       "candy",
       "sugar",
       "sweet",
-      "hard"
+      "hard",
+      "food",
+      "halloween"
     ],
     "moji": "🍬"
   },
+  "canoe": {
+    "unicode": "1F6F6",
+    "unicode_alternates": [],
+    "name": "canoe",
+    "shortname": ":canoe:",
+    "category": "travel",
+    "aliases": [
+      ":kayak:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🛶"
+  },
   "capital_abcd": {
     "unicode": "1F520",
     "unicode_alternates": [],
     "name": "input symbol for latin capital letters",
     "shortname": ":capital_abcd:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "blue-square",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🔠"
   },
@@ -3590,7 +3870,7 @@
     ],
     "name": "capricorn",
     "shortname": ":capricorn:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3603,9 +3883,8 @@
       "stars",
       "zodiac",
       "sign",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♑"
   },
@@ -3614,15 +3893,19 @@
     "unicode_alternates": [],
     "name": "card file box",
     "shortname": ":card_box:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":card_file_box:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "index",
-      "organization"
-    ]
+      "organization",
+      "object",
+      "work",
+      "office"
+    ],
+    "moji": "🗃"
   },
   "card_index": {
     "unicode": "1F4C7",
@@ -3634,7 +3917,10 @@
     "aliases_ascii": [],
     "keywords": [
       "business",
-      "stationery"
+      "stationery",
+      "object",
+      "work",
+      "office"
     ],
     "moji": "📇"
   },
@@ -3643,7 +3929,7 @@
     "unicode_alternates": [],
     "name": "carousel horse",
     "shortname": ":carousel_horse:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3651,37 +3937,106 @@
       "horse",
       "photo",
       "carousel",
-      "horse",
       "amusement",
       "park",
       "ride",
       "entertainment",
-      "park",
-      "fair"
+      "fair",
+      "places",
+      "object",
+      "vacation",
+      "roller coaster"
     ],
     "moji": "🎠"
   },
-  "cartridge": {
-    "unicode": "1F5AD",
+  "carrot": {
+    "unicode": "1F955",
     "unicode_alternates": [],
-    "name": "tape cartridge",
-    "shortname": ":cartridge:",
-    "category": "objects_symbols",
+    "name": "carrot",
+    "shortname": ":carrot:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥕"
+  },
+  "cartwheel": {
+    "unicode": "1F938",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel",
+    "shortname": ":cartwheel:",
+    "category": "activity",
     "aliases": [
-      ":tape_cartridge:"
+      ":person_doing_cartwheel:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "oldschool",
-      "save",
-      "technology",
-      "disk",
-      "storage",
-      "information",
-      "computer",
-      "drive",
-      "megabyte"
-    ]
+    "keywords": [],
+    "moji": "🤸"
+  },
+  "cartwheel_tone1": {
+    "unicode": "1F938-1F3FB",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel tone 1",
+    "shortname": ":cartwheel_tone1:",
+    "category": "activity",
+    "aliases": [
+      ":person_doing_cartwheel_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤸🏻"
+  },
+  "cartwheel_tone2": {
+    "unicode": "1F938-1F3FC",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel tone 2",
+    "shortname": ":cartwheel_tone2:",
+    "category": "activity",
+    "aliases": [
+      ":person_doing_cartwheel_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤸🏼"
+  },
+  "cartwheel_tone3": {
+    "unicode": "1F938-1F3FD",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel tone 3",
+    "shortname": ":cartwheel_tone3:",
+    "category": "activity",
+    "aliases": [
+      ":person_doing_cartwheel_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤸🏽"
+  },
+  "cartwheel_tone4": {
+    "unicode": "1F938-1F3FE",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel tone 4",
+    "shortname": ":cartwheel_tone4:",
+    "category": "activity",
+    "aliases": [
+      ":person_doing_cartwheel_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤸🏾,"
+  },
+  "cartwheel_tone5": {
+    "unicode": "1F938-1F3FF",
+    "unicode_alternates": [],
+    "name": "person doing cartwheel tone 5",
+    "shortname": ":cartwheel_tone5:",
+    "category": "activity",
+    "aliases": [
+      ":person_doing_cartwheel_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤸🏿"
   },
   "cat": {
     "unicode": "1F431",
@@ -3693,7 +4048,10 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "meow"
+      "meow",
+      "halloween",
+      "vagina",
+      "cat"
     ],
     "moji": "🐱"
   },
@@ -3711,13 +4069,13 @@
       "pet",
       "cat",
       "kitten",
-      "meow"
+      "halloween"
     ],
     "moji": "🐈"
   },
   "cd": {
     "unicode": "1F4BF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "optical disc",
     "shortname": ":cd:",
     "category": "objects",
@@ -3732,25 +4090,14 @@
       "cd",
       "computer",
       "object",
-      "office"
-    ]
-  },
-  "celtic_cross": {
-    "unicode": "1F548",
-    "unicode_alternates": [],
-    "name": "celtic cross",
-    "shortname": ":celtic_cross:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "religion",
-      "symbol"
-    ]
+      "office",
+      "electronics"
+    ],
+    "moji": "💿"
   },
   "chains": {
     "unicode": "26D3",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "chains",
     "shortname": ":chains:",
     "category": "objects",
@@ -3758,32 +4105,55 @@
     "aliases_ascii": [],
     "keywords": [
       "chain",
-      "object"
-    ]
+      "object",
+      "tool"
+    ],
+    "moji": "⛓"
   },
   "champagne": {
     "unicode": "1F37E",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bottle with popping cork",
     "shortname": ":champagne:",
-    "category": "foods",
+    "category": "food",
     "aliases": [
       ":bottle_with_popping_cork:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "drink",
+      "cheers",
+      "alcohol",
+      "parties"
+    ],
+    "moji": "🍾"
+  },
+  "champagne_glass": {
+    "unicode": "1F942",
+    "unicode_alternates": [],
+    "name": "clinking glasses",
+    "shortname": ":champagne_glass:",
+    "category": "food",
+    "aliases": [
+      ":clinking_glass:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥂"
   },
   "chart": {
     "unicode": "1F4B9",
     "unicode_alternates": [],
     "name": "chart with upwards trend and yen sign",
     "shortname": ":chart:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "graph",
-      "green-square"
+      "green-square",
+      "symbol",
+      "money"
     ],
     "moji": "💹"
   },
@@ -3796,7 +4166,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "graph"
+      "graph",
+      "work",
+      "office"
     ],
     "moji": "📉"
   },
@@ -3809,7 +4181,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "graph"
+      "graph",
+      "work",
+      "office"
     ],
     "moji": "📈"
   },
@@ -3818,7 +4192,7 @@
     "unicode_alternates": [],
     "name": "chequered flag",
     "shortname": ":checkered_flag:",
-    "category": "objects",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3832,28 +4206,32 @@
       "flag",
       "finish",
       "complete",
-      "end"
+      "end",
+      "object"
     ],
     "moji": "🏁"
   },
   "cheese": {
     "unicode": "1F9C0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "cheese wedge",
     "shortname": ":cheese:",
-    "category": "foods",
+    "category": "food",
     "aliases": [
       ":cheese_wedge:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "food"
+    ],
+    "moji": "🧀"
   },
   "cherries": {
     "unicode": "1F352",
     "unicode_alternates": [],
     "name": "cherries",
     "shortname": ":cherries:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3862,7 +4240,6 @@
       "cherry",
       "cherries",
       "tree",
-      "fruit",
       "pit"
     ],
     "moji": "🍒"
@@ -3882,7 +4259,7 @@
       "cherry",
       "blossom",
       "tree",
-      "flower"
+      "tropical"
     ],
     "moji": "🌸"
   },
@@ -3899,8 +4276,9 @@
       "squirrel",
       "chestnut",
       "roasted",
-      "food",
-      "tree"
+      "tree",
+      "nature",
+      "plant"
     ],
     "moji": "🌰"
   },
@@ -3927,7 +4305,7 @@
     "unicode_alternates": [],
     "name": "children crossing",
     "shortname": ":children_crossing:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3938,7 +4316,8 @@
       "crossing",
       "street",
       "crosswalk",
-      "slow"
+      "slow",
+      "symbol"
     ],
     "moji": "🚸"
   },
@@ -3952,15 +4331,17 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
-    ]
+      "nature",
+      "wildlife"
+    ],
+    "moji": "🐿"
   },
   "chocolate_bar": {
     "unicode": "1F36B",
     "unicode_alternates": [],
     "name": "chocolate bar",
     "shortname": ":chocolate_bar:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3971,7 +4352,8 @@
       "bar",
       "candy",
       "coca",
-      "hershey&#039;s"
+      "hershey&#039;s",
+      "halloween"
     ],
     "moji": "🍫"
   },
@@ -3980,7 +4362,7 @@
     "unicode_alternates": [],
     "name": "christmas tree",
     "shortname": ":christmas_tree:",
-    "category": "objects",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -3990,17 +4372,17 @@
       "vacation",
       "xmas",
       "christmas",
-      "xmas",
       "santa",
       "holiday",
       "winter",
-      "december",
-      "santa",
       "evergreen",
       "ornaments",
       "jesus",
       "gifts",
-      "presents"
+      "presents",
+      "plant",
+      "holidays",
+      "trees"
     ],
     "moji": "🎄"
   },
@@ -4011,13 +4393,16 @@
     ],
     "name": "church",
     "shortname": ":church:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "building",
       "christ",
-      "religion"
+      "religion",
+      "places",
+      "wedding",
+      "condolence"
     ],
     "moji": "⛪"
   },
@@ -4026,7 +4411,7 @@
     "unicode_alternates": [],
     "name": "cinema",
     "shortname": ":cinema:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4035,10 +4420,11 @@
       "movie",
       "record",
       "cinema",
-      "movie",
       "theater",
       "motion",
-      "picture"
+      "picture",
+      "symbol",
+      "camera"
     ],
     "moji": "🎦"
   },
@@ -4047,7 +4433,7 @@
     "unicode_alternates": [],
     "name": "circus tent",
     "shortname": ":circus_tent:",
-    "category": "places",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4057,10 +4443,10 @@
       "circus",
       "tent",
       "event",
-      "carnival",
       "big",
       "top",
-      "canvas"
+      "canvas",
+      "circus tent"
     ],
     "moji": "🎪"
   },
@@ -4069,7 +4455,7 @@
     "unicode_alternates": [],
     "name": "cityscape at dusk",
     "shortname": ":city_dusk:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4082,7 +4468,9 @@
       "evening",
       "metropolitan",
       "night",
-      "dark"
+      "dark",
+      "places",
+      "building"
     ],
     "moji": "🌆"
   },
@@ -4091,7 +4479,7 @@
     "unicode_alternates": [],
     "name": "sunset over buildings",
     "shortname": ":city_sunset:",
-    "category": "places",
+    "category": "travel",
     "aliases": [
       ":city_sunrise:"
     ],
@@ -4106,7 +4494,11 @@
       "morning",
       "metropolitan",
       "rise",
-      "sun"
+      "sun",
+      "places",
+      "building",
+      "sky",
+      "vacation"
     ],
     "moji": "🌇"
   },
@@ -4115,7 +4507,7 @@
     "unicode_alternates": [],
     "name": "cityscape",
     "shortname": ":cityscape:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4124,12 +4516,16 @@
       "view",
       "lights",
       "buiildings",
-      "metropolis"
-    ]
+      "metropolis",
+      "places",
+      "building",
+      "vacation"
+    ],
+    "moji": "🏙"
   },
   "cl": {
     "unicode": "1F191",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "squared cl",
     "shortname": ":cl:",
     "category": "symbols",
@@ -4143,14 +4539,15 @@
       "clear",
       "symbol",
       "word"
-    ]
+    ],
+    "moji": "🆑"
   },
   "clap": {
     "unicode": "1F44F",
     "unicode_alternates": [],
     "name": "clapping hands sign",
     "shortname": ":clap:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4163,13 +4560,18 @@
       "approval",
       "sound",
       "encouragement",
-      "enthusiasm"
+      "enthusiasm",
+      "body",
+      "win",
+      "diversity",
+      "good",
+      "beautiful"
     ],
     "moji": "👏"
   },
   "clap_tone1": {
     "unicode": "1F44F-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clapping hands sign tone 1",
     "shortname": ":clap_tone1:",
     "category": "people",
@@ -4185,11 +4587,12 @@
       "sound",
       "encouragement",
       "enthusiasm"
-    ]
+    ],
+    "moji": "👏🏻"
   },
   "clap_tone2": {
     "unicode": "1F44F-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clapping hands sign tone 2",
     "shortname": ":clap_tone2:",
     "category": "people",
@@ -4205,11 +4608,12 @@
       "sound",
       "encouragement",
       "enthusiasm"
-    ]
+    ],
+    "moji": "👏🏼"
   },
   "clap_tone3": {
     "unicode": "1F44F-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clapping hands sign tone 3",
     "shortname": ":clap_tone3:",
     "category": "people",
@@ -4225,11 +4629,12 @@
       "sound",
       "encouragement",
       "enthusiasm"
-    ]
+    ],
+    "moji": "👏🏽"
   },
   "clap_tone4": {
     "unicode": "1F44F-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clapping hands sign tone 4",
     "shortname": ":clap_tone4:",
     "category": "people",
@@ -4245,11 +4650,12 @@
       "sound",
       "encouragement",
       "enthusiasm"
-    ]
+    ],
+    "moji": "👏🏾"
   },
   "clap_tone5": {
     "unicode": "1F44F-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clapping hands sign tone 5",
     "shortname": ":clap_tone5:",
     "category": "people",
@@ -4265,14 +4671,15 @@
       "sound",
       "encouragement",
       "enthusiasm"
-    ]
+    ],
+    "moji": "👏🏿"
   },
   "clapper": {
     "unicode": "1F3AC",
     "unicode_alternates": [],
     "name": "clapper board",
     "shortname": ":clapper:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4282,8 +4689,6 @@
       "clapper",
       "board",
       "clapboard",
-      "movie",
-      "film",
       "take"
     ],
     "moji": "🎬"
@@ -4293,7 +4698,7 @@
     "unicode_alternates": [],
     "name": "classical building",
     "shortname": ":classical_building:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4301,8 +4706,13 @@
       "architecture",
       "history",
       "iconic",
-      "genre"
-    ]
+      "genre",
+      "places",
+      "building",
+      "travel",
+      "vacation"
+    ],
+    "moji": "🏛"
   },
   "clipboard": {
     "unicode": "1F4CB",
@@ -4314,7 +4724,11 @@
     "aliases_ascii": [],
     "keywords": [
       "documents",
-      "stationery"
+      "stationery",
+      "object",
+      "work",
+      "office",
+      "write"
     ],
     "moji": "📋"
   },
@@ -4323,26 +4737,29 @@
     "unicode_alternates": [],
     "name": "mantlepiece clock",
     "shortname": ":clock:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":mantlepiece_clock:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "time"
-    ]
+      "time",
+      "object"
+    ],
+    "moji": "🕰"
   },
   "clock1": {
     "unicode": "1F550",
     "unicode_alternates": [],
     "name": "clock face one oclock",
     "shortname": ":clock1:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕐"
   },
@@ -4351,12 +4768,13 @@
     "unicode_alternates": [],
     "name": "clock face ten oclock",
     "shortname": ":clock10:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕙"
   },
@@ -4365,12 +4783,13 @@
     "unicode_alternates": [],
     "name": "clock face ten-thirty",
     "shortname": ":clock1030:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕥"
   },
@@ -4379,12 +4798,13 @@
     "unicode_alternates": [],
     "name": "clock face eleven oclock",
     "shortname": ":clock11:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕚"
   },
@@ -4393,12 +4813,13 @@
     "unicode_alternates": [],
     "name": "clock face eleven-thirty",
     "shortname": ":clock1130:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕦"
   },
@@ -4407,12 +4828,13 @@
     "unicode_alternates": [],
     "name": "clock face twelve oclock",
     "shortname": ":clock12:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕛"
   },
@@ -4421,25 +4843,28 @@
     "unicode_alternates": [],
     "name": "clock face twelve-thirty",
     "shortname": ":clock1230:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
-    ]
+      "time",
+      "symbol"
+    ],
+    "moji": "🕧"
   },
   "clock130": {
     "unicode": "1F55C",
     "unicode_alternates": [],
     "name": "clock face one-thirty",
     "shortname": ":clock130:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕜"
   },
@@ -4448,12 +4873,13 @@
     "unicode_alternates": [],
     "name": "clock face two oclock",
     "shortname": ":clock2:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕑"
   },
@@ -4462,12 +4888,13 @@
     "unicode_alternates": [],
     "name": "clock face two-thirty",
     "shortname": ":clock230:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕝"
   },
@@ -4476,12 +4903,13 @@
     "unicode_alternates": [],
     "name": "clock face three oclock",
     "shortname": ":clock3:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕒"
   },
@@ -4490,12 +4918,13 @@
     "unicode_alternates": [],
     "name": "clock face three-thirty",
     "shortname": ":clock330:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕞"
   },
@@ -4504,12 +4933,13 @@
     "unicode_alternates": [],
     "name": "clock face four oclock",
     "shortname": ":clock4:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕓"
   },
@@ -4518,12 +4948,13 @@
     "unicode_alternates": [],
     "name": "clock face four-thirty",
     "shortname": ":clock430:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕟"
   },
@@ -4532,12 +4963,13 @@
     "unicode_alternates": [],
     "name": "clock face five oclock",
     "shortname": ":clock5:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕔"
   },
@@ -4546,12 +4978,13 @@
     "unicode_alternates": [],
     "name": "clock face five-thirty",
     "shortname": ":clock530:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕠"
   },
@@ -4560,12 +4993,13 @@
     "unicode_alternates": [],
     "name": "clock face six oclock",
     "shortname": ":clock6:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕕"
   },
@@ -4574,12 +5008,13 @@
     "unicode_alternates": [],
     "name": "clock face six-thirty",
     "shortname": ":clock630:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕡"
   },
@@ -4588,12 +5023,13 @@
     "unicode_alternates": [],
     "name": "clock face seven oclock",
     "shortname": ":clock7:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕖"
   },
@@ -4602,12 +5038,13 @@
     "unicode_alternates": [],
     "name": "clock face seven-thirty",
     "shortname": ":clock730:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕢"
   },
@@ -4616,12 +5053,13 @@
     "unicode_alternates": [],
     "name": "clock face eight oclock",
     "shortname": ":clock8:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕗"
   },
@@ -4630,12 +5068,13 @@
     "unicode_alternates": [],
     "name": "clock face eight-thirty",
     "shortname": ":clock830:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕣"
   },
@@ -4644,12 +5083,13 @@
     "unicode_alternates": [],
     "name": "clock face nine oclock",
     "shortname": ":clock9:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕘"
   },
@@ -4658,29 +5098,16 @@
     "unicode_alternates": [],
     "name": "clock face nine-thirty",
     "shortname": ":clock930:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clock",
-      "time"
+      "time",
+      "symbol"
     ],
     "moji": "🕤"
   },
-  "clockwise_arrows": {
-    "unicode": "1F5D8",
-    "unicode_alternates": [],
-    "name": "clockwise right and left semicircle arrows",
-    "shortname": ":clockwise_arrows:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":clockwise_right_and_left_semicircle_arrows:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "sync"
-    ]
-  },
   "closed_book": {
     "unicode": "1F4D5",
     "unicode_alternates": [],
@@ -4692,7 +5119,11 @@
     "keywords": [
       "knowledge",
       "library",
-      "read"
+      "read",
+      "object",
+      "office",
+      "write",
+      "book"
     ],
     "moji": "📕"
   },
@@ -4706,7 +5137,9 @@
     "aliases_ascii": [],
     "keywords": [
       "privacy",
-      "security"
+      "security",
+      "object",
+      "lock"
     ],
     "moji": "🔐"
   },
@@ -4715,7 +5148,7 @@
     "unicode_alternates": [],
     "name": "closed umbrella",
     "shortname": ":closed_umbrella:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4724,12 +5157,14 @@
       "weather",
       "umbrella",
       "closed",
-      "rain",
       "moisture",
       "protection",
       "sun",
       "ultraviolet",
-      "uv"
+      "uv",
+      "object",
+      "sky",
+      "accessories"
     ],
     "moji": "🌂"
   },
@@ -4745,7 +5180,10 @@
     "aliases_ascii": [],
     "keywords": [
       "sky",
-      "weather"
+      "weather",
+      "cloud",
+      "cold",
+      "rain"
     ],
     "moji": "☁"
   },
@@ -4761,8 +5199,13 @@
     "aliases_ascii": [],
     "keywords": [
       "weather",
-      "thunder"
-    ]
+      "thunder",
+      "sky",
+      "cloud",
+      "cold",
+      "rain"
+    ],
+    "moji": "🌩"
   },
   "cloud_rain": {
     "unicode": "1F327",
@@ -4776,8 +5219,14 @@
     "aliases_ascii": [],
     "keywords": [
       "weather",
-      "wet"
-    ]
+      "wet",
+      "winter",
+      "sky",
+      "cloud",
+      "cold",
+      "rain"
+    ],
+    "moji": "🌧"
   },
   "cloud_snow": {
     "unicode": "1F328",
@@ -4791,8 +5240,13 @@
     "aliases_ascii": [],
     "keywords": [
       "weather",
-      "cold"
-    ]
+      "cold",
+      "winter",
+      "sky",
+      "cloud",
+      "snow"
+    ],
+    "moji": "🌨"
   },
   "cloud_tornado": {
     "unicode": "1F32A",
@@ -4807,8 +5261,24 @@
     "keywords": [
       "weather",
       "destruction",
-      "funnel"
-    ]
+      "funnel",
+      "sky",
+      "cold"
+    ],
+    "moji": "🌪"
+  },
+  "clown": {
+    "unicode": "1F921",
+    "unicode_alternates": [],
+    "name": "clown face",
+    "shortname": ":clown:",
+    "category": "people",
+    "aliases": [
+      ":clown_face:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤡"
   },
   "clubs": {
     "unicode": "2663",
@@ -4817,12 +5287,14 @@
     ],
     "name": "black club suit",
     "shortname": ":clubs:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cards",
-      "poker"
+      "poker",
+      "symbol",
+      "game"
     ],
     "moji": "♣"
   },
@@ -4831,7 +5303,7 @@
     "unicode_alternates": [],
     "name": "cocktail glass",
     "shortname": ":cocktail:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4841,11 +5313,11 @@
       "drunk",
       "cocktail",
       "mixed",
-      "drink",
-      "alcohol",
       "glass",
       "martini",
-      "bar"
+      "bar",
+      "girls night",
+      "parties"
     ],
     "moji": "🍸"
   },
@@ -4856,20 +5328,23 @@
     ],
     "name": "hot beverage",
     "shortname": ":coffee:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "beverage",
       "cafe",
       "drink",
-      "espresso"
+      "espresso",
+      "caffeine",
+      "steam",
+      "morning"
     ],
     "moji": "☕"
   },
   "coffin": {
     "unicode": "26B0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "coffin",
     "shortname": ":coffin:",
     "category": "objects",
@@ -4877,15 +5352,18 @@
     "aliases_ascii": [],
     "keywords": [
       "death",
-      "object"
-    ]
+      "object",
+      "dead",
+      "rip"
+    ],
+    "moji": "⚰"
   },
   "cold_sweat": {
     "unicode": "1F630",
     "unicode_alternates": [],
     "name": "face with open mouth and cold sweat",
     "shortname": ":cold_sweat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4893,13 +5371,15 @@
       "nervous",
       "sweat",
       "exasperated",
-      "frustrated"
+      "frustrated",
+      "smiley",
+      "emotion"
     ],
     "moji": "😰"
   },
   "comet": {
     "unicode": "2604",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "comet",
     "shortname": ":comet:",
     "category": "nature",
@@ -4907,20 +5387,23 @@
     "aliases_ascii": [],
     "keywords": [
       "object",
-      "space"
-    ]
+      "space",
+      "sky"
+    ],
+    "moji": "☄"
   },
   "compression": {
     "unicode": "1F5DC",
     "unicode_alternates": [],
     "name": "compression",
     "shortname": ":compression:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "reduce"
-    ]
+    ],
+    "moji": "🗜"
   },
   "computer": {
     "unicode": "1F4BB",
@@ -4932,25 +5415,13 @@
     "aliases_ascii": [],
     "keywords": [
       "laptop",
-      "tech"
+      "tech",
+      "electronics",
+      "work",
+      "office"
     ],
     "moji": "💻"
   },
-  "computer_old": {
-    "unicode": "1F5B3",
-    "unicode_alternates": [],
-    "name": "old personal computer",
-    "shortname": ":computer_old:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":old_personal_computer:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "cpu",
-      "terminal"
-    ]
-  },
   "confetti_ball": {
     "unicode": "1F38A",
     "unicode_alternates": [],
@@ -4962,7 +5433,6 @@
     "keywords": [
       "festival",
       "party",
-      "party",
       "congratulations",
       "confetti",
       "ball",
@@ -4970,7 +5440,13 @@
       "win",
       "birthday",
       "new years",
-      "wedding"
+      "wedding",
+      "object",
+      "holidays",
+      "cheers",
+      "girls night",
+      "boys night",
+      "parties"
     ],
     "moji": "🎊"
   },
@@ -4979,7 +5455,7 @@
     "unicode_alternates": [],
     "name": "confounded face",
     "shortname": ":confounded:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -4991,7 +5467,11 @@
       "amaze",
       "perplex",
       "puzzle",
-      "mystify"
+      "mystify",
+      "sad",
+      "smiley",
+      "angry",
+      "emotion"
     ],
     "moji": "😖"
   },
@@ -5000,7 +5480,7 @@
     "unicode_alternates": [],
     "name": "confused face",
     "shortname": ":confused:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ">:\\",
@@ -5024,7 +5504,10 @@
       "skeptical",
       "undecided",
       "uneasy",
-      "hesitant"
+      "hesitant",
+      "smiley",
+      "surprised",
+      "emotion"
     ],
     "moji": "😕"
   },
@@ -5035,13 +5518,15 @@
     ],
     "name": "circled ideograph congratulation",
     "shortname": ":congratulations:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "japanese",
-      "kanji"
+      "kanji",
+      "japan",
+      "symbol"
     ],
     "moji": "㊗"
   },
@@ -5050,19 +5535,20 @@
     "unicode_alternates": [],
     "name": "construction sign",
     "shortname": ":construction:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "caution",
       "progress",
-      "wip"
+      "wip",
+      "object"
     ],
     "moji": "🚧"
   },
   "construction_site": {
     "unicode": "1F3D7",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "building construction",
     "shortname": ":construction_site:",
     "category": "travel",
@@ -5073,28 +5559,36 @@
     "keywords": [
       "site",
       "work",
-      "place"
-    ]
+      "place",
+      "building",
+      "crane"
+    ],
+    "moji": "🏗"
   },
   "construction_worker": {
     "unicode": "1F477",
     "unicode_alternates": [],
     "name": "construction worker",
     "shortname": ":construction_worker:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "human",
       "male",
       "man",
-      "wip"
+      "wip",
+      "people",
+      "hat",
+      "men",
+      "diversity",
+      "job"
     ],
     "moji": "👷"
   },
   "construction_worker_tone1": {
     "unicode": "1F477-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "construction worker tone 1",
     "shortname": ":construction_worker_tone1:",
     "category": "people",
@@ -5105,11 +5599,12 @@
       "male",
       "man",
       "wip"
-    ]
+    ],
+    "moji": "👷🏻"
   },
   "construction_worker_tone2": {
     "unicode": "1F477-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "construction worker tone 2",
     "shortname": ":construction_worker_tone2:",
     "category": "people",
@@ -5120,11 +5615,12 @@
       "male",
       "man",
       "wip"
-    ]
+    ],
+    "moji": "👷🏼"
   },
   "construction_worker_tone3": {
     "unicode": "1F477-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "construction worker tone 3",
     "shortname": ":construction_worker_tone3:",
     "category": "people",
@@ -5135,11 +5631,12 @@
       "male",
       "man",
       "wip"
-    ]
+    ],
+    "moji": "👷🏽"
   },
   "construction_worker_tone4": {
     "unicode": "1F477-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "construction worker tone 4",
     "shortname": ":construction_worker_tone4:",
     "category": "people",
@@ -5150,11 +5647,12 @@
       "male",
       "man",
       "wip"
-    ]
+    ],
+    "moji": "👷🏾"
   },
   "construction_worker_tone5": {
     "unicode": "1F477-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "construction worker tone 5",
     "shortname": ":construction_worker_tone5:",
     "category": "people",
@@ -5165,45 +5663,34 @@
       "male",
       "man",
       "wip"
-    ]
+    ],
+    "moji": "👷🏿"
   },
   "control_knobs": {
     "unicode": "1F39B",
     "unicode_alternates": [],
     "name": "control knobs",
     "shortname": ":control_knobs:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "dial"
-    ]
-  },
-  "contruction_site": {
-    "unicode": "1F3D7",
-    "unicode_alternates": [],
-    "name": "building construction",
-    "shortname": ":contruction_site:",
-    "category": "travel_places",
-    "aliases": [
-      ":building_construction:"
+      "dial",
+      "time"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "site",
-      "work"
-    ]
+    "moji": "🎛"
   },
   "convenience_store": {
     "unicode": "1F3EA",
     "unicode_alternates": [],
     "name": "convenience store",
     "shortname": ":convenience_store:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "building"
+      "building",
+      "places"
     ],
     "moji": "🏪"
   },
@@ -5212,7 +5699,7 @@
     "unicode_alternates": [],
     "name": "cookie",
     "shortname": ":cookie:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5224,21 +5711,44 @@
       "dessert",
       "biscuit",
       "sweet",
-      "chocolate"
+      "vagina"
     ],
     "moji": "🍪"
   },
+  "cooking": {
+    "unicode": "1F373",
+    "unicode_alternates": [],
+    "name": "cooking",
+    "shortname": ":cooking:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [
+      "breakfast",
+      "food",
+      "egg",
+      "fry",
+      "pan",
+      "flat",
+      "cook",
+      "frying",
+      "cooking",
+      "utensil"
+    ],
+    "moji": "🍳"
+  },
   "cool": {
     "unicode": "1F192",
     "unicode_alternates": [],
     "name": "squared cool",
     "shortname": ":cool:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🆒"
   },
@@ -5247,7 +5757,7 @@
     "unicode_alternates": [],
     "name": "police officer",
     "shortname": ":cop:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5255,13 +5765,19 @@
       "enforcement",
       "law",
       "man",
-      "police"
+      "police",
+      "people",
+      "hat",
+      "men",
+      "diversity",
+      "job",
+      "911"
     ],
     "moji": "👮"
   },
   "cop_tone1": {
     "unicode": "1F46E-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "police officer tone 1",
     "shortname": ":cop_tone1:",
     "category": "people",
@@ -5273,11 +5789,12 @@
       "law",
       "man",
       "cop"
-    ]
+    ],
+    "moji": "👮🏻"
   },
   "cop_tone2": {
     "unicode": "1F46E-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "police officer tone 2",
     "shortname": ":cop_tone2:",
     "category": "people",
@@ -5289,11 +5806,12 @@
       "law",
       "man",
       "cop"
-    ]
+    ],
+    "moji": "👮🏼"
   },
   "cop_tone3": {
     "unicode": "1F46E-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "police officer tone 3",
     "shortname": ":cop_tone3:",
     "category": "people",
@@ -5305,11 +5823,12 @@
       "law",
       "man",
       "cop"
-    ]
+    ],
+    "moji": "👮🏽"
   },
   "cop_tone4": {
     "unicode": "1F46E-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "police officer tone 4",
     "shortname": ":cop_tone4:",
     "category": "people",
@@ -5321,11 +5840,12 @@
       "law",
       "man",
       "cop"
-    ]
+    ],
+    "moji": "👮🏾"
   },
   "cop_tone5": {
     "unicode": "1F46E-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "police officer tone 5",
     "shortname": ":cop_tone5:",
     "category": "people",
@@ -5337,7 +5857,8 @@
       "law",
       "man",
       "cop"
-    ]
+    ],
+    "moji": "👮🏿"
   },
   "copyright": {
     "moji": "©",
@@ -5345,12 +5866,13 @@
     "unicode_alternates": [],
     "name": "copyright sign",
     "shortname": ":copyright:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "ip",
-      "license"
+      "license",
+      "symbol"
     ]
   },
   "corn": {
@@ -5358,7 +5880,7 @@
     "unicode_alternates": [],
     "name": "ear of maize",
     "shortname": ":corn:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5367,7 +5889,6 @@
       "vegetable",
       "corn",
       "maize",
-      "food",
       "iowa",
       "kernel",
       "popcorn",
@@ -5375,7 +5896,8 @@
       "yellow",
       "stalk",
       "cob",
-      "ear"
+      "ear",
+      "vegetables"
     ],
     "moji": "🌽"
   },
@@ -5384,7 +5906,7 @@
     "unicode_alternates": [],
     "name": "couch and lamp",
     "shortname": ":couch:",
-    "category": "travel_places",
+    "category": "objects",
     "aliases": [
       ":couch_and_lamp:"
     ],
@@ -5397,15 +5919,17 @@
       "leather",
       "microfiber",
       "sit",
-      "relax"
-    ]
+      "relax",
+      "object"
+    ],
+    "moji": "🛋"
   },
   "couple": {
     "unicode": "1F46B",
     "unicode_alternates": [],
     "name": "man and woman holding hands",
     "shortname": ":couple:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5417,7 +5941,9 @@
       "love",
       "marriage",
       "people",
-      "valentines"
+      "valentines",
+      "sex",
+      "creationism"
     ],
     "moji": "👫"
   },
@@ -5440,15 +5966,21 @@
       "like",
       "love",
       "marriage",
-      "valentines"
-    ]
+      "valentines",
+      "people",
+      "gay",
+      "men",
+      "sex",
+      "lgbt"
+    ],
+    "moji": "👨‍❤️‍👨"
   },
   "couple_with_heart": {
     "unicode": "1F491",
     "unicode_alternates": [],
     "name": "couple with heart",
     "shortname": ":couple_with_heart:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5458,7 +5990,9 @@
       "like",
       "love",
       "marriage",
-      "valentines"
+      "valentines",
+      "people",
+      "sex"
     ],
     "moji": "💑"
   },
@@ -5481,15 +6015,20 @@
       "like",
       "love",
       "marriage",
-      "valentines"
-    ]
+      "valentines",
+      "people",
+      "women",
+      "sex",
+      "lgbt"
+    ],
+    "moji": "👩‍❤️‍👩"
   },
   "couplekiss": {
     "unicode": "1F48F",
     "unicode_alternates": [],
     "name": "kiss",
     "shortname": ":couplekiss:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5497,7 +6036,9 @@
       "like",
       "love",
       "marriage",
-      "valentines"
+      "valentines",
+      "people",
+      "sex"
     ],
     "moji": "💏"
   },
@@ -5532,28 +6073,44 @@
       "cow",
       "milk",
       "dairy",
-      "beef",
       "bessie",
       "moo"
     ],
     "moji": "🐄"
   },
+  "cowboy": {
+    "unicode": "1F920",
+    "unicode_alternates": [],
+    "name": "face with cowboy hat",
+    "shortname": ":cowboy:",
+    "category": "people",
+    "aliases": [
+      ":face_with_cowboy_hat:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤠"
+  },
   "crab": {
     "unicode": "1F980",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "crab",
     "shortname": ":crab:",
     "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "tropical",
+      "animal"
+    ],
+    "moji": "🦀"
   },
   "crayon": {
     "unicode": "1F58D",
     "unicode_alternates": [],
     "name": "lower left crayon",
     "shortname": ":crayon:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":lower_left_crayon:"
     ],
@@ -5562,8 +6119,11 @@
       "write",
       "draw",
       "color",
-      "wax"
-    ]
+      "wax",
+      "object",
+      "office"
+    ],
+    "moji": "🖍"
   },
   "credit_card": {
     "unicode": "1F4B3",
@@ -5588,7 +6148,9 @@
       "visa",
       "american express",
       "wallet",
-      "signature"
+      "signature",
+      "object",
+      "boys night"
     ],
     "moji": "💳"
   },
@@ -5606,15 +6168,16 @@
       "crescent",
       "waxing",
       "sky",
-      "night",
       "cheese",
-      "phase"
+      "phase",
+      "space",
+      "goodnight"
     ],
     "moji": "🌙"
   },
   "cricket": {
     "unicode": "1F3CF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "cricket bat and ball",
     "shortname": ":cricket:",
     "category": "activity",
@@ -5622,7 +6185,12 @@
       ":cricket_bat_ball:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "ball",
+      "sport",
+      "cricket"
+    ],
+    "moji": "🏏"
   },
   "crocodile": {
     "unicode": "1F40A",
@@ -5639,13 +6207,26 @@
       "croc",
       "alligator",
       "gator",
-      "cranky"
+      "cranky",
+      "wildlife",
+      "reptile"
     ],
     "moji": "🐊"
   },
+  "croissant": {
+    "unicode": "1F950",
+    "unicode_alternates": [],
+    "name": "croissant",
+    "shortname": ":croissant:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥐"
+  },
   "cross": {
     "unicode": "271D",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "latin cross",
     "shortname": ":cross:",
     "category": "symbols",
@@ -5657,53 +6238,8 @@
       "religion",
       "symbol",
       "christian"
-    ]
-  },
-  "cross_heavy": {
-    "unicode": "1F547",
-    "unicode_alternates": [],
-    "name": "heavy latin cross",
-    "shortname": ":cross_heavy:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":heavy_latin_cross:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "religion",
-      "symbol"
-    ]
-  },
-  "cross_white": {
-    "unicode": "1F546",
-    "unicode_alternates": [],
-    "name": "white latin cross",
-    "shortname": ":cross_white:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":white_latin_cross:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "religion",
-      "symbol"
-    ]
-  },
-  "crossbones": {
-    "unicode": "1F571",
-    "unicode_alternates": [],
-    "name": "black skull and crossbones",
-    "shortname": ":crossbones:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":black_skull_and_crossbones:"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "poison",
-      "danger",
-      "death"
-    ]
+    "moji": "✝"
   },
   "crossed_flags": {
     "unicode": "1F38C",
@@ -5714,13 +6250,14 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "japan"
+      "japan",
+      "object"
     ],
     "moji": "🎌"
   },
   "crossed_swords": {
     "unicode": "2694",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "crossed swords",
     "shortname": ":crossed_swords:",
     "category": "objects",
@@ -5729,21 +6266,25 @@
     "keywords": [
       "object",
       "weapon"
-    ]
+    ],
+    "moji": "⚔"
   },
   "crown": {
     "unicode": "1F451",
     "unicode_alternates": [],
     "name": "crown",
     "shortname": ":crown:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "king",
       "kod",
       "leader",
-      "royalty"
+      "royalty",
+      "object",
+      "gem",
+      "accessories"
     ],
     "moji": "👑"
   },
@@ -5752,7 +6293,7 @@
     "unicode_alternates": [],
     "name": "passenger ship",
     "shortname": ":cruise_ship:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":passenger_ship:"
     ],
@@ -5760,15 +6301,18 @@
     "keywords": [
       "titanic",
       "transportation",
-      "boat"
-    ]
+      "boat",
+      "travel",
+      "vacation"
+    ],
+    "moji": "🛳"
   },
   "cry": {
     "unicode": "1F622",
     "unicode_alternates": [],
     "name": "crying face",
     "shortname": ":cry:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":'(",
@@ -5779,11 +6323,14 @@
     "keywords": [
       "face",
       "sad",
-      "sad",
       "cry",
       "tear",
       "weep",
-      "tears"
+      "tears",
+      "smiley",
+      "emotion",
+      "rip",
+      "heartbreak"
     ],
     "moji": "😢"
   },
@@ -5792,7 +6339,7 @@
     "unicode_alternates": [],
     "name": "crying cat face",
     "shortname": ":crying_cat_face:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5804,8 +6351,6 @@
       "cry",
       "cat",
       "sob",
-      "tears",
-      "sad",
       "melancholy",
       "morn",
       "somber",
@@ -5823,16 +6368,29 @@
     "aliases_ascii": [],
     "keywords": [
       "disco",
-      "party"
+      "party",
+      "object",
+      "ball"
     ],
     "moji": "🔮"
   },
+  "cucumber": {
+    "unicode": "1F952",
+    "unicode_alternates": [],
+    "name": "cucumber",
+    "shortname": ":cucumber:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥒"
+  },
   "cupid": {
     "unicode": "1F498",
     "unicode_alternates": [],
     "name": "heart with arrow",
     "shortname": ":cupid:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5840,7 +6398,8 @@
       "heart",
       "like",
       "love",
-      "valentines"
+      "valentines",
+      "symbol"
     ],
     "moji": "💘"
   },
@@ -5849,11 +6408,12 @@
     "unicode_alternates": [],
     "name": "curly loop",
     "shortname": ":curly_loop:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "scribble"
+      "scribble",
+      "symbol"
     ],
     "moji": "➰"
   },
@@ -5862,13 +6422,14 @@
     "unicode_alternates": [],
     "name": "currency exchange",
     "shortname": ":currency_exchange:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "dollar",
       "money",
-      "travel"
+      "travel",
+      "symbol"
     ],
     "moji": "💱"
   },
@@ -5877,7 +6438,7 @@
     "unicode_alternates": [],
     "name": "curry and rice",
     "shortname": ":curry:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5888,7 +6449,6 @@
       "curry",
       "spice",
       "flavor",
-      "food",
       "meal"
     ],
     "moji": "🍛"
@@ -5898,7 +6458,7 @@
     "unicode_alternates": [],
     "name": "custard",
     "shortname": ":custard:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5920,7 +6480,7 @@
     "unicode_alternates": [],
     "name": "customs",
     "shortname": ":customs:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5932,7 +6492,8 @@
       "goods",
       "check",
       "authority",
-      "government"
+      "government",
+      "symbol"
     ],
     "moji": "🛃"
   },
@@ -5942,7 +6503,7 @@
     "unicode_alternates": [],
     "name": "cyclone",
     "shortname": ":cyclone:",
-    "category": "nature",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5954,7 +6515,9 @@
       "hurricane",
       "typhoon",
       "storm",
-      "ocean"
+      "ocean",
+      "symbol",
+      "drugs"
     ]
   },
   "dagger": {
@@ -5962,22 +6525,25 @@
     "unicode_alternates": [],
     "name": "dagger knife",
     "shortname": ":dagger:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":dagger_knife:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "blade",
-      "knife"
-    ]
+      "knife",
+      "object",
+      "weapon"
+    ],
+    "moji": "🗡"
   },
   "dancer": {
     "unicode": "1F483",
     "unicode_alternates": [],
     "name": "dancer",
     "shortname": ":dancer:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -5995,13 +6561,18 @@
       "ballet",
       "tango",
       "cha cha",
-      "music"
+      "music",
+      "people",
+      "women",
+      "sexy",
+      "diversity",
+      "girls night"
     ],
     "moji": "💃"
   },
   "dancer_tone1": {
     "unicode": "1F483-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "dancer tone 1",
     "shortname": ":dancer_tone1:",
     "category": "people",
@@ -6021,11 +6592,12 @@
       "tango",
       "cha cha",
       "music"
-    ]
+    ],
+    "moji": "💃🏻"
   },
   "dancer_tone2": {
     "unicode": "1F483-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "dancer tone 2",
     "shortname": ":dancer_tone2:",
     "category": "people",
@@ -6045,11 +6617,12 @@
       "tango",
       "cha cha",
       "music"
-    ]
+    ],
+    "moji": "💃🏼"
   },
   "dancer_tone3": {
     "unicode": "1F483-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "dancer tone 3",
     "shortname": ":dancer_tone3:",
     "category": "people",
@@ -6069,11 +6642,12 @@
       "tango",
       "cha cha",
       "music"
-    ]
+    ],
+    "moji": "💃🏽"
   },
   "dancer_tone4": {
     "unicode": "1F483-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "dancer tone 4",
     "shortname": ":dancer_tone4:",
     "category": "people",
@@ -6093,11 +6667,12 @@
       "tango",
       "cha cha",
       "music"
-    ]
+    ],
+    "moji": "💃🏾"
   },
   "dancer_tone5": {
     "unicode": "1F483-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "dancer tone 5",
     "shortname": ":dancer_tone5:",
     "category": "people",
@@ -6117,14 +6692,15 @@
       "tango",
       "cha cha",
       "music"
-    ]
+    ],
+    "moji": "💃🏿"
   },
   "dancers": {
     "unicode": "1F46F",
     "unicode_alternates": [],
     "name": "woman with bunny ears",
     "shortname": ":dancers:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6137,8 +6713,13 @@
       "showgirl",
       "playboy",
       "costume",
-      "bunny",
-      "cancan"
+      "cancan",
+      "people",
+      "sexy",
+      "girls night",
+      "boys night",
+      "parties",
+      "dance"
     ],
     "moji": "👯"
   },
@@ -6147,7 +6728,7 @@
     "unicode_alternates": [],
     "name": "dango",
     "shortname": ":dango:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6166,20 +6747,24 @@
     "unicode_alternates": [],
     "name": "dark sunglasses",
     "shortname": ":dark_sunglasses:",
-    "category": "objects_symbols",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "shades",
-      "eyes"
-    ]
+      "eyes",
+      "fashion",
+      "glasses",
+      "accessories"
+    ],
+    "moji": "🕶"
   },
   "dart": {
     "unicode": "1F3AF",
     "unicode_alternates": [],
     "name": "direct hit",
     "shortname": ":dart:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6190,10 +6775,10 @@
       "bullseye",
       "dart",
       "archery",
-      "game",
       "fletching",
       "arrow",
-      "sport"
+      "sport",
+      "boys night"
     ],
     "moji": "🎯"
   },
@@ -6202,14 +6787,17 @@
     "unicode_alternates": [],
     "name": "dash symbol",
     "shortname": ":dash:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "air",
       "fast",
       "shoo",
-      "wind"
+      "wind",
+      "cloud",
+      "cold",
+      "smoking"
     ],
     "moji": "💨"
   },
@@ -6223,7 +6811,9 @@
     "aliases_ascii": [],
     "keywords": [
       "calendar",
-      "schedule"
+      "schedule",
+      "object",
+      "office"
     ],
     "moji": "📅"
   },
@@ -6242,16 +6832,29 @@
       "tree",
       "leaves",
       "fall",
-      "color"
+      "color",
+      "camp",
+      "trees"
     ],
     "moji": "🌳"
   },
+  "deer": {
+    "unicode": "1F98C",
+    "unicode_alternates": [],
+    "name": "deer",
+    "shortname": ":deer:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦌"
+  },
   "department_store": {
     "unicode": "1F3EC",
     "unicode_alternates": [],
     "name": "department store",
     "shortname": ":department_store:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6262,31 +6865,17 @@
       "store",
       "retail",
       "sale",
-      "merchandise"
+      "merchandise",
+      "places"
     ],
     "moji": "🏬"
   },
-  "descending_notes": {
-    "unicode": "1F39D",
-    "unicode_alternates": [],
-    "name": "beamed descending musical notes",
-    "shortname": ":descending_notes:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "score",
-      "music",
-      "sound",
-      "tone"
-    ]
-  },
   "desert": {
     "unicode": "1F3DC",
     "unicode_alternates": [],
     "name": "desert",
     "shortname": ":desert:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6295,41 +6884,36 @@
       "sandy",
       "cactus",
       "sunny",
-      "barren"
-    ]
+      "barren",
+      "places",
+      "travel",
+      "vacation"
+    ],
+    "moji": "🏜"
   },
   "desktop": {
     "unicode": "1F5A5",
     "unicode_alternates": [],
     "name": "desktop computer",
     "shortname": ":desktop:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":desktop_computer:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "cpu"
-    ]
-  },
-  "desktop_window": {
-    "unicode": "1F5D4",
-    "unicode_alternates": [],
-    "name": "desktop window",
-    "shortname": ":desktop_window:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "computer"
-    ]
+      "cpu",
+      "electronics",
+      "work"
+    ],
+    "moji": "🖥"
   },
   "diamond_shape_with_a_dot_inside": {
     "unicode": "1F4A0",
     "unicode_alternates": [],
     "name": "diamond shape with a dot inside",
     "shortname": ":diamond_shape_with_a_dot_inside:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6339,7 +6923,8 @@
       "kawaii",
       "japanese",
       "glyph",
-      "adorable"
+      "adorable",
+      "symbol"
     ],
     "moji": "💠"
   },
@@ -6350,12 +6935,15 @@
     ],
     "name": "black diamond suit",
     "shortname": ":diamonds:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cards",
-      "poker"
+      "poker",
+      "shapes",
+      "symbol",
+      "game"
     ],
     "moji": "♦"
   },
@@ -6364,7 +6952,7 @@
     "unicode_alternates": [],
     "name": "disappointed face",
     "shortname": ":disappointed:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ">:[",
@@ -6382,7 +6970,10 @@
       "discouraged",
       "face",
       "sad",
-      "upset"
+      "upset",
+      "smiley",
+      "tired",
+      "emotion"
     ],
     "moji": "😞"
   },
@@ -6391,7 +6982,7 @@
     "unicode_alternates": [],
     "name": "disappointed but relieved face",
     "shortname": ":disappointed_relieved:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6400,7 +6991,12 @@
       "phew",
       "sweat",
       "disappoint",
-      "relief"
+      "relief",
+      "sad",
+      "smiley",
+      "stressed",
+      "cry",
+      "emotion"
     ],
     "moji": "😥"
   },
@@ -6409,22 +7005,25 @@
     "unicode_alternates": [],
     "name": "card index dividers",
     "shortname": ":dividers:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":card_index_dividers:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "stationery",
-      "rolodex"
-    ]
+      "rolodex",
+      "work",
+      "office"
+    ],
+    "moji": "🗂"
   },
   "dizzy": {
     "unicode": "1F4AB",
     "unicode_alternates": [],
     "name": "dizzy symbol",
     "shortname": ":dizzy:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6437,7 +7036,7 @@
       "intoxicated",
       "squeans",
       "starburst",
-      "star"
+      "symbol"
     ],
     "moji": "💫"
   },
@@ -6446,7 +7045,7 @@
     "unicode_alternates": [],
     "name": "dizzy face",
     "shortname": ":dizzy_face:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "#-)",
@@ -6463,7 +7062,13 @@
       "face",
       "spent",
       "unconscious",
-      "xox"
+      "xox",
+      "smiley",
+      "surprised",
+      "dead",
+      "wow",
+      "emotion",
+      "omg"
     ],
     "moji": "😵"
   },
@@ -6472,7 +7077,7 @@
     "unicode_alternates": [],
     "name": "do not litter symbol",
     "shortname": ":do_not_litter:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6480,40 +7085,13 @@
       "garbage",
       "trash",
       "litter",
-      "garbage",
       "waste",
       "no",
       "can",
-      "trash"
+      "symbol"
     ],
     "moji": "🚯"
   },
-  "document": {
-    "unicode": "1F5CE",
-    "unicode_alternates": [],
-    "name": "document",
-    "shortname": ":document:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "page"
-    ]
-  },
-  "document_text": {
-    "unicode": "1F5B9",
-    "unicode_alternates": [],
-    "name": "document with text",
-    "shortname": ":document_text:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":document_with_text:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "page"
-    ]
-  },
   "dog": {
     "unicode": "1F436",
     "unicode_alternates": [],
@@ -6526,7 +7104,9 @@
       "animal",
       "friend",
       "nature",
-      "woof"
+      "woof",
+      "dog",
+      "pug"
     ],
     "moji": "🐶"
   },
@@ -6546,11 +7126,10 @@
       "pet",
       "dog",
       "puppy",
-      "pet",
-      "friend",
       "woof",
       "bark",
-      "fido"
+      "fido",
+      "pug"
     ],
     "moji": "🐕"
   },
@@ -6571,8 +7150,6 @@
       "canada",
       "australia",
       "banknote",
-      "money",
-      "currency",
       "paper",
       "cash",
       "bills"
@@ -6593,7 +7170,6 @@
       "toy",
       "dolls",
       "japan",
-      "japanese",
       "day",
       "girls",
       "emperor",
@@ -6602,7 +7178,8 @@
       "blessing",
       "imperial",
       "family",
-      "royal"
+      "royal",
+      "people"
     ],
     "moji": "🎎"
   },
@@ -6621,7 +7198,9 @@
       "flipper",
       "nature",
       "ocean",
-      "sea"
+      "sea",
+      "wildlife",
+      "tropical"
     ],
     "moji": "🐬"
   },
@@ -6641,8 +7220,7 @@
       "doorway",
       "entrance",
       "enter",
-      "exit",
-      "entry"
+      "object"
     ],
     "moji": "🚪"
   },
@@ -6651,7 +7229,7 @@
     "unicode_alternates": [],
     "name": "doughnut",
     "shortname": ":doughnut:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6666,8 +7244,7 @@
       "dessert",
       "breakfast",
       "police",
-      "homer",
-      "sweet"
+      "homer"
     ],
     "moji": "🍩"
   },
@@ -6676,15 +7253,17 @@
     "unicode_alternates": [],
     "name": "dove of peace",
     "shortname": ":dove:",
-    "category": "objects_symbols",
+    "category": "nature",
     "aliases": [
       ":dove_of_peace:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "symbol",
-      "bird"
-    ]
+      "bird",
+      "animal"
+    ],
+    "moji": "🕊"
   },
   "dragon": {
     "unicode": "1F409",
@@ -6703,7 +7282,8 @@
       "dragon",
       "fire",
       "legendary",
-      "myth"
+      "roar",
+      "reptile"
     ],
     "moji": "🐉"
   },
@@ -6725,7 +7305,9 @@
       "head",
       "fire",
       "legendary",
-      "myth"
+      "roar",
+      "monster",
+      "reptile"
     ],
     "moji": "🐲"
   },
@@ -6734,12 +7316,15 @@
     "unicode_alternates": [],
     "name": "dress",
     "shortname": ":dress:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "clothes",
-      "fashion"
+      "fashion",
+      "women",
+      "sexy",
+      "girls night"
     ],
     "moji": "👗"
   },
@@ -6758,23 +7343,35 @@
       "dromedary",
       "camel",
       "hump",
-      "desert",
       "middle east",
       "heat",
-      "hot",
       "water",
       "hump day",
       "wednesday",
-      "sex"
+      "sex",
+      "wildlife"
     ],
     "moji": "🐪"
   },
+  "drooling_face": {
+    "unicode": "1F924",
+    "unicode_alternates": [],
+    "name": "drooling face",
+    "shortname": ":drooling_face:",
+    "category": "people",
+    "aliases": [
+      ":drool:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤤"
+  },
   "droplet": {
     "unicode": "1F4A7",
     "unicode_alternates": [],
     "name": "droplet",
     "shortname": ":droplet:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -6784,7 +7381,6 @@
       "drop",
       "droplet",
       "h20",
-      "water",
       "aqua",
       "tear",
       "sweat",
@@ -6792,10 +7388,36 @@
       "moisture",
       "wet",
       "moist",
-      "spit"
+      "spit",
+      "weather",
+      "sky"
     ],
     "moji": "💧"
   },
+  "drum": {
+    "unicode": "1F941",
+    "unicode_alternates": [],
+    "name": "drum with drumsticks",
+    "shortname": ":drum:",
+    "category": "activity",
+    "aliases": [
+      ":drum_with_drumsticks:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥁"
+  },
+  "duck": {
+    "unicode": "1F986",
+    "unicode_alternates": [],
+    "name": "duck",
+    "shortname": ":duck:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦆"
+  },
   "dvd": {
     "unicode": "1F4C0",
     "unicode_alternates": [],
@@ -6807,7 +7429,8 @@
     "keywords": [
       "cd",
       "disc",
-      "disk"
+      "disk",
+      "electronics"
     ],
     "moji": "📀"
   },
@@ -6823,23 +7446,37 @@
     "aliases_ascii": [],
     "keywords": [
       "communication",
-      "inbox"
+      "inbox",
+      "office"
     ],
     "moji": "📧"
   },
+  "eagle": {
+    "unicode": "1F985",
+    "unicode_alternates": [],
+    "name": "eagle",
+    "shortname": ":eagle:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦅"
+  },
   "ear": {
     "unicode": "1F442",
     "unicode_alternates": [],
     "name": "ear",
     "shortname": ":ear:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "face",
       "hear",
       "listen",
-      "sound"
+      "sound",
+      "body",
+      "diversity"
     ],
     "moji": "👂"
   },
@@ -6857,14 +7494,14 @@
       "ear",
       "rice",
       "food",
-      "plant",
-      "seed"
+      "seed",
+      "leaf"
     ],
     "moji": "🌾"
   },
   "ear_tone1": {
     "unicode": "1F442-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ear tone 1",
     "shortname": ":ear_tone1:",
     "category": "people",
@@ -6874,11 +7511,12 @@
       "hear",
       "listen",
       "sound"
-    ]
+    ],
+    "moji": "👂🏻"
   },
   "ear_tone2": {
     "unicode": "1F442-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ear tone 2",
     "shortname": ":ear_tone2:",
     "category": "people",
@@ -6888,11 +7526,12 @@
       "hear",
       "listen",
       "sound"
-    ]
+    ],
+    "moji": "👂🏼"
   },
   "ear_tone3": {
     "unicode": "1F442-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ear tone 3",
     "shortname": ":ear_tone3:",
     "category": "people",
@@ -6902,11 +7541,12 @@
       "hear",
       "listen",
       "sound"
-    ]
+    ],
+    "moji": "👂🏽"
   },
   "ear_tone4": {
     "unicode": "1F442-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ear tone 4",
     "shortname": ":ear_tone4:",
     "category": "people",
@@ -6916,11 +7556,12 @@
       "hear",
       "listen",
       "sound"
-    ]
+    ],
+    "moji": "👂🏾"
   },
   "ear_tone5": {
     "unicode": "1F442-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ear tone 5",
     "shortname": ":ear_tone5:",
     "category": "people",
@@ -6930,7 +7571,8 @@
       "hear",
       "listen",
       "sound"
-    ]
+    ],
+    "moji": "👂🏿"
   },
   "earth_africa": {
     "unicode": "1F30D",
@@ -6945,12 +7587,13 @@
       "international",
       "world",
       "earth",
-      "globe",
       "space",
       "planet",
       "africa",
       "europe",
-      "home"
+      "home",
+      "map",
+      "vacation"
     ],
     "moji": "🌍"
   },
@@ -6968,14 +7611,15 @@
       "international",
       "world",
       "earth",
-      "globe",
       "space",
       "planet",
       "north",
       "south",
       "america",
       "americas",
-      "home"
+      "home",
+      "map",
+      "vacation"
     ],
     "moji": "🌎"
   },
@@ -6993,43 +7637,33 @@
       "international",
       "world",
       "earth",
-      "globe",
       "space",
       "planet",
       "asia",
       "australia",
-      "home"
+      "home",
+      "map",
+      "vacation"
     ],
     "moji": "🌏"
   },
   "egg": {
-    "unicode": "1F373",
+    "unicode": "1F95A",
     "unicode_alternates": [],
-    "name": "cooking",
+    "name": "egg",
     "shortname": ":egg:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [
-      "breakfast",
-      "food",
-      "egg",
-      "fry",
-      "pan",
-      "flat",
-      "cook",
-      "frying",
-      "cooking",
-      "utensil"
-    ],
-    "moji": "🍳"
+    "keywords": [],
+    "moji": "🥚"
   },
   "eggplant": {
     "unicode": "1F346",
     "unicode_alternates": [],
     "name": "aubergine",
     "shortname": ":eggplant:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7038,10 +7672,10 @@
       "nature",
       "vegetable",
       "eggplant",
-      "aubergine",
       "fruit",
       "purple",
-      "penis"
+      "penis",
+      "vegetables"
     ],
     "moji": "🍆"
   },
@@ -7051,15 +7685,18 @@
     "unicode_alternates": [
       "0038-FE0F-20E3"
     ],
-    "name": "digit eight",
+    "name": "keycap digit eight",
     "shortname": ":eight:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "8",
       "blue-square",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "eight_pointed_black_star": {
@@ -7069,10 +7706,12 @@
     ],
     "name": "eight pointed black star",
     "shortname": ":eight_pointed_black_star:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "symbol"
+    ],
     "moji": "✴"
   },
   "eight_spoked_asterisk": {
@@ -7082,16 +7721,32 @@
     ],
     "name": "eight spoked asterisk",
     "shortname": ":eight_spoked_asterisk:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "green-square",
       "sparkle",
-      "star"
+      "star",
+      "symbol"
     ],
     "moji": "✳"
   },
+  "eject": {
+    "unicode": "23CF",
+    "unicode_alternates": [
+      "23CF-FE0F"
+    ],
+    "name": "eject symbol",
+    "shortname": ":eject:",
+    "category": "symbols",
+    "aliases": [
+      ":eject_symbol:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "⏏"
+  },
   "electric_plug": {
     "unicode": "1F50C",
     "unicode_alternates": [],
@@ -7102,7 +7757,8 @@
     "aliases_ascii": [],
     "keywords": [
       "charger",
-      "power"
+      "power",
+      "electronics"
     ],
     "moji": "🔌"
   },
@@ -7118,7 +7774,8 @@
       "animal",
       "nature",
       "nose",
-      "thailand"
+      "thailand",
+      "wildlife"
     ],
     "moji": "🐘"
   },
@@ -7127,100 +7784,36 @@
     "unicode_alternates": [],
     "name": "end with leftwards arrow above",
     "shortname": ":end:",
-    "category": "other",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "arrow",
-      "words"
-    ],
-    "moji": "🔚"
-  },
-  "envelope": {
-    "unicode": "2709",
-    "unicode_alternates": [
-      "2709-FE0F"
-    ],
-    "name": "envelope",
-    "shortname": ":envelope:",
-    "category": "objects",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "letter",
-      "mail",
-      "postal"
-    ],
-    "moji": "✉"
-  },
-  "envelope_back": {
-    "unicode": "1F582",
-    "unicode_alternates": [],
-    "name": "back of envelope",
-    "shortname": ":envelope_back:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":back_of_envelope:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "letter",
-      "mail",
-      "postal"
-    ]
-  },
-  "envelope_flying": {
-    "unicode": "1F585",
-    "unicode_alternates": [],
-    "name": "flying envelope",
-    "shortname": ":envelope_flying:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":flying_envelope:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "letter",
-      "mail",
-      "postal"
-    ]
-  },
-  "envelope_stamped": {
-    "unicode": "1F583",
-    "unicode_alternates": [],
-    "name": "stamped envelope",
-    "shortname": ":envelope_stamped:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":stamped_envelope:"
-    ],
+    "category": "symbols",
+    "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "communication",
-      "letter",
-      "mail",
-      "postal"
-    ]
+      "arrow",
+      "words",
+      "symbol"
+    ],
+    "moji": "🔚"
   },
-  "envelope_stamped_pen": {
-    "unicode": "1F586",
-    "unicode_alternates": [],
-    "name": "pen over stamped envelope",
-    "shortname": ":envelope_stamped_pen:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":pen_over_stamped_envelope:"
+  "envelope": {
+    "unicode": "2709",
+    "unicode_alternates": [
+      "2709-FE0F"
     ],
+    "name": "envelope",
+    "shortname": ":envelope:",
+    "category": "objects",
+    "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "communication",
       "letter",
       "mail",
-      "postal"
-    ]
+      "postal",
+      "object",
+      "office",
+      "write"
+    ],
+    "moji": "✉"
   },
   "envelope_with_arrow": {
     "unicode": "1F4E9",
@@ -7231,7 +7824,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "email"
+      "email",
+      "object",
+      "office"
     ],
     "moji": "📩"
   },
@@ -7250,8 +7845,6 @@
       "euro",
       "europe",
       "banknote",
-      "money",
-      "currency",
       "paper",
       "cash",
       "bills"
@@ -7263,7 +7856,7 @@
     "unicode_alternates": [],
     "name": "european castle",
     "shortname": ":european_castle:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7273,7 +7866,6 @@
       "castle",
       "european",
       "residence",
-      "royalty",
       "disneyland",
       "disney",
       "fort",
@@ -7287,7 +7879,10 @@
       "queen",
       "fortress",
       "nobel",
-      "stronghold"
+      "stronghold",
+      "places",
+      "travel",
+      "vacation"
     ],
     "moji": "🏰"
   },
@@ -7296,11 +7891,13 @@
     "unicode_alternates": [],
     "name": "european post office",
     "shortname": ":european_post_office:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "building"
+      "building",
+      "places",
+      "post office"
     ],
     "moji": "🏤"
   },
@@ -7318,7 +7915,10 @@
       "evergreen",
       "tree",
       "needles",
-      "christmas"
+      "christmas",
+      "holidays",
+      "camp",
+      "trees"
     ],
     "moji": "🌲"
   },
@@ -7329,11 +7929,13 @@
     ],
     "name": "heavy exclamation mark symbol",
     "shortname": ":exclamation:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "surprise"
+      "surprise",
+      "symbol",
+      "punctuation"
     ],
     "moji": "❗"
   },
@@ -7342,7 +7944,7 @@
     "unicode_alternates": [],
     "name": "expressionless face",
     "shortname": ":expressionless:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "-_-",
@@ -7356,7 +7958,11 @@
       "vapid",
       "without expression",
       "face",
-      "indifferent"
+      "indifferent",
+      "mad",
+      "smiley",
+      "neutral",
+      "emotion"
     ],
     "moji": "😑"
   },
@@ -7371,25 +7977,36 @@
     "keywords": [
       "look",
       "peek",
-      "watch"
-    ]
+      "watch",
+      "body",
+      "eyes"
+    ],
+    "moji": "👁"
   },
   "eye_in_speech_bubble": {
     "unicode": "1F441-1F5E8",
-    "unicode_alternates": "1f441-200d-1f5e8",
+    "unicode_alternates": [
+      "1F441-200D-1F5E8"
+    ],
     "name": "eye in speech bubble",
     "shortname": ":eye_in_speech_bubble:",
     "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "object",
+      "symbol",
+      "eyes",
+      "talk"
+    ],
+    "moji": "👁‍🗨"
   },
   "eyeglasses": {
     "unicode": "1F453",
     "unicode_alternates": [],
     "name": "eyeglasses",
     "shortname": ":eyeglasses:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7408,7 +8025,8 @@
       "vision",
       "see",
       "blurry",
-      "contacts"
+      "contacts",
+      "glasses"
     ],
     "moji": "👓"
   },
@@ -7417,27 +8035,110 @@
     "unicode_alternates": [],
     "name": "eyes",
     "shortname": ":eyes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "look",
       "peek",
       "stalk",
-      "watch"
+      "watch",
+      "body",
+      "eyes"
     ],
     "moji": "👀"
   },
+  "face_palm": {
+    "unicode": "1F926",
+    "unicode_alternates": [],
+    "name": "face palm",
+    "shortname": ":face_palm:",
+    "category": "people",
+    "aliases": [
+      ":facepalm:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦"
+  },
+  "face_palm_tone1": {
+    "unicode": "1F926-1F3FB",
+    "unicode_alternates": [],
+    "name": "face palm tone 1",
+    "shortname": ":face_palm_tone1:",
+    "category": "people",
+    "aliases": [
+      ":facepalm_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦🏻"
+  },
+  "face_palm_tone2": {
+    "unicode": "1F926-1F3FC",
+    "unicode_alternates": [],
+    "name": "face palm tone 2",
+    "shortname": ":face_palm_tone2:",
+    "category": "people",
+    "aliases": [
+      ":facepalm_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦🏼"
+  },
+  "face_palm_tone3": {
+    "unicode": "1F926-1F3FD",
+    "unicode_alternates": [],
+    "name": "face palm tone 3",
+    "shortname": ":face_palm_tone3:",
+    "category": "people",
+    "aliases": [
+      ":facepalm_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦🏽"
+  },
+  "face_palm_tone4": {
+    "unicode": "1F926-1F3FE",
+    "unicode_alternates": [],
+    "name": "face palm tone 4",
+    "shortname": ":face_palm_tone4:",
+    "category": "people",
+    "aliases": [
+      ":facepalm_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦🏾"
+  },
+  "face_palm_tone5": {
+    "unicode": "1F926-1F3FF",
+    "unicode_alternates": [],
+    "name": "face palm tone 5",
+    "shortname": ":face_palm_tone5:",
+    "category": "people",
+    "aliases": [
+      ":facepalm_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤦🏿"
+  },
   "factory": {
     "unicode": "1F3ED",
     "unicode_alternates": [],
     "name": "factory",
     "shortname": ":factory:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "building"
+      "building",
+      "places",
+      "travel",
+      "steam"
     ],
     "moji": "🏭"
   },
@@ -7467,7 +8168,7 @@
     "unicode_alternates": [],
     "name": "family",
     "shortname": ":family:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7479,13 +8180,12 @@
       "mother",
       "parents",
       "family",
-      "mother",
-      "father",
-      "child",
       "girl",
       "boy",
       "group",
-      "unit"
+      "unit",
+      "people",
+      "baby"
     ],
     "moji": "👪"
   },
@@ -7509,8 +8209,14 @@
       "gay",
       "homosexual",
       "man",
-      "boy"
-    ]
+      "boy",
+      "people",
+      "family",
+      "men",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👨‍👨‍👦"
   },
   "family_mmbb": {
     "unicode": "1F468-1F468-1F466-1F466",
@@ -7532,8 +8238,14 @@
       "gay",
       "homosexual",
       "man",
-      "boy"
-    ]
+      "boy",
+      "people",
+      "family",
+      "men",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👨‍👨‍👦‍👦"
   },
   "family_mmg": {
     "unicode": "1F468-1F468-1F467",
@@ -7555,8 +8267,14 @@
       "gay",
       "homosexual",
       "man",
-      "girl"
-    ]
+      "girl",
+      "people",
+      "family",
+      "men",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👨‍👨‍👧"
   },
   "family_mmgb": {
     "unicode": "1F468-1F468-1F467-1F466",
@@ -7579,8 +8297,14 @@
       "homosexual",
       "man",
       "girl",
-      "boy"
-    ]
+      "boy",
+      "people",
+      "family",
+      "men",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👨‍👨‍👧‍👦"
   },
   "family_mmgg": {
     "unicode": "1F468-1F468-1F467-1F467",
@@ -7602,8 +8326,14 @@
       "gay",
       "homosexual",
       "man",
-      "girl"
-    ]
+      "girl",
+      "people",
+      "family",
+      "men",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👨‍👨‍👧‍👧"
   },
   "family_mwbb": {
     "unicode": "1F468-1F469-1F466-1F466",
@@ -7626,8 +8356,12 @@
       "group",
       "unit",
       "man",
-      "woman"
-    ]
+      "woman",
+      "people",
+      "family",
+      "baby"
+    ],
+    "moji": "👨‍👩‍👦‍👦"
   },
   "family_mwg": {
     "unicode": "1F468-1F469-1F467",
@@ -7651,8 +8385,12 @@
       "group",
       "unit",
       "man",
-      "woman"
-    ]
+      "woman",
+      "people",
+      "family",
+      "baby"
+    ],
+    "moji": "👨‍👩‍👧"
   },
   "family_mwgb": {
     "unicode": "1F468-1F469-1F467-1F466",
@@ -7676,8 +8414,12 @@
       "group",
       "unit",
       "man",
-      "woman"
-    ]
+      "woman",
+      "people",
+      "family",
+      "baby"
+    ],
+    "moji": "👨‍👩‍👧‍👦"
   },
   "family_mwgg": {
     "unicode": "1F468-1F469-1F467-1F467",
@@ -7700,8 +8442,12 @@
       "group",
       "unit",
       "man",
-      "woman"
-    ]
+      "woman",
+      "people",
+      "family",
+      "baby"
+    ],
+    "moji": "👨‍👩‍👧‍👧"
   },
   "family_wwb": {
     "unicode": "1F469-1F469-1F466",
@@ -7724,8 +8470,14 @@
       "gay",
       "lesbian",
       "homosexual",
-      "woman"
-    ]
+      "woman",
+      "people",
+      "family",
+      "women",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👩‍👩‍👦"
   },
   "family_wwbb": {
     "unicode": "1F469-1F469-1F466-1F466",
@@ -7748,8 +8500,14 @@
       "lesbian",
       "homosexual",
       "woman",
-      "boy"
-    ]
+      "boy",
+      "people",
+      "family",
+      "women",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👩‍👩‍👦‍👦"
   },
   "family_wwg": {
     "unicode": "1F469-1F469-1F467",
@@ -7772,8 +8530,14 @@
       "unit",
       "gay",
       "lesbian",
-      "homosexual"
-    ]
+      "homosexual",
+      "people",
+      "family",
+      "women",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👩‍👩‍👧"
   },
   "family_wwgb": {
     "unicode": "1F469-1F469-1F467-1F466",
@@ -7797,8 +8561,14 @@
       "homosexual",
       "woman",
       "girl",
-      "boy"
-    ]
+      "boy",
+      "people",
+      "family",
+      "women",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👩‍👩‍👧‍👦"
   },
   "family_wwgg": {
     "unicode": "1F469-1F469-1F467-1F467",
@@ -7821,19 +8591,27 @@
       "lesbian",
       "homosexual",
       "woman",
-      "girl"
-    ]
+      "girl",
+      "people",
+      "family",
+      "women",
+      "baby",
+      "lgbt"
+    ],
+    "moji": "👩‍👩‍👧‍👧"
   },
   "fast_forward": {
     "unicode": "23E9",
     "unicode_alternates": [],
     "name": "black right-pointing double triangle",
     "shortname": ":fast_forward:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "⏩"
   },
@@ -7847,7 +8625,10 @@
     "aliases_ascii": [],
     "keywords": [
       "communication",
-      "technology"
+      "technology",
+      "electronics",
+      "work",
+      "office"
     ],
     "moji": "📠"
   },
@@ -7856,7 +8637,7 @@
     "unicode_alternates": [],
     "name": "fearful face",
     "shortname": ":fearful:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7867,8 +8648,10 @@
       "terrified",
       "fear",
       "fearful",
-      "scared",
-      "frightened"
+      "frightened",
+      "smiley",
+      "surprised",
+      "emotion"
     ],
     "moji": "😨"
   },
@@ -7888,16 +8671,12 @@
       "paw",
       "pet",
       "tracking",
-      "paw",
       "prints",
       "mark",
       "imprints",
       "footsteps",
-      "animal",
       "lion",
       "bear",
-      "dog",
-      "cat",
       "raccoon",
       "critter",
       "feet",
@@ -7905,12 +8684,25 @@
     ],
     "moji": "🐾"
   },
+  "fencer": {
+    "unicode": "1F93A",
+    "unicode_alternates": [],
+    "name": "fencer",
+    "shortname": ":fencer:",
+    "category": "activity",
+    "aliases": [
+      ":fencing:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤺"
+  },
   "ferris_wheel": {
     "unicode": "1F3A1",
     "unicode_alternates": [],
     "name": "ferris wheel",
     "shortname": ":ferris_wheel:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7923,13 +8715,16 @@
       "park",
       "fair",
       "ride",
-      "entertainment"
+      "entertainment",
+      "places",
+      "vacation",
+      "ferris wheel"
     ],
     "moji": "🎡"
   },
   "ferry": {
     "unicode": "26F4",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ferry",
     "shortname": ":ferry:",
     "category": "travel",
@@ -7938,33 +8733,44 @@
     "keywords": [
       "boat",
       "place",
-      "travel"
-    ]
+      "travel",
+      "transportation",
+      "vacation"
+    ],
+    "moji": "⛴"
   },
   "field_hockey": {
     "unicode": "1F3D1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "field hockey stick and ball",
     "shortname": ":field_hockey:",
     "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "ball",
+      "sport",
+      "hockey"
+    ],
+    "moji": "🏑"
   },
   "file_cabinet": {
     "unicode": "1F5C4",
     "unicode_alternates": [],
     "name": "file cabinet",
     "shortname": ":file_cabinet:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "folders",
       "office",
       "documents",
-      "storage"
-    ]
+      "storage",
+      "object",
+      "work"
+    ],
+    "moji": "🗄"
   },
   "file_folder": {
     "unicode": "1F4C1",
@@ -7975,7 +8781,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "documents"
+      "documents",
+      "work",
+      "office"
     ],
     "moji": "📁"
   },
@@ -7984,7 +8792,7 @@
     "unicode_alternates": [],
     "name": "film frames",
     "shortname": ":film_frames:",
-    "category": "activity",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -7993,95 +8801,96 @@
       "8mm",
       "16mm",
       "reel",
-      "celluloid"
-    ]
+      "celluloid",
+      "object",
+      "camera"
+    ],
+    "moji": "🎞"
   },
-  "finger_pointing_down": {
-    "unicode": "1F597",
+  "fingers_crossed": {
+    "unicode": "1F91E",
     "unicode_alternates": [],
-    "name": "white down pointing left hand index",
-    "shortname": ":finger_pointing_down:",
+    "name": "hand with first and index finger crossed",
+    "shortname": ":fingers_crossed:",
     "category": "people",
     "aliases": [
-      ":white_down_pointing_left_hand_index:"
+      ":hand_with_index_and_middle_finger_crossed:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "direction",
-      "finger",
-      "hand"
-    ]
+    "keywords": [],
+    "moji": "🤞"
   },
-  "finger_pointing_down2": {
-    "unicode": "1F59F",
+  "fingers_crossed_tone1": {
+    "unicode": "1F91E-1F3FB",
     "unicode_alternates": [],
-    "name": "sideways white down pointing index",
-    "shortname": ":finger_pointing_down2:",
+    "name": "hand with index and middle fingers crossed tone 1",
+    "shortname": ":fingers_crossed_tone1:",
     "category": "people",
     "aliases": [
-      ":sideways_white_down_pointing_index:"
+      ":hand_with_index_and_middle_fingers_crossed_tone1:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "direction",
-      "finger",
-      "hand"
-    ]
+    "keywords": [],
+    "moji": "🤞🏻"
   },
-  "finger_pointing_left": {
-    "unicode": "1F598",
+  "fingers_crossed_tone2": {
+    "unicode": "1F91E-1F3FC",
     "unicode_alternates": [],
-    "name": "sideways white left pointing index",
-    "shortname": ":finger_pointing_left:",
+    "name": "hand with index and middle fingers crossed tone 2",
+    "shortname": ":fingers_crossed_tone2:",
     "category": "people",
     "aliases": [
-      ":sideways_white_left_pointing_index:"
+      ":hand_with_index_and_middle_fingers_crossed_tone2:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "direction",
-      "finger",
-      "hand"
-    ]
+    "keywords": [],
+    "moji": "🤞🏼"
   },
-  "finger_pointing_right": {
-    "unicode": "1F599",
+  "fingers_crossed_tone3": {
+    "unicode": "1F91E-1F3FD",
     "unicode_alternates": [],
-    "name": "sideways white right pointing index",
-    "shortname": ":finger_pointing_right:",
+    "name": "hand with index and middle fingers crossed tone 3",
+    "shortname": ":fingers_crossed_tone3:",
     "category": "people",
     "aliases": [
-      ":sideways_white_right_pointing_index:"
+      ":hand_with_index_and_middle_fingers_crossed_tone3:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "direction",
-      "finger",
-      "hand"
-    ]
+    "keywords": [],
+    "moji": "🤞🏽"
   },
-  "finger_pointing_up": {
-    "unicode": "1F59E",
+  "fingers_crossed_tone4": {
+    "unicode": "1F91E-1F3FE",
     "unicode_alternates": [],
-    "name": "sideways white up pointing index",
-    "shortname": ":finger_pointing_up:",
+    "name": "hand with index and middle fingers crossed tone 4",
+    "shortname": ":fingers_crossed_tone4:",
     "category": "people",
     "aliases": [
-      ":sideways_white_up_pointing_index:"
+      ":hand_with_index_and_middle_fingers_crossed_tone4:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "direction",
-      "finger",
-      "hand"
-    ]
+    "keywords": [],
+    "moji": "🤞🏾"
+  },
+  "fingers_crossed_tone5": {
+    "unicode": "1F91E-1F3FF",
+    "unicode_alternates": [],
+    "name": "hand with index and middle fingers crossed tone 5",
+    "shortname": ":fingers_crossed_tone5:",
+    "category": "people",
+    "aliases": [
+      ":hand_with_index_and_middle_fingers_crossed_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤞🏿"
   },
   "fire": {
     "unicode": "1F525",
     "unicode_alternates": [],
     "name": "fire",
     "shortname": ":fire:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [
       ":flame:"
     ],
@@ -8089,7 +8898,8 @@
     "keywords": [
       "cook",
       "hot",
-      "flame"
+      "flame",
+      "wth"
     ],
     "moji": "🔥"
   },
@@ -8098,7 +8908,7 @@
     "unicode_alternates": [],
     "name": "fire engine",
     "shortname": ":fire_engine:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -8110,34 +8920,17 @@
       "engine",
       "truck",
       "emergency",
-      "medical"
+      "medical",
+      "911"
     ],
     "moji": "🚒"
   },
-  "fire_engine_oncoming": {
-    "unicode": "1F6F1",
-    "unicode_alternates": [],
-    "name": "oncoming fire engine",
-    "shortname": ":fire_engine_oncoming:",
-    "category": "travel_places",
-    "aliases": [
-      ":oncoming_fire_engine:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "transportation",
-      "vehicle",
-      "fighter",
-      "truck",
-      "emergency"
-    ]
-  },
   "fireworks": {
     "unicode": "1F386",
     "unicode_alternates": [],
     "name": "fireworks",
     "shortname": ":fireworks:",
-    "category": "objects",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -8154,10 +8947,24 @@
       "rocket",
       "sky",
       "idea",
-      "excitement"
+      "excitement",
+      "parties"
     ],
     "moji": "🎆"
   },
+  "first_place": {
+    "unicode": "1F947",
+    "unicode_alternates": [],
+    "name": "first place medal",
+    "shortname": ":first_place:",
+    "category": "activity",
+    "aliases": [
+      ":first_place_medal:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥇"
+  },
   "first_quarter_moon": {
     "unicode": "1F313",
     "unicode_alternates": [],
@@ -8174,7 +8981,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌓"
   },
@@ -8196,7 +9004,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌛"
   },
@@ -8211,7 +9020,8 @@
     "keywords": [
       "animal",
       "food",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐟"
   },
@@ -8220,7 +9030,7 @@
     "unicode_alternates": [],
     "name": "fish cake with swirl design",
     "shortname": ":fish_cake:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -8231,7 +9041,8 @@
       "swirl",
       "ramen",
       "noodles",
-      "naruto"
+      "naruto",
+      "sushi"
     ],
     "moji": "🍥"
   },
@@ -8240,7 +9051,7 @@
     "unicode_alternates": [],
     "name": "fishing pole and fish",
     "shortname": ":fishing_pole_and_fish:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -8248,7 +9059,9 @@
       "hobby",
       "fish",
       "fishing",
-      "pole"
+      "pole",
+      "vacation",
+      "sport"
     ],
     "moji": "🎣"
   },
@@ -8257,19 +9070,25 @@
     "unicode_alternates": [],
     "name": "raised fist",
     "shortname": ":fist:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fingers",
       "grasp",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "hi",
+      "fist bump",
+      "diversity",
+      "condolence"
     ],
     "moji": "✊"
   },
   "fist_tone1": {
     "unicode": "270A-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised fist tone 1",
     "shortname": ":fist_tone1:",
     "category": "people",
@@ -8279,11 +9098,12 @@
       "fingers",
       "grasp",
       "hand"
-    ]
+    ],
+    "moji": "✊🏻"
   },
   "fist_tone2": {
     "unicode": "270A-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised fist tone 2",
     "shortname": ":fist_tone2:",
     "category": "people",
@@ -8293,11 +9113,12 @@
       "fingers",
       "grasp",
       "hand"
-    ]
+    ],
+    "moji": "✊🏼"
   },
   "fist_tone3": {
     "unicode": "270A-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised fist tone 3",
     "shortname": ":fist_tone3:",
     "category": "people",
@@ -8307,11 +9128,12 @@
       "fingers",
       "grasp",
       "hand"
-    ]
+    ],
+    "moji": "✊🏽"
   },
   "fist_tone4": {
     "unicode": "270A-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised fist tone 4",
     "shortname": ":fist_tone4:",
     "category": "people",
@@ -8321,11 +9143,12 @@
       "fingers",
       "grasp",
       "hand"
-    ]
+    ],
+    "moji": "✊🏾"
   },
   "fist_tone5": {
     "unicode": "270A-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised fist tone 5",
     "shortname": ":fist_tone5:",
     "category": "people",
@@ -8335,7 +9158,8 @@
       "fingers",
       "grasp",
       "hand"
-    ]
+    ],
+    "moji": "✊🏿"
   },
   "five": {
     "moji": "5️⃣",
@@ -8343,15 +9167,18 @@
     "unicode_alternates": [
       "0035-FE0F-20E3"
     ],
-    "name": "digit five",
+    "name": "keycap digit five",
     "shortname": ":five:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
       "numbers",
-      "prime"
+      "prime",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "flag_ac": {
@@ -8367,8 +9194,10 @@
     "keywords": [
       "country",
       "nation",
-      "ac"
-    ]
+      "ac",
+      "flag"
+    ],
+    "moji": "🇦🇨"
   },
   "flag_ad": {
     "unicode": "1F1E6-1F1E9",
@@ -8383,8 +9212,10 @@
     "keywords": [
       "country",
       "nation",
-      "ad"
-    ]
+      "ad",
+      "flag"
+    ],
+    "moji": "🇦🇩"
   },
   "flag_ae": {
     "unicode": "1F1E6-1F1EA",
@@ -8399,8 +9230,10 @@
     "keywords": [
       "country",
       "nation",
-      "ae"
-    ]
+      "ae",
+      "flag"
+    ],
+    "moji": "🇦🇪"
   },
   "flag_af": {
     "unicode": "1F1E6-1F1EB",
@@ -8416,8 +9249,10 @@
       "country",
       "nation",
       "afghanestan",
-      "af"
-    ]
+      "af",
+      "flag"
+    ],
+    "moji": "🇦🇫"
   },
   "flag_ag": {
     "unicode": "1F1E6-1F1EC",
@@ -8432,8 +9267,10 @@
     "keywords": [
       "country",
       "nation",
-      "ag"
-    ]
+      "ag",
+      "flag"
+    ],
+    "moji": "🇦🇬"
   },
   "flag_ai": {
     "unicode": "1F1E6-1F1EE",
@@ -8448,8 +9285,10 @@
     "keywords": [
       "country",
       "nation",
-      "ai"
-    ]
+      "ai",
+      "flag"
+    ],
+    "moji": "🇦🇮"
   },
   "flag_al": {
     "unicode": "1F1E6-1F1F1",
@@ -8465,8 +9304,10 @@
       "country",
       "nation",
       "shqiperia",
-      "al"
-    ]
+      "al",
+      "flag"
+    ],
+    "moji": "🇦🇱"
   },
   "flag_am": {
     "unicode": "1F1E6-1F1F2",
@@ -8482,8 +9323,10 @@
       "country",
       "nation",
       "hayastan",
-      "am"
-    ]
+      "am",
+      "flag"
+    ],
+    "moji": "🇦🇲"
   },
   "flag_ao": {
     "unicode": "1F1E6-1F1F4",
@@ -8498,12 +9341,14 @@
     "keywords": [
       "country",
       "nation",
-      "ao"
-    ]
+      "ao",
+      "flag"
+    ],
+    "moji": "🇦🇴"
   },
   "flag_aq": {
     "unicode": "1F1E6-1F1F6",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "antarctica",
     "shortname": ":flag_aq:",
     "category": "flags",
@@ -8511,7 +9356,11 @@
       ":aq:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇦🇶"
   },
   "flag_ar": {
     "unicode": "1F1E6-1F1F7",
@@ -8526,12 +9375,14 @@
     "keywords": [
       "country",
       "nation",
-      "ar"
-    ]
+      "ar",
+      "flag"
+    ],
+    "moji": "🇦🇷"
   },
   "flag_as": {
     "unicode": "1F1E6-1F1F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "american samoa",
     "shortname": ":flag_as:",
     "category": "flags",
@@ -8539,7 +9390,11 @@
       ":as:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇦🇸"
   },
   "flag_at": {
     "unicode": "1F1E6-1F1F9",
@@ -8556,8 +9411,10 @@
       "nation",
       "&ouml;sterreich",
       "osterreich",
-      "at"
-    ]
+      "at",
+      "flag"
+    ],
+    "moji": "🇦🇹"
   },
   "flag_au": {
     "unicode": "1F1E6-1F1FA",
@@ -8572,8 +9429,10 @@
     "keywords": [
       "country",
       "nation",
-      "au"
-    ]
+      "au",
+      "flag"
+    ],
+    "moji": "🇦🇺"
   },
   "flag_aw": {
     "unicode": "1F1E6-1F1FC",
@@ -8588,12 +9447,14 @@
     "keywords": [
       "country",
       "nation",
-      "aw"
-    ]
+      "aw",
+      "flag"
+    ],
+    "moji": "🇦🇼"
   },
   "flag_ax": {
     "unicode": "1F1E6-1F1FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "åland islands",
     "shortname": ":flag_ax:",
     "category": "flags",
@@ -8601,7 +9462,11 @@
       ":ax:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇦🇽"
   },
   "flag_az": {
     "unicode": "1F1E6-1F1FF",
@@ -8617,8 +9482,10 @@
       "country",
       "nation",
       "azarbaycan",
-      "az"
-    ]
+      "az",
+      "flag"
+    ],
+    "moji": "🇦🇿"
   },
   "flag_ba": {
     "unicode": "1F1E7-1F1E6",
@@ -8634,8 +9501,10 @@
       "country",
       "nation",
       "bosna i hercegovina",
-      "ba"
-    ]
+      "ba",
+      "flag"
+    ],
+    "moji": "🇧🇦"
   },
   "flag_bb": {
     "unicode": "1F1E7-1F1E7",
@@ -8650,8 +9519,10 @@
     "keywords": [
       "country",
       "nation",
-      "bb"
-    ]
+      "bb",
+      "flag"
+    ],
+    "moji": "🇧🇧"
   },
   "flag_bd": {
     "unicode": "1F1E7-1F1E9",
@@ -8666,8 +9537,10 @@
     "keywords": [
       "country",
       "nation",
-      "bd"
-    ]
+      "bd",
+      "flag"
+    ],
+    "moji": "🇧🇩"
   },
   "flag_be": {
     "unicode": "1F1E7-1F1EA",
@@ -8684,8 +9557,10 @@
       "nation",
       "belgique",
       "belgie",
-      "be"
-    ]
+      "be",
+      "flag"
+    ],
+    "moji": "🇧🇪"
   },
   "flag_bf": {
     "unicode": "1F1E7-1F1EB",
@@ -8700,8 +9575,10 @@
     "keywords": [
       "country",
       "nation",
-      "bf"
-    ]
+      "bf",
+      "flag"
+    ],
+    "moji": "🇧🇫"
   },
   "flag_bg": {
     "unicode": "1F1E7-1F1EC",
@@ -8716,8 +9593,10 @@
     "keywords": [
       "country",
       "nation",
-      "bg"
-    ]
+      "bg",
+      "flag"
+    ],
+    "moji": "🇧🇬"
   },
   "flag_bh": {
     "unicode": "1F1E7-1F1ED",
@@ -8733,8 +9612,10 @@
       "country",
       "nation",
       "al bahrayn",
-      "bh"
-    ]
+      "bh",
+      "flag"
+    ],
+    "moji": "🇧🇭"
   },
   "flag_bi": {
     "unicode": "1F1E7-1F1EE",
@@ -8749,8 +9630,10 @@
     "keywords": [
       "country",
       "nation",
-      "bi"
-    ]
+      "bi",
+      "flag"
+    ],
+    "moji": "🇧🇮"
   },
   "flag_bj": {
     "unicode": "1F1E7-1F1EF",
@@ -8765,12 +9648,14 @@
     "keywords": [
       "country",
       "nation",
-      "bj"
-    ]
+      "bj",
+      "flag"
+    ],
+    "moji": "🇧🇯"
   },
   "flag_bl": {
     "unicode": "1F1E7-1F1F1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "saint barthélemy",
     "shortname": ":flag_bl:",
     "category": "flags",
@@ -8778,22 +9663,28 @@
       ":bl:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇧🇱"
   },
   "flag_black": {
     "unicode": "1F3F4",
     "unicode_alternates": [],
     "name": "waving black flag",
     "shortname": ":flag_black:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":waving_black_flag:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "symbol",
-      "signal"
-    ]
+      "signal",
+      "object"
+    ],
+    "moji": "🏴"
   },
   "flag_bm": {
     "unicode": "1F1E7-1F1F2",
@@ -8808,8 +9699,10 @@
     "keywords": [
       "country",
       "nation",
-      "bm"
-    ]
+      "bm",
+      "flag"
+    ],
+    "moji": "🇧🇲"
   },
   "flag_bn": {
     "unicode": "1F1E7-1F1F3",
@@ -8824,8 +9717,10 @@
     "keywords": [
       "country",
       "nation",
-      "bn"
-    ]
+      "bn",
+      "flag"
+    ],
+    "moji": "🇧🇳"
   },
   "flag_bo": {
     "unicode": "1F1E7-1F1F4",
@@ -8840,12 +9735,14 @@
     "keywords": [
       "country",
       "nation",
-      "bo"
-    ]
+      "bo",
+      "flag"
+    ],
+    "moji": "🇧🇴"
   },
   "flag_bq": {
     "unicode": "1F1E7-1F1F6",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "caribbean netherlands",
     "shortname": ":flag_bq:",
     "category": "flags",
@@ -8853,7 +9750,11 @@
       ":bq:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇧🇶"
   },
   "flag_br": {
     "unicode": "1F1E7-1F1F7",
@@ -8869,8 +9770,10 @@
       "country",
       "nation",
       "brasil",
-      "br"
-    ]
+      "br",
+      "flag"
+    ],
+    "moji": "🇧🇷"
   },
   "flag_bs": {
     "unicode": "1F1E7-1F1F8",
@@ -8885,8 +9788,10 @@
     "keywords": [
       "country",
       "nation",
-      "bs"
-    ]
+      "bs",
+      "flag"
+    ],
+    "moji": "🇧🇸"
   },
   "flag_bt": {
     "unicode": "1F1E7-1F1F9",
@@ -8901,12 +9806,14 @@
     "keywords": [
       "country",
       "nation",
-      "bt"
-    ]
+      "bt",
+      "flag"
+    ],
+    "moji": "🇧🇹"
   },
   "flag_bv": {
     "unicode": "1F1E7-1F1FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "bouvet island",
     "shortname": ":flag_bv:",
     "category": "flags",
@@ -8914,7 +9821,11 @@
       ":bv:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇧🇻"
   },
   "flag_bw": {
     "unicode": "1F1E7-1F1FC",
@@ -8929,8 +9840,10 @@
     "keywords": [
       "country",
       "nation",
-      "bw"
-    ]
+      "bw",
+      "flag"
+    ],
+    "moji": "🇧🇼"
   },
   "flag_by": {
     "unicode": "1F1E7-1F1FE",
@@ -8946,8 +9859,10 @@
       "country",
       "nation",
       "byelarus",
-      "by"
-    ]
+      "by",
+      "flag"
+    ],
+    "moji": "🇧🇾"
   },
   "flag_bz": {
     "unicode": "1F1E7-1F1FF",
@@ -8962,8 +9877,10 @@
     "keywords": [
       "country",
       "nation",
-      "bz"
-    ]
+      "bz",
+      "flag"
+    ],
+    "moji": "🇧🇿"
   },
   "flag_ca": {
     "unicode": "1F1E8-1F1E6",
@@ -8978,12 +9895,14 @@
     "keywords": [
       "country",
       "nation",
-      "ca"
-    ]
+      "ca",
+      "flag"
+    ],
+    "moji": "🇨🇦"
   },
   "flag_cc": {
     "unicode": "1F1E8-1F1E8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "cocos (keeling) islands",
     "shortname": ":flag_cc:",
     "category": "flags",
@@ -8991,7 +9910,11 @@
       ":cc:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇨🇨"
   },
   "flag_cd": {
     "unicode": "1F1E8-1F1E9",
@@ -9008,8 +9931,10 @@
       "nation",
       "r&eacute;publique d&eacute;mocratique du congo",
       "republique democratique du congo",
-      "cd"
-    ]
+      "cd",
+      "flag"
+    ],
+    "moji": "🇨🇩"
   },
   "flag_cf": {
     "unicode": "1F1E8-1F1EB",
@@ -9024,8 +9949,10 @@
     "keywords": [
       "country",
       "nation",
-      "cf"
-    ]
+      "cf",
+      "flag"
+    ],
+    "moji": "🇨🇫"
   },
   "flag_cg": {
     "unicode": "1F1E8-1F1EC",
@@ -9040,8 +9967,10 @@
     "keywords": [
       "country",
       "nation",
-      "cg"
-    ]
+      "cg",
+      "flag"
+    ],
+    "moji": "🇨🇬"
   },
   "flag_ch": {
     "unicode": "1F1E8-1F1ED",
@@ -9056,8 +9985,11 @@
     "keywords": [
       "country",
       "nation",
-      "swiss"
-    ]
+      "swiss",
+      "neutral",
+      "flag"
+    ],
+    "moji": "🇨🇭"
   },
   "flag_ci": {
     "unicode": "1F1E8-1F1EE",
@@ -9072,12 +10004,14 @@
     "keywords": [
       "country",
       "nation",
-      "ci"
-    ]
+      "ci",
+      "flag"
+    ],
+    "moji": "🇨🇮"
   },
   "flag_ck": {
     "unicode": "1F1E8-1F1F0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "cook islands",
     "shortname": ":flag_ck:",
     "category": "flags",
@@ -9085,7 +10019,11 @@
       ":ck:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇨🇰"
   },
   "flag_cl": {
     "unicode": "1F1E8-1F1F1",
@@ -9100,8 +10038,10 @@
     "keywords": [
       "country",
       "nation",
-      "cl"
-    ]
+      "cl",
+      "flag"
+    ],
+    "moji": "🇨🇱"
   },
   "flag_cm": {
     "unicode": "1F1E8-1F1F2",
@@ -9116,8 +10056,10 @@
     "keywords": [
       "country",
       "nation",
-      "cm"
-    ]
+      "cm",
+      "flag"
+    ],
+    "moji": "🇨🇲"
   },
   "flag_cn": {
     "unicode": "1F1E8-1F1F3",
@@ -9135,8 +10077,10 @@
       "zhong guo",
       "country",
       "nation",
-      "cn"
-    ]
+      "cn",
+      "flag"
+    ],
+    "moji": "🇨🇳"
   },
   "flag_co": {
     "unicode": "1F1E8-1F1F4",
@@ -9151,12 +10095,14 @@
     "keywords": [
       "country",
       "nation",
-      "co"
-    ]
+      "co",
+      "flag"
+    ],
+    "moji": "🇨🇴"
   },
   "flag_cp": {
     "unicode": "1F1E8-1F1F5",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "clipperton island",
     "shortname": ":flag_cp:",
     "category": "flags",
@@ -9164,7 +10110,11 @@
       ":cp:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇨🇵"
   },
   "flag_cr": {
     "unicode": "1F1E8-1F1F7",
@@ -9179,8 +10129,10 @@
     "keywords": [
       "country",
       "nation",
-      "cr"
-    ]
+      "cr",
+      "flag"
+    ],
+    "moji": "🇨🇷"
   },
   "flag_cu": {
     "unicode": "1F1E8-1F1FA",
@@ -9195,8 +10147,10 @@
     "keywords": [
       "country",
       "nation",
-      "cu"
-    ]
+      "cu",
+      "flag"
+    ],
+    "moji": "🇨🇺"
   },
   "flag_cv": {
     "unicode": "1F1E8-1F1FB",
@@ -9212,12 +10166,14 @@
       "country",
       "nation",
       "cabo verde",
-      "cv"
-    ]
+      "cv",
+      "flag"
+    ],
+    "moji": "🇨🇻"
   },
   "flag_cw": {
     "unicode": "1F1E8-1F1FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "curaçao",
     "shortname": ":flag_cw:",
     "category": "flags",
@@ -9225,11 +10181,15 @@
       ":cw:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇨🇼"
   },
   "flag_cx": {
     "unicode": "1F1E8-1F1FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "christmas island",
     "shortname": ":flag_cx:",
     "category": "flags",
@@ -9237,7 +10197,11 @@
       ":cx:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇨🇽"
   },
   "flag_cy": {
     "unicode": "1F1E8-1F1FE",
@@ -9254,8 +10218,10 @@
       "nation",
       "kibris",
       "kypros",
-      "cy"
-    ]
+      "cy",
+      "flag"
+    ],
+    "moji": "🇨🇾"
   },
   "flag_cz": {
     "unicode": "1F1E8-1F1FF",
@@ -9271,8 +10237,10 @@
       "country",
       "nation",
       "ceska republika",
-      "cz"
-    ]
+      "cz",
+      "flag"
+    ],
+    "moji": "🇨🇿"
   },
   "flag_de": {
     "unicode": "1F1E9-1F1EA",
@@ -9289,20 +10257,26 @@
       "nation",
       "deutschland",
       "country",
-      "de"
-    ]
+      "de",
+      "flag"
+    ],
+    "moji": "🇩🇪"
   },
   "flag_dg": {
     "unicode": "1F1E9-1F1EC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "diego garcia",
     "shortname": ":flag_dg:",
     "category": "flags",
     "aliases": [
       ":dg:"
     ],
-    "aliases_ascii": [],
-    "keywords": []
+    "aliases_ascii": [],
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇩🇬"
   },
   "flag_dj": {
     "unicode": "1F1E9-1F1EF",
@@ -9317,8 +10291,10 @@
     "keywords": [
       "country",
       "nation",
-      "dj"
-    ]
+      "dj",
+      "flag"
+    ],
+    "moji": "🇩🇯"
   },
   "flag_dk": {
     "unicode": "1F1E9-1F1F0",
@@ -9334,8 +10310,10 @@
       "country",
       "nation",
       "danmark",
-      "dk"
-    ]
+      "dk",
+      "flag"
+    ],
+    "moji": "🇩🇰"
   },
   "flag_dm": {
     "unicode": "1F1E9-1F1F2",
@@ -9350,8 +10328,10 @@
     "keywords": [
       "country",
       "nation",
-      "dm"
-    ]
+      "dm",
+      "flag"
+    ],
+    "moji": "🇩🇲"
   },
   "flag_do": {
     "unicode": "1F1E9-1F1F4",
@@ -9366,8 +10346,10 @@
     "keywords": [
       "country",
       "nation",
-      "do"
-    ]
+      "do",
+      "flag"
+    ],
+    "moji": "🇩🇴"
   },
   "flag_dz": {
     "unicode": "1F1E9-1F1FF",
@@ -9384,12 +10366,14 @@
       "nation",
       "al jaza'ir",
       "al jazair",
-      "dz"
-    ]
+      "dz",
+      "flag"
+    ],
+    "moji": "🇩🇿"
   },
   "flag_ea": {
     "unicode": "1F1EA-1F1E6",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ceuta, melilla",
     "shortname": ":flag_ea:",
     "category": "flags",
@@ -9397,7 +10381,11 @@
       ":ea:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇪🇦"
   },
   "flag_ec": {
     "unicode": "1F1EA-1F1E8",
@@ -9412,8 +10400,10 @@
     "keywords": [
       "country",
       "nation",
-      "ec"
-    ]
+      "ec",
+      "flag"
+    ],
+    "moji": "🇪🇨"
   },
   "flag_ee": {
     "unicode": "1F1EA-1F1EA",
@@ -9429,8 +10419,10 @@
       "country",
       "nation",
       "eesti vabariik",
-      "ee"
-    ]
+      "ee",
+      "flag"
+    ],
+    "moji": "🇪🇪"
   },
   "flag_eg": {
     "unicode": "1F1EA-1F1EC",
@@ -9446,8 +10438,10 @@
       "country",
       "nation",
       "misr",
-      "eg"
-    ]
+      "eg",
+      "flag"
+    ],
+    "moji": "🇪🇬"
   },
   "flag_eh": {
     "unicode": "1F1EA-1F1ED",
@@ -9465,8 +10459,10 @@
       "aṣ-Ṣaḥrā’ al-gharbīyah",
       "sahra",
       "gharbiyah",
-      "eh"
-    ]
+      "eh",
+      "flag"
+    ],
+    "moji": "🇪🇭"
   },
   "flag_er": {
     "unicode": "1F1EA-1F1F7",
@@ -9482,8 +10478,10 @@
       "country",
       "nation",
       "hagere ertra",
-      "er"
-    ]
+      "er",
+      "flag"
+    ],
+    "moji": "🇪🇷"
   },
   "flag_es": {
     "unicode": "1F1EA-1F1F8",
@@ -9500,8 +10498,10 @@
       "espa&ntilde;a",
       "country",
       "espana",
-      "es"
-    ]
+      "es",
+      "flag"
+    ],
+    "moji": "🇪🇸"
   },
   "flag_et": {
     "unicode": "1F1EA-1F1F9",
@@ -9518,12 +10518,14 @@
       "nation",
       "ityop'iya",
       "ityopiya",
-      "et"
-    ]
+      "et",
+      "flag"
+    ],
+    "moji": "🇪🇹"
   },
   "flag_eu": {
     "unicode": "1F1EA-1F1FA",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "european union",
     "shortname": ":flag_eu:",
     "category": "flags",
@@ -9531,7 +10533,11 @@
       ":eu:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇪🇺"
   },
   "flag_fi": {
     "unicode": "1F1EB-1F1EE",
@@ -9547,8 +10553,10 @@
       "country",
       "nation",
       "suomen tasavalta",
-      "fi"
-    ]
+      "fi",
+      "flag"
+    ],
+    "moji": "🇫🇮"
   },
   "flag_fj": {
     "unicode": "1F1EB-1F1EF",
@@ -9563,8 +10571,10 @@
     "keywords": [
       "country",
       "nation",
-      "fj"
-    ]
+      "fj",
+      "flag"
+    ],
+    "moji": "🇫🇯"
   },
   "flag_fk": {
     "unicode": "1F1EB-1F1F0",
@@ -9580,8 +10590,10 @@
       "country",
       "nation",
       "islas malvinas",
-      "fk"
-    ]
+      "fk",
+      "flag"
+    ],
+    "moji": "🇫🇰"
   },
   "flag_fm": {
     "unicode": "1F1EB-1F1F2",
@@ -9596,8 +10608,10 @@
     "keywords": [
       "country",
       "nation",
-      "fm"
-    ]
+      "fm",
+      "flag"
+    ],
+    "moji": "🇫🇲"
   },
   "flag_fo": {
     "unicode": "1F1EB-1F1F4",
@@ -9613,8 +10627,10 @@
       "country",
       "nation",
       "foroyar",
-      "fo"
-    ]
+      "fo",
+      "flag"
+    ],
+    "moji": "🇫🇴"
   },
   "flag_fr": {
     "unicode": "1F1EB-1F1F7",
@@ -9630,8 +10646,10 @@
       "french",
       "nation",
       "country",
-      "fr"
-    ]
+      "fr",
+      "flag"
+    ],
+    "moji": "🇫🇷"
   },
   "flag_ga": {
     "unicode": "1F1EC-1F1E6",
@@ -9646,8 +10664,10 @@
     "keywords": [
       "country",
       "nation",
-      "ga"
-    ]
+      "ga",
+      "flag"
+    ],
+    "moji": "🇬🇦"
   },
   "flag_gb": {
     "unicode": "1F1EC-1F1E7",
@@ -9666,8 +10686,10 @@
       "nation",
       "united kingdom",
       "england",
-      "country"
-    ]
+      "country",
+      "flag"
+    ],
+    "moji": "🇬🇧"
   },
   "flag_gd": {
     "unicode": "1F1EC-1F1E9",
@@ -9682,8 +10704,10 @@
     "keywords": [
       "country",
       "nation",
-      "gd"
-    ]
+      "gd",
+      "flag"
+    ],
+    "moji": "🇬🇩"
   },
   "flag_ge": {
     "unicode": "1F1EC-1F1EA",
@@ -9700,12 +10724,14 @@
       "nation",
       "sak'art'velo",
       "sakartvelo",
-      "ge"
-    ]
+      "ge",
+      "flag"
+    ],
+    "moji": "🇬🇪"
   },
   "flag_gf": {
     "unicode": "1F1EC-1F1EB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "french guiana",
     "shortname": ":flag_gf:",
     "category": "flags",
@@ -9713,11 +10739,15 @@
       ":gf:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇬🇫"
   },
   "flag_gg": {
     "unicode": "1F1EC-1F1EC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guernsey",
     "shortname": ":flag_gg:",
     "category": "flags",
@@ -9725,7 +10755,11 @@
       ":gg:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇬🇬"
   },
   "flag_gh": {
     "unicode": "1F1EC-1F1ED",
@@ -9740,8 +10774,10 @@
     "keywords": [
       "country",
       "nation",
-      "gh"
-    ]
+      "gh",
+      "flag"
+    ],
+    "moji": "🇬🇭"
   },
   "flag_gi": {
     "unicode": "1F1EC-1F1EE",
@@ -9756,8 +10792,10 @@
     "keywords": [
       "country",
       "nation",
-      "gi"
-    ]
+      "gi",
+      "flag"
+    ],
+    "moji": "🇬🇮"
   },
   "flag_gl": {
     "unicode": "1F1EC-1F1F1",
@@ -9773,8 +10811,10 @@
       "country",
       "nation",
       "kalaallit nunaat",
-      "gl"
-    ]
+      "gl",
+      "flag"
+    ],
+    "moji": "🇬🇱"
   },
   "flag_gm": {
     "unicode": "1F1EC-1F1F2",
@@ -9789,8 +10829,10 @@
     "keywords": [
       "country",
       "nation",
-      "gm"
-    ]
+      "gm",
+      "flag"
+    ],
+    "moji": "🇬🇲"
   },
   "flag_gn": {
     "unicode": "1F1EC-1F1F3",
@@ -9806,12 +10848,14 @@
       "country",
       "nation",
       "guinee",
-      "gn"
-    ]
+      "gn",
+      "flag"
+    ],
+    "moji": "🇬🇳"
   },
   "flag_gp": {
     "unicode": "1F1EC-1F1F5",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guadeloupe",
     "shortname": ":flag_gp:",
     "category": "flags",
@@ -9819,7 +10863,11 @@
       ":gp:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇬🇵"
   },
   "flag_gq": {
     "unicode": "1F1EC-1F1F6",
@@ -9835,8 +10883,10 @@
       "country",
       "nation",
       "guinea ecuatorial",
-      "gq"
-    ]
+      "gq",
+      "flag"
+    ],
+    "moji": "🇬🇶"
   },
   "flag_gr": {
     "unicode": "1F1EC-1F1F7",
@@ -9853,12 +10903,14 @@
       "nation",
       "ellas",
       "ellada",
-      "gr"
-    ]
+      "gr",
+      "flag"
+    ],
+    "moji": "🇬🇷"
   },
   "flag_gs": {
     "unicode": "1F1EC-1F1F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "south georgia",
     "shortname": ":flag_gs:",
     "category": "flags",
@@ -9866,7 +10918,11 @@
       ":gs:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇬🇸"
   },
   "flag_gt": {
     "unicode": "1F1EC-1F1F9",
@@ -9881,8 +10937,10 @@
     "keywords": [
       "country",
       "nation",
-      "gt"
-    ]
+      "gt",
+      "flag"
+    ],
+    "moji": "🇬🇹"
   },
   "flag_gu": {
     "unicode": "1F1EC-1F1FA",
@@ -9897,8 +10955,10 @@
     "keywords": [
       "country",
       "nation",
-      "gu"
-    ]
+      "gu",
+      "flag"
+    ],
+    "moji": "🇬🇺"
   },
   "flag_gw": {
     "unicode": "1F1EC-1F1FC",
@@ -9915,8 +10975,10 @@
       "nation",
       "guine-bissau",
       "guine bissau",
-      "gw"
-    ]
+      "gw",
+      "flag"
+    ],
+    "moji": "🇬🇼"
   },
   "flag_gy": {
     "unicode": "1F1EC-1F1FE",
@@ -9931,8 +10993,10 @@
     "keywords": [
       "country",
       "nation",
-      "gy"
-    ]
+      "gy",
+      "flag"
+    ],
+    "moji": "🇬🇾"
   },
   "flag_hk": {
     "unicode": "1F1ED-1F1F0",
@@ -9948,12 +11012,14 @@
       "country",
       "nation",
       "xianggang",
-      "hk"
-    ]
+      "hk",
+      "flag"
+    ],
+    "moji": "🇭🇰"
   },
   "flag_hm": {
     "unicode": "1F1ED-1F1F2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "heard island and mcdonald islands",
     "shortname": ":flag_hm:",
     "category": "flags",
@@ -9961,7 +11027,11 @@
       ":hm:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇭🇲"
   },
   "flag_hn": {
     "unicode": "1F1ED-1F1F3",
@@ -9976,8 +11046,10 @@
     "keywords": [
       "country",
       "nation",
-      "hn"
-    ]
+      "hn",
+      "flag"
+    ],
+    "moji": "🇭🇳"
   },
   "flag_hr": {
     "unicode": "1F1ED-1F1F7",
@@ -9993,8 +11065,10 @@
       "country",
       "nation",
       "hrvatska",
-      "hr"
-    ]
+      "hr",
+      "flag"
+    ],
+    "moji": "🇭🇷"
   },
   "flag_ht": {
     "unicode": "1F1ED-1F1F9",
@@ -10009,8 +11083,10 @@
     "keywords": [
       "country",
       "nation",
-      "ht"
-    ]
+      "ht",
+      "flag"
+    ],
+    "moji": "🇭🇹"
   },
   "flag_hu": {
     "unicode": "1F1ED-1F1FA",
@@ -10026,12 +11102,14 @@
       "country",
       "nation",
       "magyarorszag",
-      "hu"
-    ]
+      "hu",
+      "flag"
+    ],
+    "moji": "🇭🇺"
   },
   "flag_ic": {
     "unicode": "1F1EE-1F1E8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "canary islands",
     "shortname": ":flag_ic:",
     "category": "flags",
@@ -10039,7 +11117,11 @@
       ":ic:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇮🇨"
   },
   "flag_id": {
     "unicode": "1F1EE-1F1E9",
@@ -10054,8 +11136,10 @@
     "keywords": [
       "country",
       "nation",
-      "id"
-    ]
+      "id",
+      "flag"
+    ],
+    "moji": "🇮🇩"
   },
   "flag_ie": {
     "unicode": "1F1EE-1F1EA",
@@ -10072,8 +11156,10 @@
       "nation",
       "&eacute;ire",
       "eire",
-      "ie"
-    ]
+      "ie",
+      "flag"
+    ],
+    "moji": "🇮🇪"
   },
   "flag_il": {
     "unicode": "1F1EE-1F1F1",
@@ -10090,12 +11176,15 @@
       "nation",
       "yisra'el",
       "yisrael",
-      "il"
-    ]
+      "il",
+      "jew",
+      "flag"
+    ],
+    "moji": "🇮🇱"
   },
   "flag_im": {
     "unicode": "1F1EE-1F1F2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "isle of man",
     "shortname": ":flag_im:",
     "category": "flags",
@@ -10103,7 +11192,11 @@
       ":im:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇮🇲"
   },
   "flag_in": {
     "unicode": "1F1EE-1F1F3",
@@ -10119,12 +11212,14 @@
       "country",
       "nation",
       "bharat",
-      "in"
-    ]
+      "in",
+      "flag"
+    ],
+    "moji": "🇮🇳"
   },
   "flag_io": {
     "unicode": "1F1EE-1F1F4",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "british indian ocean territory",
     "shortname": ":flag_io:",
     "category": "flags",
@@ -10132,7 +11227,11 @@
       ":io:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇮🇴"
   },
   "flag_iq": {
     "unicode": "1F1EE-1F1F6",
@@ -10147,8 +11246,10 @@
     "keywords": [
       "country",
       "nation",
-      "iq"
-    ]
+      "iq",
+      "flag"
+    ],
+    "moji": "🇮🇶"
   },
   "flag_ir": {
     "unicode": "1F1EE-1F1F7",
@@ -10163,8 +11264,10 @@
     "keywords": [
       "country",
       "nation",
-      "ir"
-    ]
+      "ir",
+      "flag"
+    ],
+    "moji": "🇮🇷"
   },
   "flag_is": {
     "unicode": "1F1EE-1F1F8",
@@ -10180,8 +11283,10 @@
       "country",
       "nation",
       "lyoveldio island",
-      "is"
-    ]
+      "is",
+      "flag"
+    ],
+    "moji": "🇮🇸"
   },
   "flag_it": {
     "unicode": "1F1EE-1F1F9",
@@ -10197,8 +11302,11 @@
       "italia",
       "country",
       "nation",
-      "it"
-    ]
+      "it",
+      "italian",
+      "flag"
+    ],
+    "moji": "🇮🇹"
   },
   "flag_je": {
     "unicode": "1F1EF-1F1EA",
@@ -10213,8 +11321,10 @@
     "keywords": [
       "country",
       "nation",
-      "je"
-    ]
+      "je",
+      "flag"
+    ],
+    "moji": "🇯🇪"
   },
   "flag_jm": {
     "unicode": "1F1EF-1F1F2",
@@ -10229,8 +11339,10 @@
     "keywords": [
       "country",
       "nation",
-      "jm"
-    ]
+      "jm",
+      "flag"
+    ],
+    "moji": "🇯🇲"
   },
   "flag_jo": {
     "unicode": "1F1EF-1F1F4",
@@ -10246,8 +11358,10 @@
       "country",
       "nation",
       "al urdun",
-      "jo"
-    ]
+      "jo",
+      "flag"
+    ],
+    "moji": "🇯🇴"
   },
   "flag_jp": {
     "unicode": "1F1EF-1F1F5",
@@ -10263,8 +11377,11 @@
       "nation",
       "nippon",
       "country",
-      "jp"
-    ]
+      "jp",
+      "japan",
+      "flag"
+    ],
+    "moji": "🇯🇵"
   },
   "flag_ke": {
     "unicode": "1F1F0-1F1EA",
@@ -10279,8 +11396,10 @@
     "keywords": [
       "country",
       "nation",
-      "ke"
-    ]
+      "ke",
+      "flag"
+    ],
+    "moji": "🇰🇪"
   },
   "flag_kg": {
     "unicode": "1F1F0-1F1EC",
@@ -10296,8 +11415,10 @@
       "country",
       "nation",
       "kyrgyz respublikasy",
-      "kg"
-    ]
+      "kg",
+      "flag"
+    ],
+    "moji": "🇰🇬"
   },
   "flag_kh": {
     "unicode": "1F1F0-1F1ED",
@@ -10313,8 +11434,10 @@
       "country",
       "nation",
       "kampuchea",
-      "kh"
-    ]
+      "kh",
+      "flag"
+    ],
+    "moji": "🇰🇭"
   },
   "flag_ki": {
     "unicode": "1F1F0-1F1EE",
@@ -10331,8 +11454,10 @@
       "nation",
       "kiribati",
       "kiribas",
-      "ki"
-    ]
+      "ki",
+      "flag"
+    ],
+    "moji": "🇰🇮"
   },
   "flag_km": {
     "unicode": "1F1F0-1F1F2",
@@ -10347,8 +11472,10 @@
     "keywords": [
       "country",
       "nation",
-      "km"
-    ]
+      "km",
+      "flag"
+    ],
+    "moji": "🇰🇲"
   },
   "flag_kn": {
     "unicode": "1F1F0-1F1F3",
@@ -10363,8 +11490,10 @@
     "keywords": [
       "country",
       "nation",
-      "kn"
-    ]
+      "kn",
+      "flag"
+    ],
+    "moji": "🇰🇳"
   },
   "flag_kp": {
     "unicode": "1F1F0-1F1F5",
@@ -10379,8 +11508,10 @@
     "keywords": [
       "country",
       "nation",
-      "kp"
-    ]
+      "kp",
+      "flag"
+    ],
+    "moji": "🇰🇵"
   },
   "flag_kr": {
     "unicode": "1F1F0-1F1F7",
@@ -10396,8 +11527,10 @@
       "nation",
       "country",
       "south korea",
-      "kr"
-    ]
+      "kr",
+      "flag"
+    ],
+    "moji": "🇰🇷"
   },
   "flag_kw": {
     "unicode": "1F1F0-1F1FC",
@@ -10413,8 +11546,10 @@
       "country",
       "nation",
       "al kuwayt",
-      "kw"
-    ]
+      "kw",
+      "flag"
+    ],
+    "moji": "🇰🇼"
   },
   "flag_ky": {
     "unicode": "1F1F0-1F1FE",
@@ -10429,8 +11564,10 @@
     "keywords": [
       "country",
       "nation",
-      "ky"
-    ]
+      "ky",
+      "flag"
+    ],
+    "moji": "🇰🇾"
   },
   "flag_kz": {
     "unicode": "1F1F0-1F1FF",
@@ -10446,8 +11583,10 @@
       "country",
       "nation",
       "qazaqstan",
-      "kz"
-    ]
+      "kz",
+      "flag"
+    ],
+    "moji": "🇰🇿"
   },
   "flag_la": {
     "unicode": "1F1F1-1F1E6",
@@ -10462,8 +11601,10 @@
     "keywords": [
       "country",
       "nation",
-      "la"
-    ]
+      "la",
+      "flag"
+    ],
+    "moji": "🇱🇦"
   },
   "flag_lb": {
     "unicode": "1F1F1-1F1E7",
@@ -10479,8 +11620,10 @@
       "country",
       "nation",
       "lubnan",
-      "lb"
-    ]
+      "lb",
+      "flag"
+    ],
+    "moji": "🇱🇧"
   },
   "flag_lc": {
     "unicode": "1F1F1-1F1E8",
@@ -10495,8 +11638,10 @@
     "keywords": [
       "country",
       "nation",
-      "lc"
-    ]
+      "lc",
+      "flag"
+    ],
+    "moji": "🇱🇨"
   },
   "flag_li": {
     "unicode": "1F1F1-1F1EE",
@@ -10511,8 +11656,10 @@
     "keywords": [
       "country",
       "nation",
-      "li"
-    ]
+      "li",
+      "flag"
+    ],
+    "moji": "🇱🇮"
   },
   "flag_lk": {
     "unicode": "1F1F1-1F1F0",
@@ -10527,8 +11674,10 @@
     "keywords": [
       "country",
       "nation",
-      "lk"
-    ]
+      "lk",
+      "flag"
+    ],
+    "moji": "🇱🇰"
   },
   "flag_lr": {
     "unicode": "1F1F1-1F1F7",
@@ -10543,8 +11692,10 @@
     "keywords": [
       "country",
       "nation",
-      "lr"
-    ]
+      "lr",
+      "flag"
+    ],
+    "moji": "🇱🇷"
   },
   "flag_ls": {
     "unicode": "1F1F1-1F1F8",
@@ -10559,8 +11710,10 @@
     "keywords": [
       "country",
       "nation",
-      "ls"
-    ]
+      "ls",
+      "flag"
+    ],
+    "moji": "🇱🇸"
   },
   "flag_lt": {
     "unicode": "1F1F1-1F1F9",
@@ -10576,8 +11729,10 @@
       "country",
       "nation",
       "lietuva",
-      "lt"
-    ]
+      "lt",
+      "flag"
+    ],
+    "moji": "🇱🇹"
   },
   "flag_lu": {
     "unicode": "1F1F1-1F1FA",
@@ -10594,8 +11749,10 @@
       "nation",
       "luxembourg",
       "letzebuerg",
-      "lu"
-    ]
+      "lu",
+      "flag"
+    ],
+    "moji": "🇱🇺"
   },
   "flag_lv": {
     "unicode": "1F1F1-1F1FB",
@@ -10611,8 +11768,10 @@
       "country",
       "nation",
       "latvija",
-      "lv"
-    ]
+      "lv",
+      "flag"
+    ],
+    "moji": "🇱🇻"
   },
   "flag_ly": {
     "unicode": "1F1F1-1F1FE",
@@ -10628,8 +11787,10 @@
       "country",
       "nation",
       "libiyah",
-      "ly"
-    ]
+      "ly",
+      "flag"
+    ],
+    "moji": "🇱🇾"
   },
   "flag_ma": {
     "unicode": "1F1F2-1F1E6",
@@ -10645,8 +11806,10 @@
       "country",
       "nation",
       "al maghrib",
-      "ma"
-    ]
+      "ma",
+      "flag"
+    ],
+    "moji": "🇲🇦"
   },
   "flag_mc": {
     "unicode": "1F1F2-1F1E8",
@@ -10661,8 +11824,10 @@
     "keywords": [
       "country",
       "nation",
-      "mc"
-    ]
+      "mc",
+      "flag"
+    ],
+    "moji": "🇲🇨"
   },
   "flag_md": {
     "unicode": "1F1F2-1F1E9",
@@ -10677,8 +11842,10 @@
     "keywords": [
       "country",
       "nation",
-      "md"
-    ]
+      "md",
+      "flag"
+    ],
+    "moji": "🇲🇩"
   },
   "flag_me": {
     "unicode": "1F1F2-1F1EA",
@@ -10694,12 +11861,14 @@
       "country",
       "nation",
       "crna gora",
-      "me"
-    ]
+      "me",
+      "flag"
+    ],
+    "moji": "🇲🇪"
   },
   "flag_mf": {
     "unicode": "1F1F2-1F1EB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "saint martin",
     "shortname": ":flag_mf:",
     "category": "flags",
@@ -10707,7 +11876,11 @@
       ":mf:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇲🇫"
   },
   "flag_mg": {
     "unicode": "1F1F2-1F1EC",
@@ -10722,8 +11895,10 @@
     "keywords": [
       "country",
       "nation",
-      "mg"
-    ]
+      "mg",
+      "flag"
+    ],
+    "moji": "🇲🇬"
   },
   "flag_mh": {
     "unicode": "1F1F2-1F1ED",
@@ -10738,8 +11913,10 @@
     "keywords": [
       "country",
       "nation",
-      "mh"
-    ]
+      "mh",
+      "flag"
+    ],
+    "moji": "🇲🇭"
   },
   "flag_mk": {
     "unicode": "1F1F2-1F1F0",
@@ -10754,8 +11931,10 @@
     "keywords": [
       "country",
       "nation",
-      "mk"
-    ]
+      "mk",
+      "flag"
+    ],
+    "moji": "🇲🇰"
   },
   "flag_ml": {
     "unicode": "1F1F2-1F1F1",
@@ -10770,8 +11949,10 @@
     "keywords": [
       "country",
       "nation",
-      "ml"
-    ]
+      "ml",
+      "flag"
+    ],
+    "moji": "🇲🇱"
   },
   "flag_mm": {
     "unicode": "1F1F2-1F1F2",
@@ -10787,8 +11968,10 @@
       "country",
       "nation",
       "myanma naingngandaw",
-      "mm"
-    ]
+      "mm",
+      "flag"
+    ],
+    "moji": "🇲🇲"
   },
   "flag_mn": {
     "unicode": "1F1F2-1F1F3",
@@ -10804,8 +11987,10 @@
       "country",
       "nation",
       "mongol uls",
-      "mn"
-    ]
+      "mn",
+      "flag"
+    ],
+    "moji": "🇲🇳"
   },
   "flag_mo": {
     "unicode": "1F1F2-1F1F4",
@@ -10821,12 +12006,14 @@
       "country",
       "nation",
       "aomen",
-      "mo"
-    ]
+      "mo",
+      "flag"
+    ],
+    "moji": "🇲🇴"
   },
   "flag_mp": {
     "unicode": "1F1F2-1F1F5",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "northern mariana islands",
     "shortname": ":flag_mp:",
     "category": "flags",
@@ -10834,11 +12021,15 @@
       ":mp:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇲🇵"
   },
   "flag_mq": {
     "unicode": "1F1F2-1F1F6",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "martinique",
     "shortname": ":flag_mq:",
     "category": "flags",
@@ -10846,7 +12037,11 @@
       ":mq:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇲🇶"
   },
   "flag_mr": {
     "unicode": "1F1F2-1F1F7",
@@ -10862,8 +12057,10 @@
       "country",
       "nation",
       "muritaniyah",
-      "mr"
-    ]
+      "mr",
+      "flag"
+    ],
+    "moji": "🇲🇷"
   },
   "flag_ms": {
     "unicode": "1F1F2-1F1F8",
@@ -10878,8 +12075,10 @@
     "keywords": [
       "country",
       "nation",
-      "ms"
-    ]
+      "ms",
+      "flag"
+    ],
+    "moji": "🇲🇸"
   },
   "flag_mt": {
     "unicode": "1F1F2-1F1F9",
@@ -10894,8 +12093,10 @@
     "keywords": [
       "country",
       "nation",
-      "mt"
-    ]
+      "mt",
+      "flag"
+    ],
+    "moji": "🇲🇹"
   },
   "flag_mu": {
     "unicode": "1F1F2-1F1FA",
@@ -10910,8 +12111,10 @@
     "keywords": [
       "country",
       "nation",
-      "mu"
-    ]
+      "mu",
+      "flag"
+    ],
+    "moji": "🇲🇺"
   },
   "flag_mv": {
     "unicode": "1F1F2-1F1FB",
@@ -10927,8 +12130,10 @@
       "country",
       "nation",
       "dhivehi raajje",
-      "mv"
-    ]
+      "mv",
+      "flag"
+    ],
+    "moji": "🇲🇻"
   },
   "flag_mw": {
     "unicode": "1F1F2-1F1FC",
@@ -10943,8 +12148,10 @@
     "keywords": [
       "country",
       "nation",
-      "mw"
-    ]
+      "mw",
+      "flag"
+    ],
+    "moji": "🇲🇼"
   },
   "flag_mx": {
     "unicode": "1F1F2-1F1FD",
@@ -10959,8 +12166,11 @@
     "keywords": [
       "country",
       "nation",
-      "mx"
-    ]
+      "mx",
+      "mexican",
+      "flag"
+    ],
+    "moji": "🇲🇽"
   },
   "flag_my": {
     "unicode": "1F1F2-1F1FE",
@@ -10975,8 +12185,10 @@
     "keywords": [
       "country",
       "nation",
-      "my"
-    ]
+      "my",
+      "flag"
+    ],
+    "moji": "🇲🇾"
   },
   "flag_mz": {
     "unicode": "1F1F2-1F1FF",
@@ -10992,8 +12204,10 @@
       "country",
       "nation",
       "mocambique",
-      "mz"
-    ]
+      "mz",
+      "flag"
+    ],
+    "moji": "🇲🇿"
   },
   "flag_na": {
     "unicode": "1F1F3-1F1E6",
@@ -11008,8 +12222,10 @@
     "keywords": [
       "country",
       "nation",
-      "na"
-    ]
+      "na",
+      "flag"
+    ],
+    "moji": "🇳🇦"
   },
   "flag_nc": {
     "unicode": "1F1F3-1F1E8",
@@ -11027,8 +12243,10 @@
       "nouvelle",
       "cal&eacute;donie",
       "caledonie",
-      "nc"
-    ]
+      "nc",
+      "flag"
+    ],
+    "moji": "🇳🇨"
   },
   "flag_ne": {
     "unicode": "1F1F3-1F1EA",
@@ -11043,12 +12261,14 @@
     "keywords": [
       "country",
       "nation",
-      "ne"
-    ]
+      "ne",
+      "flag"
+    ],
+    "moji": "🇳🇪"
   },
   "flag_nf": {
     "unicode": "1F1F3-1F1EB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "norfolk island",
     "shortname": ":flag_nf:",
     "category": "flags",
@@ -11056,7 +12276,11 @@
       ":nf:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇳🇫"
   },
   "flag_ng": {
     "unicode": "1F1F3-1F1EC",
@@ -11071,8 +12295,10 @@
     "keywords": [
       "country",
       "nation",
-      "ng"
-    ]
+      "ng",
+      "flag"
+    ],
+    "moji": "🇳🇬"
   },
   "flag_ni": {
     "unicode": "1F1F3-1F1EE",
@@ -11087,8 +12313,10 @@
     "keywords": [
       "country",
       "nation",
-      "ni"
-    ]
+      "ni",
+      "flag"
+    ],
+    "moji": "🇳🇮"
   },
   "flag_nl": {
     "unicode": "1F1F3-1F1F1",
@@ -11105,8 +12333,10 @@
       "nation",
       "nederland",
       "holland",
-      "nl"
-    ]
+      "nl",
+      "flag"
+    ],
+    "moji": "🇳🇱"
   },
   "flag_no": {
     "unicode": "1F1F3-1F1F4",
@@ -11122,8 +12352,10 @@
       "country",
       "nation",
       "norge",
-      "no"
-    ]
+      "no",
+      "flag"
+    ],
+    "moji": "🇳🇴"
   },
   "flag_np": {
     "unicode": "1F1F3-1F1F5",
@@ -11138,8 +12370,10 @@
     "keywords": [
       "country",
       "nation",
-      "np"
-    ]
+      "np",
+      "flag"
+    ],
+    "moji": "🇳🇵"
   },
   "flag_nr": {
     "unicode": "1F1F3-1F1F7",
@@ -11154,8 +12388,10 @@
     "keywords": [
       "country",
       "nation",
-      "nr"
-    ]
+      "nr",
+      "flag"
+    ],
+    "moji": "🇳🇷"
   },
   "flag_nu": {
     "unicode": "1F1F3-1F1FA",
@@ -11170,8 +12406,10 @@
     "keywords": [
       "country",
       "nation",
-      "nu"
-    ]
+      "nu",
+      "flag"
+    ],
+    "moji": "🇳🇺"
   },
   "flag_nz": {
     "unicode": "1F1F3-1F1FF",
@@ -11187,8 +12425,10 @@
       "country",
       "nation",
       "aotearoa",
-      "nz"
-    ]
+      "nz",
+      "flag"
+    ],
+    "moji": "🇳🇿"
   },
   "flag_om": {
     "unicode": "1F1F4-1F1F2",
@@ -11204,8 +12444,10 @@
       "country",
       "nation",
       "saltanat uman",
-      "om"
-    ]
+      "om",
+      "flag"
+    ],
+    "moji": "🇴🇲"
   },
   "flag_pa": {
     "unicode": "1F1F5-1F1E6",
@@ -11220,8 +12462,10 @@
     "keywords": [
       "country",
       "nation",
-      "pa"
-    ]
+      "pa",
+      "flag"
+    ],
+    "moji": "🇵🇦"
   },
   "flag_pe": {
     "unicode": "1F1F5-1F1EA",
@@ -11236,8 +12480,10 @@
     "keywords": [
       "country",
       "nation",
-      "pe"
-    ]
+      "pe",
+      "flag"
+    ],
+    "moji": "🇵🇪"
   },
   "flag_pf": {
     "unicode": "1F1F5-1F1EB",
@@ -11254,8 +12500,10 @@
       "nation",
       "polyn&eacute;sie fran&ccedil;aise",
       "polynesie francaise",
-      "pf"
-    ]
+      "pf",
+      "flag"
+    ],
+    "moji": "🇵🇫"
   },
   "flag_pg": {
     "unicode": "1F1F5-1F1EC",
@@ -11271,8 +12519,10 @@
       "country",
       "nation",
       "papua niu gini",
-      "pg"
-    ]
+      "pg",
+      "flag"
+    ],
+    "moji": "🇵🇬"
   },
   "flag_ph": {
     "unicode": "1F1F5-1F1ED",
@@ -11288,8 +12538,10 @@
       "country",
       "nation",
       "pilipinas",
-      "ph"
-    ]
+      "ph",
+      "flag"
+    ],
+    "moji": "🇵🇭"
   },
   "flag_pk": {
     "unicode": "1F1F5-1F1F0",
@@ -11304,8 +12556,10 @@
     "keywords": [
       "country",
       "nation",
-      "pk"
-    ]
+      "pk",
+      "flag"
+    ],
+    "moji": "🇵🇰"
   },
   "flag_pl": {
     "unicode": "1F1F5-1F1F1",
@@ -11321,12 +12575,14 @@
       "country",
       "nation",
       "polska",
-      "pl"
-    ]
+      "pl",
+      "flag"
+    ],
+    "moji": "🇵🇱"
   },
   "flag_pm": {
     "unicode": "1F1F5-1F1F2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "saint pierre and miquelon",
     "shortname": ":flag_pm:",
     "category": "flags",
@@ -11334,11 +12590,15 @@
       ":pm:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇵🇲"
   },
   "flag_pn": {
     "unicode": "1F1F5-1F1F3",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pitcairn",
     "shortname": ":flag_pn:",
     "category": "flags",
@@ -11346,7 +12606,11 @@
       ":pn:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇵🇳"
   },
   "flag_pr": {
     "unicode": "1F1F5-1F1F7",
@@ -11361,8 +12625,10 @@
     "keywords": [
       "country",
       "nation",
-      "pr"
-    ]
+      "pr",
+      "flag"
+    ],
+    "moji": "🇵🇷"
   },
   "flag_ps": {
     "unicode": "1F1F5-1F1F8",
@@ -11377,8 +12643,10 @@
     "keywords": [
       "country",
       "nation",
-      "ps"
-    ]
+      "ps",
+      "flag"
+    ],
+    "moji": "🇵🇸"
   },
   "flag_pt": {
     "unicode": "1F1F5-1F1F9",
@@ -11393,8 +12661,10 @@
     "keywords": [
       "country",
       "nation",
-      "pt"
-    ]
+      "pt",
+      "flag"
+    ],
+    "moji": "🇵🇹"
   },
   "flag_pw": {
     "unicode": "1F1F5-1F1FC",
@@ -11410,8 +12680,10 @@
       "country",
       "nation",
       "belau",
-      "pw"
-    ]
+      "pw",
+      "flag"
+    ],
+    "moji": "🇵🇼"
   },
   "flag_py": {
     "unicode": "1F1F5-1F1FE",
@@ -11426,8 +12698,10 @@
     "keywords": [
       "country",
       "nation",
-      "py"
-    ]
+      "py",
+      "flag"
+    ],
+    "moji": "🇵🇾"
   },
   "flag_qa": {
     "unicode": "1F1F6-1F1E6",
@@ -11443,12 +12717,14 @@
       "country",
       "nation",
       "dawlat qatar",
-      "qa"
-    ]
+      "qa",
+      "flag"
+    ],
+    "moji": "🇶🇦"
   },
   "flag_re": {
     "unicode": "1F1F7-1F1EA",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "réunion",
     "shortname": ":flag_re:",
     "category": "flags",
@@ -11456,7 +12732,11 @@
       ":re:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇷🇪"
   },
   "flag_ro": {
     "unicode": "1F1F7-1F1F4",
@@ -11471,8 +12751,10 @@
     "keywords": [
       "country",
       "nation",
-      "ro"
-    ]
+      "ro",
+      "flag"
+    ],
+    "moji": "🇷🇴"
   },
   "flag_rs": {
     "unicode": "1F1F7-1F1F8",
@@ -11488,8 +12770,10 @@
       "country",
       "nation",
       "srbija",
-      "rs"
-    ]
+      "rs",
+      "flag"
+    ],
+    "moji": "🇷🇸"
   },
   "flag_ru": {
     "unicode": "1F1F7-1F1FA",
@@ -11505,8 +12789,10 @@
       "nation",
       "russian",
       "country",
-      "ru"
-    ]
+      "ru",
+      "flag"
+    ],
+    "moji": "🇷🇺"
   },
   "flag_rw": {
     "unicode": "1F1F7-1F1FC",
@@ -11521,8 +12807,10 @@
     "keywords": [
       "country",
       "nation",
-      "rw"
-    ]
+      "rw",
+      "flag"
+    ],
+    "moji": "🇷🇼"
   },
   "flag_sa": {
     "unicode": "1F1F8-1F1E6",
@@ -11539,8 +12827,10 @@
       "country",
       "nation",
       "al arabiyah as suudiyah",
-      "sa"
-    ]
+      "sa",
+      "flag"
+    ],
+    "moji": "🇸🇦"
   },
   "flag_sb": {
     "unicode": "1F1F8-1F1E7",
@@ -11555,8 +12845,10 @@
     "keywords": [
       "country",
       "nation",
-      "sb"
-    ]
+      "sb",
+      "flag"
+    ],
+    "moji": "🇸🇧"
   },
   "flag_sc": {
     "unicode": "1F1F8-1F1E8",
@@ -11572,8 +12864,10 @@
       "country",
       "nation",
       "seychelles",
-      "sc"
-    ]
+      "sc",
+      "flag"
+    ],
+    "moji": "🇸🇨"
   },
   "flag_sd": {
     "unicode": "1F1F8-1F1E9",
@@ -11589,8 +12883,10 @@
       "country",
       "nation",
       "as-sudan",
-      "sd"
-    ]
+      "sd",
+      "flag"
+    ],
+    "moji": "🇸🇩"
   },
   "flag_se": {
     "unicode": "1F1F8-1F1EA",
@@ -11606,8 +12902,10 @@
       "country",
       "nation",
       "sverige",
-      "se"
-    ]
+      "se",
+      "flag"
+    ],
+    "moji": "🇸🇪"
   },
   "flag_sg": {
     "unicode": "1F1F8-1F1EC",
@@ -11622,8 +12920,10 @@
     "keywords": [
       "country",
       "nation",
-      "sg"
-    ]
+      "sg",
+      "flag"
+    ],
+    "moji": "🇸🇬"
   },
   "flag_sh": {
     "unicode": "1F1F8-1F1ED",
@@ -11638,8 +12938,10 @@
     "keywords": [
       "country",
       "nation",
-      "sh"
-    ]
+      "sh",
+      "flag"
+    ],
+    "moji": "🇸🇭"
   },
   "flag_si": {
     "unicode": "1F1F8-1F1EE",
@@ -11655,12 +12957,14 @@
       "country",
       "nation",
       "slovenija",
-      "si"
-    ]
+      "si",
+      "flag"
+    ],
+    "moji": "🇸🇮"
   },
   "flag_sj": {
     "unicode": "1F1F8-1F1EF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "svalbard and jan mayen",
     "shortname": ":flag_sj:",
     "category": "flags",
@@ -11668,7 +12972,11 @@
       ":sj:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇸🇯"
   },
   "flag_sk": {
     "unicode": "1F1F8-1F1F0",
@@ -11683,8 +12991,10 @@
     "keywords": [
       "country",
       "nation",
-      "sk"
-    ]
+      "sk",
+      "flag"
+    ],
+    "moji": "🇸🇰"
   },
   "flag_sl": {
     "unicode": "1F1F8-1F1F1",
@@ -11699,8 +13009,10 @@
     "keywords": [
       "country",
       "nation",
-      "sl"
-    ]
+      "sl",
+      "flag"
+    ],
+    "moji": "🇸🇱"
   },
   "flag_sm": {
     "unicode": "1F1F8-1F1F2",
@@ -11715,8 +13027,10 @@
     "keywords": [
       "country",
       "nation",
-      "sm"
-    ]
+      "sm",
+      "flag"
+    ],
+    "moji": "🇸🇲"
   },
   "flag_sn": {
     "unicode": "1F1F8-1F1F3",
@@ -11731,8 +13045,10 @@
     "keywords": [
       "country",
       "nation",
-      "sn"
-    ]
+      "sn",
+      "flag"
+    ],
+    "moji": "🇸🇳"
   },
   "flag_so": {
     "unicode": "1F1F8-1F1F4",
@@ -11747,8 +13063,10 @@
     "keywords": [
       "country",
       "nation",
-      "so"
-    ]
+      "so",
+      "flag"
+    ],
+    "moji": "🇸🇴"
   },
   "flag_sr": {
     "unicode": "1F1F8-1F1F7",
@@ -11763,12 +13081,14 @@
     "keywords": [
       "country",
       "nation",
-      "sr"
-    ]
+      "sr",
+      "flag"
+    ],
+    "moji": "🇸🇷"
   },
   "flag_ss": {
     "unicode": "1F1F8-1F1F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "south sudan",
     "shortname": ":flag_ss:",
     "category": "flags",
@@ -11776,7 +13096,11 @@
       ":ss:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇸🇸"
   },
   "flag_st": {
     "unicode": "1F1F8-1F1F9",
@@ -11792,8 +13116,10 @@
       "country",
       "nation",
       "sao tome e principe",
-      "st"
-    ]
+      "st",
+      "flag"
+    ],
+    "moji": "🇸🇹"
   },
   "flag_sv": {
     "unicode": "1F1F8-1F1FB",
@@ -11808,12 +13134,14 @@
     "keywords": [
       "country",
       "nation",
-      "sv"
-    ]
+      "sv",
+      "flag"
+    ],
+    "moji": "🇸🇻"
   },
   "flag_sx": {
     "unicode": "1F1F8-1F1FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sint maarten",
     "shortname": ":flag_sx:",
     "category": "flags",
@@ -11821,7 +13149,11 @@
       ":sx:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇸🇽"
   },
   "flag_sy": {
     "unicode": "1F1F8-1F1FE",
@@ -11836,8 +13168,10 @@
     "keywords": [
       "country",
       "nation",
-      "sy"
-    ]
+      "sy",
+      "flag"
+    ],
+    "moji": "🇸🇾"
   },
   "flag_sz": {
     "unicode": "1F1F8-1F1FF",
@@ -11852,12 +13186,14 @@
     "keywords": [
       "country",
       "nation",
-      "sz"
-    ]
+      "sz",
+      "flag"
+    ],
+    "moji": "🇸🇿"
   },
   "flag_ta": {
     "unicode": "1F1F9-1F1E6",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "tristan da cunha",
     "shortname": ":flag_ta:",
     "category": "flags",
@@ -11865,11 +13201,15 @@
       ":ta:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇹🇦"
   },
   "flag_tc": {
     "unicode": "1F1F9-1F1E8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "turks and caicos islands",
     "shortname": ":flag_tc:",
     "category": "flags",
@@ -11877,7 +13217,11 @@
       ":tc:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇹🇨"
   },
   "flag_td": {
     "unicode": "1F1F9-1F1E9",
@@ -11893,12 +13237,14 @@
       "country",
       "nation",
       "tchad",
-      "td"
-    ]
+      "td",
+      "flag"
+    ],
+    "moji": "🇹🇩"
   },
   "flag_tf": {
     "unicode": "1F1F9-1F1EB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "french southern territories",
     "shortname": ":flag_tf:",
     "category": "flags",
@@ -11906,7 +13252,11 @@
       ":tf:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇹🇫"
   },
   "flag_tg": {
     "unicode": "1F1F9-1F1EC",
@@ -11922,8 +13272,10 @@
       "country",
       "nation",
       "republique togolaise",
-      "tg"
-    ]
+      "tg",
+      "flag"
+    ],
+    "moji": "🇹🇬"
   },
   "flag_th": {
     "unicode": "1F1F9-1F1ED",
@@ -11939,8 +13291,10 @@
       "country",
       "nation",
       "prathet thai",
-      "th"
-    ]
+      "th",
+      "flag"
+    ],
+    "moji": "🇹🇭"
   },
   "flag_tj": {
     "unicode": "1F1F9-1F1EF",
@@ -11956,12 +13310,14 @@
       "country",
       "nation",
       "jumhurii tojikiston",
-      "tj"
-    ]
+      "tj",
+      "flag"
+    ],
+    "moji": "🇹🇯"
   },
   "flag_tk": {
     "unicode": "1F1F9-1F1F0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "tokelau",
     "shortname": ":flag_tk:",
     "category": "flags",
@@ -11969,7 +13325,11 @@
       ":tk:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇹🇰"
   },
   "flag_tl": {
     "unicode": "1F1F9-1F1F1",
@@ -11984,8 +13344,10 @@
     "keywords": [
       "country",
       "nation",
-      "tl"
-    ]
+      "tl",
+      "flag"
+    ],
+    "moji": "🇹🇱"
   },
   "flag_tm": {
     "unicode": "1F1F9-1F1F2",
@@ -12000,8 +13362,10 @@
     "keywords": [
       "country",
       "nation",
-      "tm"
-    ]
+      "tm",
+      "flag"
+    ],
+    "moji": "🇹🇲"
   },
   "flag_tn": {
     "unicode": "1F1F9-1F1F3",
@@ -12017,8 +13381,10 @@
       "country",
       "nation",
       "tunis",
-      "tn"
-    ]
+      "tn",
+      "flag"
+    ],
+    "moji": "🇹🇳"
   },
   "flag_to": {
     "unicode": "1F1F9-1F1F4",
@@ -12033,8 +13399,10 @@
     "keywords": [
       "country",
       "nation",
-      "to"
-    ]
+      "to",
+      "flag"
+    ],
+    "moji": "🇹🇴"
   },
   "flag_tr": {
     "unicode": "1F1F9-1F1F7",
@@ -12049,8 +13417,10 @@
     "keywords": [
       "country",
       "nation",
-      "turkiye"
-    ]
+      "turkiye",
+      "flag"
+    ],
+    "moji": "🇹🇷"
   },
   "flag_tt": {
     "unicode": "1F1F9-1F1F9",
@@ -12065,8 +13435,10 @@
     "keywords": [
       "country",
       "nation",
-      "tt"
-    ]
+      "tt",
+      "flag"
+    ],
+    "moji": "🇹🇹"
   },
   "flag_tv": {
     "unicode": "1F1F9-1F1FB",
@@ -12081,8 +13453,10 @@
     "keywords": [
       "country",
       "nation",
-      "tv"
-    ]
+      "tv",
+      "flag"
+    ],
+    "moji": "🇹🇻"
   },
   "flag_tw": {
     "unicode": "1F1F9-1F1FC",
@@ -12098,8 +13472,10 @@
       "country",
       "nation",
       "taiwan",
-      "tw"
-    ]
+      "tw",
+      "flag"
+    ],
+    "moji": "🇹🇼"
   },
   "flag_tz": {
     "unicode": "1F1F9-1F1FF",
@@ -12114,8 +13490,10 @@
     "keywords": [
       "country",
       "nation",
-      "tz"
-    ]
+      "tz",
+      "flag"
+    ],
+    "moji": "🇹🇿"
   },
   "flag_ua": {
     "unicode": "1F1FA-1F1E6",
@@ -12131,8 +13509,10 @@
       "country",
       "nation",
       "ukrayina",
-      "ua"
-    ]
+      "ua",
+      "flag"
+    ],
+    "moji": "🇺🇦"
   },
   "flag_ug": {
     "unicode": "1F1FA-1F1EC",
@@ -12147,12 +13527,14 @@
     "keywords": [
       "country",
       "nation",
-      "ug"
-    ]
+      "ug",
+      "flag"
+    ],
+    "moji": "🇺🇬"
   },
   "flag_um": {
     "unicode": "1F1FA-1F1F2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "united states minor outlying islands",
     "shortname": ":flag_um:",
     "category": "flags",
@@ -12160,7 +13542,11 @@
       ":um:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇺🇲"
   },
   "flag_us": {
     "unicode": "1F1FA-1F1F8",
@@ -12180,8 +13566,10 @@
       "united states of america",
       "america",
       "old glory",
-      "us"
-    ]
+      "us",
+      "flag"
+    ],
+    "moji": "🇺🇸"
   },
   "flag_uy": {
     "unicode": "1F1FA-1F1FE",
@@ -12196,8 +13584,10 @@
     "keywords": [
       "country",
       "nation",
-      "uy"
-    ]
+      "uy",
+      "flag"
+    ],
+    "moji": "🇺🇾"
   },
   "flag_uz": {
     "unicode": "1F1FA-1F1FF",
@@ -12213,8 +13603,10 @@
       "country",
       "nation",
       "uzbekiston respublikasi",
-      "uz"
-    ]
+      "uz",
+      "flag"
+    ],
+    "moji": "🇺🇿"
   },
   "flag_va": {
     "unicode": "1F1FB-1F1E6",
@@ -12229,8 +13621,10 @@
     "keywords": [
       "country",
       "nation",
-      "va"
-    ]
+      "va",
+      "flag"
+    ],
+    "moji": "🇻🇦"
   },
   "flag_vc": {
     "unicode": "1F1FB-1F1E8",
@@ -12245,8 +13639,10 @@
     "keywords": [
       "country",
       "nation",
-      "vc"
-    ]
+      "vc",
+      "flag"
+    ],
+    "moji": "🇻🇨"
   },
   "flag_ve": {
     "unicode": "1F1FB-1F1EA",
@@ -12261,12 +13657,14 @@
     "keywords": [
       "country",
       "nation",
-      "ve"
-    ]
+      "ve",
+      "flag"
+    ],
+    "moji": "🇻🇪"
   },
   "flag_vg": {
     "unicode": "1F1FB-1F1EC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "british virgin islands",
     "shortname": ":flag_vg:",
     "category": "flags",
@@ -12274,7 +13672,11 @@
       ":vg:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇻🇬"
   },
   "flag_vi": {
     "unicode": "1F1FB-1F1EE",
@@ -12289,8 +13691,10 @@
     "keywords": [
       "country",
       "nation",
-      "vi"
-    ]
+      "vi",
+      "flag"
+    ],
+    "moji": "🇻🇮"
   },
   "flag_vn": {
     "unicode": "1F1FB-1F1F3",
@@ -12306,8 +13710,10 @@
       "country",
       "nation",
       "viet nam",
-      "vn"
-    ]
+      "vn",
+      "flag"
+    ],
+    "moji": "🇻🇳"
   },
   "flag_vu": {
     "unicode": "1F1FB-1F1FA",
@@ -12322,8 +13728,10 @@
     "keywords": [
       "country",
       "nation",
-      "vu"
-    ]
+      "vu",
+      "flag"
+    ],
+    "moji": "🇻🇺"
   },
   "flag_wf": {
     "unicode": "1F1FC-1F1EB",
@@ -12338,23 +13746,27 @@
     "keywords": [
       "country",
       "nation",
-      "wf"
-    ]
+      "wf",
+      "flag"
+    ],
+    "moji": "🇼🇫"
   },
   "flag_white": {
     "unicode": "1F3F3",
     "unicode_alternates": [],
     "name": "waving white flag",
     "shortname": ":flag_white:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":waving_white_flag:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "symbol",
-      "signal"
-    ]
+      "signal",
+      "object"
+    ],
+    "moji": "🏳"
   },
   "flag_ws": {
     "unicode": "1F1FC-1F1F8",
@@ -12370,8 +13782,10 @@
       "country",
       "nation",
       "american samoa",
-      "ws"
-    ]
+      "ws",
+      "flag"
+    ],
+    "moji": "🇼🇸"
   },
   "flag_xk": {
     "unicode": "1F1FD-1F1F0",
@@ -12386,8 +13800,10 @@
     "keywords": [
       "country",
       "nation",
-      "xk"
-    ]
+      "xk",
+      "flag"
+    ],
+    "moji": "🇽🇰"
   },
   "flag_ye": {
     "unicode": "1F1FE-1F1EA",
@@ -12403,12 +13819,14 @@
       "country",
       "nation",
       "al yaman",
-      "ye"
-    ]
+      "ye",
+      "flag"
+    ],
+    "moji": "🇾🇪"
   },
   "flag_yt": {
     "unicode": "1F1FE-1F1F9",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mayotte",
     "shortname": ":flag_yt:",
     "category": "flags",
@@ -12416,7 +13834,11 @@
       ":yt:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "country",
+      "flag"
+    ],
+    "moji": "🇾🇹"
   },
   "flag_za": {
     "unicode": "1F1FF-1F1E6",
@@ -12430,8 +13852,10 @@
     "aliases_ascii": [],
     "keywords": [
       "country",
-      "nation"
-    ]
+      "nation",
+      "flag"
+    ],
+    "moji": "🇿🇦"
   },
   "flag_zm": {
     "unicode": "1F1FF-1F1F2",
@@ -12446,8 +13870,10 @@
     "keywords": [
       "country",
       "nation",
-      "zm"
-    ]
+      "zm",
+      "flag"
+    ],
+    "moji": "🇿🇲"
   },
   "flag_zw": {
     "unicode": "1F1FF-1F1FC",
@@ -12462,8 +13888,10 @@
     "keywords": [
       "country",
       "nation",
-      "zw"
-    ]
+      "zw",
+      "flag"
+    ],
+    "moji": "🇿🇼"
   },
   "flags": {
     "unicode": "1F38F",
@@ -12484,11 +13912,11 @@
       "boys",
       "celebration",
       "happiness",
-      "carp",
       "streamers",
-      "japanese",
       "holiday",
-      "flags"
+      "flags",
+      "object",
+      "japan"
     ],
     "moji": "🎏"
   },
@@ -12501,56 +13929,25 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "dark"
+      "dark",
+      "electronics",
+      "object"
     ],
     "moji": "🔦"
   },
   "fleur-de-lis": {
     "unicode": "269C",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fleur-de-lis",
     "shortname": ":fleur-de-lis:",
     "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "symbol"
-    ]
-  },
-  "flip_phone": {
-    "unicode": "1F581",
-    "unicode_alternates": [],
-    "name": "clamshell mobile phone",
-    "shortname": ":flip_phone:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":clamshell_mobile_phone:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "cellphone"
-    ]
-  },
-  "floppy_black": {
-    "unicode": "1F5AA",
-    "unicode_alternates": [],
-    "name": "black hard shell floppy disk",
-    "shortname": ":floppy_black:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":black_hard_shell_floppy_disk:"
+      "symbol",
+      "object"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "oldschool",
-      "save",
-      "technology",
-      "storage",
-      "information",
-      "computer",
-      "drive",
-      "megabyte"
-    ]
+    "moji": "⚜"
   },
   "floppy_disk": {
     "unicode": "1F4BE",
@@ -12570,37 +13967,18 @@
       "information",
       "computer",
       "drive",
-      "megabyte"
-    ],
-    "moji": "💾"
-  },
-  "floppy_white": {
-    "unicode": "1F5AB",
-    "unicode_alternates": [],
-    "name": "white hard shell floppy disk",
-    "shortname": ":floppy_white:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":white_hard_shell_floppy_disk:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "oldschool",
-      "save",
-      "technology",
-      "storage",
-      "information",
-      "computer",
-      "drive",
-      "megabyte"
-    ]
+      "megabyte",
+      "electronics",
+      "office"
+    ],
+    "moji": "💾"
   },
   "flower_playing_cards": {
     "unicode": "1F3B4",
     "unicode_alternates": [],
     "name": "flower playing cards",
     "shortname": ":flower_playing_cards:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12610,7 +13988,9 @@
       "game",
       "august",
       "moon",
-      "special"
+      "special",
+      "object",
+      "symbol"
     ],
     "moji": "🎴"
   },
@@ -12619,7 +13999,7 @@
     "unicode_alternates": [],
     "name": "flushed face",
     "shortname": ":flushed:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":$",
@@ -12630,11 +14010,13 @@
       "face",
       "flattered",
       "flush",
-      "blush",
       "red",
       "pink",
       "cheeks",
-      "shy"
+      "shy",
+      "smiley",
+      "emotion",
+      "omg"
     ],
     "moji": "😳"
   },
@@ -12650,15 +14032,18 @@
       "weather",
       "damp",
       "cloud",
-      "hazy"
-    ]
+      "hazy",
+      "sky",
+      "cold"
+    ],
+    "moji": "🌫"
   },
   "foggy": {
     "unicode": "1F301",
     "unicode_alternates": [],
     "name": "foggy",
     "shortname": ":foggy:",
-    "category": "nature",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12667,43 +14052,21 @@
       "bridge",
       "weather",
       "fog",
-      "foggy"
+      "foggy",
+      "places",
+      "building",
+      "sky",
+      "travel",
+      "vacation"
     ],
     "moji": "🌁"
   },
-  "folder": {
-    "unicode": "1F5C0",
-    "unicode_alternates": [],
-    "name": "folder",
-    "shortname": ":folder:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "documents"
-    ]
-  },
-  "folder_open": {
-    "unicode": "1F5C1",
-    "unicode_alternates": [],
-    "name": "open folder",
-    "shortname": ":folder_open:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":open_folder:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "documents",
-      "load"
-    ]
-  },
   "football": {
     "unicode": "1F3C8",
     "unicode_alternates": [],
     "name": "american football",
     "shortname": ":football:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12714,7 +14077,8 @@
       "ball",
       "sport",
       "america",
-      "american"
+      "american",
+      "game"
     ],
     "moji": "🏈"
   },
@@ -12723,7 +14087,7 @@
     "unicode_alternates": [],
     "name": "footprints",
     "shortname": ":footprints:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12736,7 +14100,7 @@
     "unicode_alternates": [],
     "name": "fork and knife",
     "shortname": ":fork_and_knife:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12747,7 +14111,9 @@
       "restaurant",
       "meal",
       "food",
-      "eat"
+      "eat",
+      "object",
+      "weapon"
     ],
     "moji": "🍴"
   },
@@ -12756,7 +14122,7 @@
     "unicode_alternates": [],
     "name": "fork and knife with plate",
     "shortname": ":fork_knife_plate:",
-    "category": "travel_places",
+    "category": "food",
     "aliases": [
       ":fork_and_knife_with_plate:"
     ],
@@ -12768,8 +14134,10 @@
       "lunch",
       "dinner",
       "utensils",
-      "setting"
-    ]
+      "setting",
+      "object"
+    ],
+    "moji": "🍽"
   },
   "fountain": {
     "unicode": "26F2",
@@ -12778,11 +14146,13 @@
     ],
     "name": "fountain",
     "shortname": ":fountain:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "photo"
+      "photo",
+      "travel",
+      "vacation"
     ],
     "moji": "⛲"
   },
@@ -12792,15 +14162,18 @@
     "unicode_alternates": [
       "0034-FE0F-20E3"
     ],
-    "name": "digit four",
+    "name": "keycap digit four",
     "shortname": ":four:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "4",
       "blue-square",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "four_leaf_clover": {
@@ -12823,74 +14196,75 @@
       "irish",
       "saint",
       "patrick",
-      "green"
+      "green",
+      "sol"
     ],
     "moji": "🍀"
   },
-  "frame_photo": {
-    "unicode": "1F5BC",
+  "fox": {
+    "unicode": "1F98A",
     "unicode_alternates": [],
-    "name": "frame with picture",
-    "shortname": ":frame_photo:",
-    "category": "objects_symbols",
+    "name": "fox face",
+    "shortname": ":fox:",
+    "category": "nature",
     "aliases": [
-      ":frame_with_picture:"
+      ":fox_face:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "photo"
-    ]
+    "keywords": [],
+    "moji": "🦊"
   },
-  "frame_tiles": {
-    "unicode": "1F5BD",
+  "frame_photo": {
+    "unicode": "1F5BC",
     "unicode_alternates": [],
-    "name": "frame with tiles",
-    "shortname": ":frame_tiles:",
-    "category": "objects_symbols",
+    "name": "frame with picture",
+    "shortname": ":frame_photo:",
+    "category": "objects",
     "aliases": [
-      ":frame_with_tiles:"
+      ":frame_with_picture:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "photo",
-      "painting"
-    ]
-  },
-  "frame_x": {
-    "unicode": "1F5BE",
-    "unicode_alternates": [],
-    "name": "frame with an x",
-    "shortname": ":frame_x:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":frame_with_an_x:"
+      "travel",
+      "vacation"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "photo",
-      "painting"
-    ]
+    "moji": "🖼"
   },
   "free": {
     "unicode": "1F193",
     "unicode_alternates": [],
     "name": "squared free",
     "shortname": ":free:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🆓"
   },
+  "french_bread": {
+    "unicode": "1F956",
+    "unicode_alternates": [],
+    "name": "baguette bread",
+    "shortname": ":french_bread:",
+    "category": "food",
+    "aliases": [
+      ":baguette_bread:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥖"
+  },
   "fried_shrimp": {
     "unicode": "1F364",
     "unicode_alternates": [],
     "name": "fried shrimp",
     "shortname": ":fried_shrimp:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12909,7 +14283,7 @@
     "unicode_alternates": [],
     "name": "french fries",
     "shortname": ":fries:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -12920,7 +14294,8 @@
       "potato",
       "fry",
       "russet",
-      "idaho"
+      "idaho",
+      "america"
     ],
     "moji": "🍟"
   },
@@ -12934,7 +14309,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐸"
   },
@@ -12943,7 +14319,7 @@
     "unicode_alternates": [],
     "name": "frowning face with open mouth",
     "shortname": ":frowning:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":anguished:"
     ],
@@ -12955,13 +14331,16 @@
       "sad",
       "pout",
       "sulk",
-      "glower"
+      "glower",
+      "smiley",
+      "surprised",
+      "emotion"
     ],
     "moji": "😦"
   },
   "frowning2": {
     "unicode": "2639",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white frowning face",
     "shortname": ":frowning2:",
     "category": "people",
@@ -12971,8 +14350,12 @@
     "aliases_ascii": [],
     "keywords": [
       "frown",
-      "person"
-    ]
+      "person",
+      "sad",
+      "smiley",
+      "emotion"
+    ],
+    "moji": "☹"
   },
   "fuelpump": {
     "unicode": "26FD",
@@ -12981,12 +14364,14 @@
     ],
     "name": "fuel pump",
     "shortname": ":fuelpump:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "gas station",
-      "petroleum"
+      "petroleum",
+      "object",
+      "gas pump"
     ],
     "moji": "⛽"
   },
@@ -13010,7 +14395,8 @@
       "monster",
       "spooky",
       "werewolves",
-      "twilight"
+      "twilight",
+      "space"
     ],
     "moji": "🌕"
   },
@@ -13029,12 +14415,13 @@
       "anthropomorphic",
       "face",
       "sky",
-      "night",
       "cheese",
       "phase",
       "spooky",
       "werewolves",
-      "monsters"
+      "monsters",
+      "space",
+      "goodnight"
     ],
     "moji": "🌝"
   },
@@ -13043,23 +14430,24 @@
     "unicode_alternates": [],
     "name": "game die",
     "shortname": ":game_die:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "dice",
       "game",
       "die",
-      "dice",
       "craps",
       "gamble",
-      "play"
+      "play",
+      "object",
+      "boys night"
     ],
     "moji": "🎲"
   },
   "gear": {
     "unicode": "2699",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "gear",
     "shortname": ":gear:",
     "category": "objects",
@@ -13068,19 +14456,22 @@
     "keywords": [
       "object",
       "tool"
-    ]
+    ],
+    "moji": "⚙"
   },
   "gem": {
     "unicode": "1F48E",
     "unicode_alternates": [],
     "name": "gem stone",
     "shortname": ":gem:",
-    "category": "emoticons",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue",
-      "ruby"
+      "ruby",
+      "object",
+      "gem"
     ],
     "moji": "💎"
   },
@@ -13091,7 +14482,7 @@
     ],
     "name": "gemini",
     "shortname": ":gemini:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13103,9 +14494,8 @@
       "stars",
       "zodiac",
       "sign",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♊"
   },
@@ -13114,11 +14504,13 @@
     "unicode_alternates": [],
     "name": "ghost",
     "shortname": ":ghost:",
-    "category": "objects",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "halloween"
+      "halloween",
+      "holidays",
+      "monster"
     ],
     "moji": "👻"
   },
@@ -13136,11 +14528,12 @@
       "present",
       "xmas",
       "gift",
-      "present",
       "wrap",
       "package",
-      "birthday",
-      "wedding"
+      "wedding",
+      "object",
+      "holidays",
+      "parties"
     ],
     "moji": "🎁"
   },
@@ -13149,12 +14542,14 @@
     "unicode_alternates": [],
     "name": "heart with ribbon",
     "shortname": ":gift_heart:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "love",
-      "valentines"
+      "valentines",
+      "symbol",
+      "condolence"
     ],
     "moji": "💝"
   },
@@ -13163,18 +14558,22 @@
     "unicode_alternates": [],
     "name": "girl",
     "shortname": ":girl:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "female",
-      "woman"
+      "woman",
+      "people",
+      "women",
+      "baby",
+      "diversity"
     ],
     "moji": "👧"
   },
   "girl_tone1": {
     "unicode": "1F467-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "girl tone 1",
     "shortname": ":girl_tone1:",
     "category": "people",
@@ -13184,11 +14583,12 @@
       "female",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👧🏻"
   },
   "girl_tone2": {
     "unicode": "1F467-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "girl tone 2",
     "shortname": ":girl_tone2:",
     "category": "people",
@@ -13198,11 +14598,12 @@
       "female",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👧🏼"
   },
   "girl_tone3": {
     "unicode": "1F467-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "girl tone 3",
     "shortname": ":girl_tone3:",
     "category": "people",
@@ -13212,11 +14613,12 @@
       "female",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👧🏽"
   },
   "girl_tone4": {
     "unicode": "1F467-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "girl tone 4",
     "shortname": ":girl_tone4:",
     "category": "people",
@@ -13226,11 +14628,12 @@
       "female",
       "kid",
       "child"
-    ]
+    ],
+    "moji": "👧🏾"
   },
   "girl_tone5": {
     "unicode": "1F467-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "girl tone 5",
     "shortname": ":girl_tone5:",
     "category": "people",
@@ -13240,42 +14643,43 @@
       "female",
       "kid",
       "child"
-    ]
-  },
-  "girls_symbol": {
-    "unicode": "1F6CA",
-    "unicode_alternates": [],
-    "name": "girls symbol",
-    "shortname": ":girls_symbol:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "female",
-      "child"
-    ]
+    ],
+    "moji": "👧🏿"
   },
   "globe_with_meridians": {
     "unicode": "1F310",
     "unicode_alternates": [],
     "name": "globe with meridians",
     "shortname": ":globe_with_meridians:",
-    "category": "nature",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "earth",
       "international",
       "world",
-      "earth",
       "meridian",
       "globe",
       "space",
       "planet",
-      "home"
+      "home",
+      "symbol"
     ],
     "moji": "🌐"
   },
+  "goal": {
+    "unicode": "1F945",
+    "unicode_alternates": [],
+    "name": "goal net",
+    "shortname": ":goal:",
+    "category": "activity",
+    "aliases": [
+      ":goal_net:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥅"
+  },
   "goat": {
     "unicode": "1F410",
     "unicode_alternates": [],
@@ -13302,12 +14706,17 @@
     ],
     "name": "flag in hole",
     "shortname": ":golf:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "business",
-      "sports"
+      "sports",
+      "game",
+      "ball",
+      "vacation",
+      "sport",
+      "golf"
     ],
     "moji": "⛳"
   },
@@ -13324,15 +14733,32 @@
       "par",
       "birdie",
       "eagle",
-      "mulligan"
-    ]
+      "mulligan",
+      "men",
+      "game",
+      "ball",
+      "vacation",
+      "golf"
+    ],
+    "moji": "🏌"
+  },
+  "gorilla": {
+    "unicode": "1F98D",
+    "unicode_alternates": [],
+    "name": "gorilla",
+    "shortname": ":gorilla:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦍"
   },
   "grapes": {
     "unicode": "1F347",
     "unicode_alternates": [],
     "name": "grapes",
     "shortname": ":grapes:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13341,7 +14767,6 @@
       "grapes",
       "wine",
       "vinegar",
-      "fruit",
       "cluster",
       "vine"
     ],
@@ -13352,19 +14777,19 @@
     "unicode_alternates": [],
     "name": "green apple",
     "shortname": ":green_apple:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fruit",
       "nature",
       "apple",
-      "fruit",
       "green",
       "pie",
       "granny",
       "smith",
-      "core"
+      "core",
+      "food"
     ],
     "moji": "🍏"
   },
@@ -13379,7 +14804,10 @@
     "keywords": [
       "knowledge",
       "library",
-      "read"
+      "read",
+      "object",
+      "office",
+      "book"
     ],
     "moji": "📗"
   },
@@ -13388,7 +14816,7 @@
     "unicode_alternates": [],
     "name": "green heart",
     "shortname": ":green_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13398,14 +14826,14 @@
       "valentines",
       "green",
       "heart",
-      "love",
       "nature",
       "rebirth",
       "reborn",
       "jealous",
       "clingy",
       "envious",
-      "possessive"
+      "possessive",
+      "symbol"
     ],
     "moji": "💚"
   },
@@ -13414,11 +14842,13 @@
     "unicode_alternates": [],
     "name": "white exclamation mark ornament",
     "shortname": ":grey_exclamation:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "surprise"
+      "surprise",
+      "symbol",
+      "punctuation"
     ],
     "moji": "❕"
   },
@@ -13427,11 +14857,13 @@
     "unicode_alternates": [],
     "name": "white question mark ornament",
     "shortname": ":grey_question:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "doubts"
+      "doubts",
+      "symbol",
+      "punctuation"
     ],
     "moji": "❔"
   },
@@ -13440,16 +14872,19 @@
     "unicode_alternates": [],
     "name": "grimacing face",
     "shortname": ":grimacing:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "face",
       "grimace",
       "teeth",
-      "grimace",
       "disapprove",
-      "pain"
+      "pain",
+      "silly",
+      "smiley",
+      "emotion",
+      "selfie"
     ],
     "moji": "😬"
   },
@@ -13458,7 +14893,7 @@
     "unicode_alternates": [],
     "name": "grinning face with smiling eyes",
     "shortname": ":grin:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13469,8 +14904,11 @@
       "grin",
       "grinning",
       "smiling",
-      "smile",
-      "smiley"
+      "smiley",
+      "silly",
+      "emotion",
+      "good",
+      "selfie"
     ],
     "moji": "😁"
   },
@@ -13479,7 +14917,7 @@
     "unicode_alternates": [],
     "name": "grinning face",
     "shortname": ":grinning:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13490,17 +14928,17 @@
       "grin",
       "grinning",
       "smiling",
-      "smile",
-      "smiley"
+      "smiley",
+      "emotion"
     ],
-    "moji": "🕧"
+    "moji": "😀"
   },
   "guardsman": {
     "unicode": "1F482",
     "unicode_alternates": [],
     "name": "guardsman",
     "shortname": ":guardsman:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13513,16 +14951,19 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
-      "military"
+      "military",
+      "people",
+      "men",
+      "diversity",
+      "job"
     ],
     "moji": "💂"
   },
   "guardsman_tone1": {
     "unicode": "1F482-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guardsman tone 1",
     "shortname": ":guardsman_tone1:",
     "category": "people",
@@ -13537,15 +14978,15 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
       "military"
-    ]
+    ],
+    "moji": "💂🏻"
   },
   "guardsman_tone2": {
     "unicode": "1F482-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guardsman tone 2",
     "shortname": ":guardsman_tone2:",
     "category": "people",
@@ -13560,15 +15001,15 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
       "military"
-    ]
+    ],
+    "moji": "💂🏼"
   },
   "guardsman_tone3": {
     "unicode": "1F482-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guardsman tone 3",
     "shortname": ":guardsman_tone3:",
     "category": "people",
@@ -13583,15 +15024,15 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
       "military"
-    ]
+    ],
+    "moji": "💂🏽"
   },
   "guardsman_tone4": {
     "unicode": "1F482-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guardsman tone 4",
     "shortname": ":guardsman_tone4:",
     "category": "people",
@@ -13606,15 +15047,15 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
       "military"
-    ]
+    ],
+    "moji": "💂🏾"
   },
   "guardsman_tone5": {
     "unicode": "1F482-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "guardsman tone 5",
     "shortname": ":guardsman_tone5:",
     "category": "people",
@@ -13629,18 +15070,18 @@
       "guard",
       "bearskin",
       "hat",
-      "british",
       "queen",
       "ceremonial",
       "military"
-    ]
+    ],
+    "moji": "💂🏿"
   },
   "guitar": {
     "unicode": "1F3B8",
     "unicode_alternates": [],
     "name": "guitar",
     "shortname": ":guitar:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13648,12 +15089,11 @@
       "music",
       "guitar",
       "string",
-      "music",
-      "instrument",
       "jam",
       "rock",
       "acoustic",
-      "electric"
+      "electric",
+      "instruments"
     ],
     "moji": "🎸"
   },
@@ -13667,7 +15107,11 @@
     "aliases_ascii": [],
     "keywords": [
       "violence",
-      "weapon"
+      "weapon",
+      "object",
+      "dead",
+      "gun",
+      "sarcastic"
     ],
     "moji": "🔫"
   },
@@ -13676,19 +15120,22 @@
     "unicode_alternates": [],
     "name": "haircut",
     "shortname": ":haircut:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "female",
       "girl",
-      "woman"
+      "woman",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "💇"
   },
   "haircut_tone1": {
     "unicode": "1F487-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "haircut tone 1",
     "shortname": ":haircut_tone1:",
     "category": "people",
@@ -13698,11 +15145,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💇🏻"
   },
   "haircut_tone2": {
     "unicode": "1F487-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "haircut tone 2",
     "shortname": ":haircut_tone2:",
     "category": "people",
@@ -13712,11 +15160,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💇🏼"
   },
   "haircut_tone3": {
     "unicode": "1F487-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "haircut tone 3",
     "shortname": ":haircut_tone3:",
     "category": "people",
@@ -13726,11 +15175,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💇🏽"
   },
   "haircut_tone4": {
     "unicode": "1F487-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "haircut tone 4",
     "shortname": ":haircut_tone4:",
     "category": "people",
@@ -13740,11 +15190,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💇🏾"
   },
   "haircut_tone5": {
     "unicode": "1F487-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "haircut tone 5",
     "shortname": ":haircut_tone5:",
     "category": "people",
@@ -13754,14 +15205,15 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💇🏿"
   },
   "hamburger": {
     "unicode": "1F354",
     "unicode_alternates": [],
     "name": "hamburger",
     "shortname": ":hamburger:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -13769,9 +15221,9 @@
       "meat",
       "hamburger",
       "burger",
-      "meat",
       "cow",
-      "beef"
+      "beef",
+      "america"
     ],
     "moji": "🍔"
   },
@@ -13789,13 +15241,16 @@
       "law",
       "ruling",
       "tools",
-      "verdict"
+      "verdict",
+      "object",
+      "tool",
+      "weapon"
     ],
     "moji": "🔨"
   },
   "hammer_pick": {
     "unicode": "2692",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "hammer and pick",
     "shortname": ":hammer_pick:",
     "category": "objects",
@@ -13805,8 +15260,10 @@
     "aliases_ascii": [],
     "keywords": [
       "object",
-      "tool"
-    ]
+      "tool",
+      "weapon"
+    ],
+    "moji": "⚒"
   },
   "hamster": {
     "unicode": "1F439",
@@ -13836,29 +15293,16 @@
       "hi",
       "five",
       "stop",
-      "halt"
-    ]
-  },
-  "hand_splayed_reverse": {
-    "unicode": "1F591",
-    "unicode_alternates": [],
-    "name": "reversed raised hand with fingers splayed",
-    "shortname": ":hand_splayed_reverse:",
-    "category": "people",
-    "aliases": [
-      ":reversed_raised_hand_with_fingers_splayed:"
+      "halt",
+      "body",
+      "hands",
+      "diversity"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "hi",
-      "five",
-      "stop",
-      "halt"
-    ]
+    "moji": "🖐"
   },
   "hand_splayed_tone1": {
     "unicode": "1F590-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with fingers splayed tone 1",
     "shortname": ":hand_splayed_tone1:",
     "category": "people",
@@ -13871,11 +15315,12 @@
       "five",
       "stop",
       "halt"
-    ]
+    ],
+    "moji": "🖐🏻"
   },
   "hand_splayed_tone2": {
     "unicode": "1F590-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with fingers splayed tone 2",
     "shortname": ":hand_splayed_tone2:",
     "category": "people",
@@ -13888,11 +15333,12 @@
       "five",
       "stop",
       "halt"
-    ]
+    ],
+    "moji": "🖐🏼"
   },
   "hand_splayed_tone3": {
     "unicode": "1F590-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with fingers splayed tone 3",
     "shortname": ":hand_splayed_tone3:",
     "category": "people",
@@ -13905,11 +15351,12 @@
       "five",
       "stop",
       "halt"
-    ]
+    ],
+    "moji": "🖐🏽"
   },
   "hand_splayed_tone4": {
     "unicode": "1F590-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with fingers splayed tone 4",
     "shortname": ":hand_splayed_tone4:",
     "category": "people",
@@ -13922,11 +15369,12 @@
       "five",
       "stop",
       "halt"
-    ]
+    ],
+    "moji": "🖐🏾"
   },
   "hand_splayed_tone5": {
     "unicode": "1F590-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with fingers splayed tone 5",
     "shortname": ":hand_splayed_tone5:",
     "category": "people",
@@ -13939,57 +15387,170 @@
       "five",
       "stop",
       "halt"
-    ]
-  },
-  "hand_victory": {
-    "unicode": "1F594",
-    "unicode_alternates": [],
-    "name": "reversed victory hand",
-    "shortname": ":hand_victory:",
-    "category": "people",
-    "aliases": [
-      ":reversed_victory_hand:"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "fu"
-    ]
+    "moji": "🖐🏿"
   },
   "handbag": {
     "unicode": "1F45C",
     "unicode_alternates": [],
     "name": "handbag",
     "shortname": ":handbag:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "accessories",
       "accessory",
       "bag",
-      "fashion"
+      "fashion",
+      "women",
+      "vacation"
     ],
     "moji": "👜"
   },
-  "hard_disk": {
-    "unicode": "1F5B4",
+  "handball": {
+    "unicode": "1F93E",
     "unicode_alternates": [],
-    "name": "hard disk",
-    "shortname": ":hard_disk:",
-    "category": "objects_symbols",
+    "name": "handball",
+    "shortname": ":handball:",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [
-      "save",
-      "technology",
-      "storage",
-      "information",
-      "computer",
-      "drive",
-      "megabyte",
-      "gigabyte",
-      "hd"
-    ]
+    "keywords": [],
+    "moji": "🤾"
+  },
+  "handball_tone1": {
+    "unicode": "1F93E-1F3FB",
+    "unicode_alternates": [],
+    "name": "handball tone 1",
+    "shortname": ":handball_tone1:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤾🏻"
+  },
+  "handball_tone2": {
+    "unicode": "1F93E-1F3FC",
+    "unicode_alternates": [],
+    "name": "handball tone 2",
+    "shortname": ":handball_tone2:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤾🏼"
+  },
+  "handball_tone3": {
+    "unicode": "1F93E-1F3FD",
+    "unicode_alternates": [],
+    "name": "handball tone 3",
+    "shortname": ":handball_tone3:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤾🏽"
+  },
+  "handball_tone4": {
+    "unicode": "1F93E-1F3FE",
+    "unicode_alternates": [],
+    "name": "handball tone 4",
+    "shortname": ":handball_tone4:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤾🏾"
+  },
+  "handball_tone5": {
+    "unicode": "1F93E-1F3FF",
+    "unicode_alternates": [],
+    "name": "handball tone 5",
+    "shortname": ":handball_tone5:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤾🏿"
+  },
+  "handshake": {
+    "unicode": "1F91D",
+    "unicode_alternates": [],
+    "name": "handshake",
+    "shortname": ":handshake:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝"
+  },
+  "handshake_tone1": {
+    "unicode": "1F91D-1F3FB",
+    "unicode_alternates": [],
+    "name": "handshake tone 1",
+    "shortname": ":handshake_tone1:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝🏻"
+  },
+  "handshake_tone2": {
+    "unicode": "1F91D-1F3FC",
+    "unicode_alternates": [],
+    "name": "handshake tone 2",
+    "shortname": ":handshake_tone2:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝🏼"
+  },
+  "handshake_tone3": {
+    "unicode": "1F91D-1F3FD",
+    "unicode_alternates": [],
+    "name": "handshake tone 3",
+    "shortname": ":handshake_tone3:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝🏽"
+  },
+  "handshake_tone4": {
+    "unicode": "1F91D-1F3FE",
+    "unicode_alternates": [],
+    "name": "handshake tone 4",
+    "shortname": ":handshake_tone4:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝🏾"
+  },
+  "handshake_tone5": {
+    "unicode": "1F91D-1F3FF",
+    "unicode_alternates": [],
+    "name": "handshake tone 5",
+    "shortname": ":handshake_tone5:",
+    "category": "people",
+    "aliases": [
+      ":shaking_hands_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤝🏿"
   },
   "hash": {
     "moji": "#⃣",
@@ -13999,11 +15560,12 @@
     ],
     "name": "number sign",
     "shortname": ":hash:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "symbol"
+      "symbol",
+      "number"
     ]
   },
   "hatched_chick": {
@@ -14018,12 +15580,11 @@
       "baby",
       "chicken",
       "chick",
-      "baby",
       "bird",
-      "chicken",
       "young",
       "woman",
-      "cute"
+      "cute",
+      "animal"
     ],
     "moji": "🐥"
   },
@@ -14040,19 +15601,18 @@
       "chicken",
       "egg",
       "chick",
-      "egg",
       "baby",
       "bird",
-      "chicken",
       "young",
       "woman",
-      "cute"
+      "cute",
+      "animal"
     ],
     "moji": "🐣"
   },
   "head_bandage": {
     "unicode": "1F915",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with head-bandage",
     "shortname": ":head_bandage:",
     "category": "people",
@@ -14060,14 +15620,20 @@
       ":face_with_head_bandage:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "smiley",
+      "health",
+      "sick",
+      "emotion"
+    ],
+    "moji": "🤕"
   },
   "headphones": {
     "unicode": "1F3A7",
     "unicode_alternates": [],
     "name": "headphone",
     "shortname": ":headphones:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14076,12 +15642,12 @@
       "score",
       "headphone",
       "sound",
-      "music",
       "ears",
       "beats",
       "buds",
       "audio",
-      "listen"
+      "listen",
+      "instruments"
     ],
     "moji": "🎧"
   },
@@ -14090,13 +15656,12 @@
     "unicode_alternates": [],
     "name": "hear-no-evil monkey",
     "shortname": ":hear_no_evil:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "animal",
       "monkey",
-      "monkey",
       "ears",
       "hear",
       "sound",
@@ -14112,7 +15677,7 @@
     ],
     "name": "heavy black heart",
     "shortname": ":heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [
       "<3"
@@ -14124,7 +15689,6 @@
       "pink",
       "black",
       "heart",
-      "love",
       "passion",
       "romance",
       "intense",
@@ -14132,7 +15696,9 @@
       "death",
       "evil",
       "cold",
-      "valentines"
+      "valentines",
+      "symbol",
+      "parties"
     ]
   },
   "heart_decoration": {
@@ -14140,19 +15706,20 @@
     "unicode_alternates": [],
     "name": "heart decoration",
     "shortname": ":heart_decoration:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "like",
       "love",
-      "purple-square"
+      "purple-square",
+      "symbol"
     ],
     "moji": "💟"
   },
   "heart_exclamation": {
     "unicode": "2763",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "heavy heart exclamation mark ornament",
     "shortname": ":heart_exclamation:",
     "category": "symbols",
@@ -14163,15 +15730,17 @@
     "keywords": [
       "emotion",
       "punctuation",
-      "symbol"
-    ]
+      "symbol",
+      "love"
+    ],
+    "moji": "❣"
   },
   "heart_eyes": {
     "unicode": "1F60D",
     "unicode_alternates": [],
     "name": "smiling face with heart-shaped eyes",
     "shortname": ":heart_eyes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14185,10 +15754,15 @@
       "smiling",
       "heart",
       "lovestruck",
-      "love",
       "flirt",
       "smile",
-      "heart-shaped"
+      "heart-shaped",
+      "happy",
+      "smiley",
+      "sex",
+      "heart eyes",
+      "emotion",
+      "beautiful"
     ],
     "moji": "😍"
   },
@@ -14197,7 +15771,7 @@
     "unicode_alternates": [],
     "name": "smiling cat face with heart-shaped eyes",
     "shortname": ":heart_eyes_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14208,41 +15782,27 @@
       "love",
       "valentines",
       "lovestruck",
-      "love",
-      "heart"
+      "heart",
+      "heart eyes",
+      "cat",
+      "beautiful"
     ],
     "moji": "😻"
   },
-  "heart_tip": {
-    "unicode": "1F394",
-    "unicode_alternates": [],
-    "name": "heart with tip on the left",
-    "shortname": ":heart_tip:",
-    "category": "celebration",
-    "aliases": [
-      ":heart_with_tip_on_the_left:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "affection",
-      "like",
-      "love",
-      "valentines"
-    ]
-  },
   "heartbeat": {
     "unicode": "1F493",
     "unicode_alternates": [],
     "name": "beating heart",
     "shortname": ":heartbeat:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "affection",
       "like",
       "love",
-      "valentines"
+      "valentines",
+      "symbol"
     ],
     "moji": "💓"
   },
@@ -14251,14 +15811,15 @@
     "unicode_alternates": [],
     "name": "growing heart",
     "shortname": ":heartpulse:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "affection",
       "like",
       "love",
-      "valentines"
+      "valentines",
+      "symbol"
     ],
     "moji": "💗"
   },
@@ -14269,12 +15830,15 @@
     ],
     "name": "black heart suit",
     "shortname": ":hearts:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cards",
-      "poker"
+      "poker",
+      "love",
+      "symbol",
+      "game"
     ],
     "moji": "♥"
   },
@@ -14285,12 +15849,13 @@
     ],
     "name": "heavy check mark",
     "shortname": ":heavy_check_mark:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "nike",
-      "ok"
+      "ok",
+      "symbol"
     ],
     "moji": "✔"
   },
@@ -14299,13 +15864,14 @@
     "unicode_alternates": [],
     "name": "heavy division sign",
     "shortname": ":heavy_division_sign:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "calculation",
       "divide",
-      "math"
+      "math",
+      "symbol"
     ],
     "moji": "➗"
   },
@@ -14314,7 +15880,7 @@
     "unicode_alternates": [],
     "name": "heavy dollar sign",
     "shortname": ":heavy_dollar_sign:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14322,12 +15888,12 @@
       "money",
       "payment",
       "dollar",
-      "currency",
-      "money",
       "cash",
       "sale",
       "purchase",
-      "value"
+      "value",
+      "math",
+      "symbol"
     ],
     "moji": "💲"
   },
@@ -14336,12 +15902,13 @@
     "unicode_alternates": [],
     "name": "heavy minus sign",
     "shortname": ":heavy_minus_sign:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "calculation",
-      "math"
+      "math",
+      "symbol"
     ],
     "moji": "➖"
   },
@@ -14352,12 +15919,13 @@
     ],
     "name": "heavy multiplication x",
     "shortname": ":heavy_multiplication_x:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "calculation",
-      "math"
+      "math",
+      "symbol"
     ],
     "moji": "✖"
   },
@@ -14366,12 +15934,13 @@
     "unicode_alternates": [],
     "name": "heavy plus sign",
     "shortname": ":heavy_plus_sign:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "calculation",
-      "math"
+      "math",
+      "symbol"
     ],
     "moji": "➕"
   },
@@ -14380,7 +15949,7 @@
     "unicode_alternates": [],
     "name": "helicopter",
     "shortname": ":helicopter:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14389,13 +15958,16 @@
       "helicopter",
       "helo",
       "gyro",
-      "gyrocopter"
+      "gyrocopter",
+      "plane",
+      "travel",
+      "fly"
     ],
     "moji": "🚁"
   },
   "helmet_with_cross": {
     "unicode": "26D1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "helmet with white cross",
     "shortname": ":helmet_with_cross:",
     "category": "people",
@@ -14407,8 +15979,12 @@
       "aid",
       "face",
       "hat",
-      "person"
-    ]
+      "person",
+      "object",
+      "accessories",
+      "job"
+    ],
+    "moji": "⛑"
   },
   "herb": {
     "unicode": "1F33F",
@@ -14427,9 +16003,10 @@
       "weed",
       "herb",
       "spice",
-      "plant",
       "cook",
-      "cooking"
+      "cooking",
+      "nature",
+      "leaf"
     ],
     "moji": "🌿"
   },
@@ -14447,7 +16024,9 @@
       "vegetable",
       "hibiscus",
       "flower",
-      "warm"
+      "warm",
+      "nature",
+      "tropical"
     ],
     "moji": "🌺"
   },
@@ -14456,13 +16035,14 @@
     "unicode_alternates": [],
     "name": "high brightness symbol",
     "shortname": ":high_brightness:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "light",
       "summer",
-      "sun"
+      "sun",
+      "symbol"
     ],
     "moji": "🔆"
   },
@@ -14471,45 +16051,57 @@
     "unicode_alternates": [],
     "name": "high-heeled shoe",
     "shortname": ":high_heel:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fashion",
       "female",
-      "shoes"
+      "shoes",
+      "women",
+      "shoe",
+      "sexy",
+      "accessories",
+      "girls night"
     ],
     "moji": "👠"
   },
   "hockey": {
     "unicode": "1F3D2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ice hockey stick and puck",
     "shortname": ":hockey:",
     "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "game",
+      "sport",
+      "hockey"
+    ],
+    "moji": "🏒"
   },
   "hole": {
     "unicode": "1F573",
     "unicode_alternates": [],
     "name": "hole",
     "shortname": ":hole:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "pit",
-      "well"
-    ]
+      "well",
+      "object"
+    ],
+    "moji": "🕳"
   },
   "homes": {
     "unicode": "1F3D8",
     "unicode_alternates": [],
     "name": "house buildings",
     "shortname": ":homes:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":house_buildings:"
     ],
@@ -14521,15 +16113,19 @@
       "mansion",
       "bungalow",
       "ranch",
-      "craftsman"
-    ]
+      "craftsman",
+      "places",
+      "building",
+      "house"
+    ],
+    "moji": "🏘"
   },
   "honey_pot": {
     "unicode": "1F36F",
     "unicode_alternates": [],
     "name": "honey pot",
     "shortname": ":honey_pot:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14537,9 +16133,10 @@
       "sweet",
       "honey",
       "pot",
-      "bees",
       "pooh",
-      "bear"
+      "bear",
+      "food",
+      "vagina"
     ],
     "moji": "🍯"
   },
@@ -14553,7 +16150,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "brown"
+      "brown",
+      "wildlife"
     ],
     "moji": "🐴"
   },
@@ -14562,7 +16160,7 @@
     "unicode_alternates": [],
     "name": "horse racing",
     "shortname": ":horse_racing:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14573,13 +16171,16 @@
       "race",
       "racing",
       "jockey",
-      "triple crown"
+      "triple crown",
+      "men",
+      "sport",
+      "horse racing"
     ],
     "moji": "🏇"
   },
   "horse_racing_tone1": {
     "unicode": "1F3C7-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "horse racing tone 1",
     "shortname": ":horse_racing_tone1:",
     "category": "activity",
@@ -14592,11 +16193,12 @@
       "race",
       "jockey",
       "triple crown"
-    ]
+    ],
+    "moji": "🏇🏻"
   },
   "horse_racing_tone2": {
     "unicode": "1F3C7-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "horse racing tone 2",
     "shortname": ":horse_racing_tone2:",
     "category": "activity",
@@ -14609,11 +16211,12 @@
       "race",
       "jockey",
       "triple crown"
-    ]
+    ],
+    "moji": "🏇🏼"
   },
   "horse_racing_tone3": {
     "unicode": "1F3C7-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "horse racing tone 3",
     "shortname": ":horse_racing_tone3:",
     "category": "activity",
@@ -14626,11 +16229,12 @@
       "race",
       "jockey",
       "triple crown"
-    ]
+    ],
+    "moji": "🏇🏽"
   },
   "horse_racing_tone4": {
     "unicode": "1F3C7-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "horse racing tone 4",
     "shortname": ":horse_racing_tone4:",
     "category": "activity",
@@ -14643,11 +16247,12 @@
       "race",
       "jockey",
       "triple crown"
-    ]
+    ],
+    "moji": "🏇🏾"
   },
   "horse_racing_tone5": {
     "unicode": "1F3C7-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "horse racing tone 5",
     "shortname": ":horse_racing_tone5:",
     "category": "activity",
@@ -14660,21 +16265,24 @@
       "race",
       "jockey",
       "triple crown"
-    ]
+    ],
+    "moji": "🏇🏿"
   },
   "hospital": {
     "unicode": "1F3E5",
     "unicode_alternates": [],
     "name": "hospital",
     "shortname": ":hospital:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "building",
       "doctor",
       "health",
-      "surgery"
+      "surgery",
+      "places",
+      "911"
     ],
     "moji": "🏥"
   },
@@ -14683,7 +16291,7 @@
     "unicode_alternates": [],
     "name": "hot pepper",
     "shortname": ":hot_pepper:",
-    "category": "food_drink",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14693,27 +16301,33 @@
       "chili",
       "cayenne",
       "habanero",
-      "jalapeno"
-    ]
+      "jalapeno",
+      "vegetables"
+    ],
+    "moji": "🌶"
   },
   "hotdog": {
     "unicode": "1F32D",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "hot dog",
     "shortname": ":hotdog:",
-    "category": "foods",
+    "category": "food",
     "aliases": [
       ":hot_dog:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "america",
+      "food"
+    ],
+    "moji": "🌭"
   },
   "hotel": {
     "unicode": "1F3E8",
     "unicode_alternates": [],
     "name": "hotel",
     "shortname": ":hotel:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14724,7 +16338,9 @@
       "hotel",
       "motel",
       "holiday inn",
-      "hospital"
+      "hospital",
+      "places",
+      "vacation"
     ],
     "moji": "🏨"
   },
@@ -14735,13 +16351,14 @@
     ],
     "name": "hot springs",
     "shortname": ":hotsprings:",
-    "category": "places",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "bath",
       "relax",
-      "warm"
+      "warm",
+      "symbol"
     ],
     "moji": "♨"
   },
@@ -14758,7 +16375,8 @@
     "keywords": [
       "clock",
       "oldschool",
-      "time"
+      "time",
+      "object"
     ],
     "moji": "⌛"
   },
@@ -14773,7 +16391,8 @@
     "keywords": [
       "countdown",
       "oldschool",
-      "time"
+      "time",
+      "object"
     ],
     "moji": "⏳"
   },
@@ -14782,20 +16401,20 @@
     "unicode_alternates": [],
     "name": "house building",
     "shortname": ":house:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "building",
       "home",
       "house",
-      "home",
       "residence",
       "dwelling",
       "mansion",
       "bungalow",
       "ranch",
-      "craftsman"
+      "craftsman",
+      "places"
     ],
     "moji": "🏠"
   },
@@ -14804,7 +16423,7 @@
     "unicode_alternates": [],
     "name": "derelict house building",
     "shortname": ":house_abandoned:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":derelict_house_building:"
     ],
@@ -14821,27 +16440,34 @@
       "abandoned",
       "vacant",
       "run down",
-      "shoddy"
-    ]
+      "shoddy",
+      "places",
+      "building",
+      "house"
+    ],
+    "moji": "🏚"
   },
   "house_with_garden": {
     "unicode": "1F3E1",
     "unicode_alternates": [],
     "name": "house with garden",
     "shortname": ":house_with_garden:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "home",
       "nature",
-      "plant"
+      "plant",
+      "places",
+      "building",
+      "house"
     ],
     "moji": "🏡"
   },
   "hugging": {
     "unicode": "1F917",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "hugging face",
     "shortname": ":hugging:",
     "category": "people",
@@ -14849,14 +16475,19 @@
       ":hugging_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "smiley",
+      "hug",
+      "thank you"
+    ],
+    "moji": "🤗"
   },
   "hushed": {
     "unicode": "1F62F",
     "unicode_alternates": [],
     "name": "hushed face",
     "shortname": ":hushed:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14865,7 +16496,10 @@
       "quiet",
       "hush",
       "whisper",
-      "silent"
+      "silent",
+      "smiley",
+      "surprised",
+      "wow"
     ],
     "moji": "😯"
   },
@@ -14874,7 +16508,7 @@
     "unicode_alternates": [],
     "name": "ice cream",
     "shortname": ":ice_cream:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14896,7 +16530,7 @@
   },
   "ice_skate": {
     "unicode": "26F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ice skate",
     "shortname": ":ice_skate:",
     "category": "activity",
@@ -14905,15 +16539,18 @@
     "keywords": [
       "place",
       "sport",
-      "travel"
-    ]
+      "travel",
+      "cold",
+      "ice skating"
+    ],
+    "moji": "⛸"
   },
   "icecream": {
     "unicode": "1F366",
     "unicode_alternates": [],
     "name": "soft ice cream",
     "shortname": ":icecream:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14935,7 +16572,7 @@
   },
   "id": {
     "unicode": "1F194",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "squared id",
     "shortname": ":id:",
     "category": "symbols",
@@ -14947,21 +16584,24 @@
       "identity",
       "symbol",
       "word"
-    ]
+    ],
+    "moji": "🆔"
   },
   "ideograph_advantage": {
     "unicode": "1F250",
     "unicode_alternates": [],
     "name": "circled ideograph advantage",
     "shortname": ":ideograph_advantage:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "get",
       "kanji",
-      "obtain"
+      "obtain",
+      "japan",
+      "symbol"
     ],
     "moji": "🉐"
   },
@@ -14970,7 +16610,7 @@
     "unicode_alternates": [],
     "name": "imp",
     "shortname": ":imp:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -14979,7 +16619,9 @@
       "evil",
       "horns",
       "cute",
-      "devil"
+      "smiley",
+      "monster",
+      "wth"
     ],
     "moji": "👿"
   },
@@ -14993,7 +16635,9 @@
     "aliases_ascii": [],
     "keywords": [
       "documents",
-      "email"
+      "email",
+      "work",
+      "office"
     ],
     "moji": "📥"
   },
@@ -15007,30 +16651,17 @@
     "aliases_ascii": [],
     "keywords": [
       "email",
-      "inbox"
+      "inbox",
+      "object"
     ],
     "moji": "📨"
   },
-  "info": {
-    "unicode": "1F6C8",
-    "unicode_alternates": [],
-    "name": "circled information source",
-    "shortname": ":info:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":circled_information_source:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "icon"
-    ]
-  },
   "information_desk_person": {
     "unicode": "1F481",
     "unicode_alternates": [],
     "name": "information desk person",
     "shortname": ":information_desk_person:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15045,13 +16676,16 @@
       "sassy",
       "unimpressed",
       "attitude",
-      "snarky"
+      "snarky",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "💁"
   },
   "information_desk_person_tone1": {
     "unicode": "1F481-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "information desk person tone 1",
     "shortname": ":information_desk_person_tone1:",
     "category": "people",
@@ -15069,11 +16703,12 @@
       "unimpressed",
       "attitude",
       "snarky"
-    ]
+    ],
+    "moji": "💁🏻"
   },
   "information_desk_person_tone2": {
     "unicode": "1F481-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "information desk person tone 2",
     "shortname": ":information_desk_person_tone2:",
     "category": "people",
@@ -15091,11 +16726,12 @@
       "unimpressed",
       "attitude",
       "snarky"
-    ]
+    ],
+    "moji": "💁🏼"
   },
   "information_desk_person_tone3": {
     "unicode": "1F481-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "information desk person tone 3",
     "shortname": ":information_desk_person_tone3:",
     "category": "people",
@@ -15113,11 +16749,12 @@
       "unimpressed",
       "attitude",
       "snarky"
-    ]
+    ],
+    "moji": "💁🏽"
   },
   "information_desk_person_tone4": {
     "unicode": "1F481-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "information desk person tone 4",
     "shortname": ":information_desk_person_tone4:",
     "category": "people",
@@ -15135,11 +16772,12 @@
       "unimpressed",
       "attitude",
       "snarky"
-    ]
+    ],
+    "moji": "💁🏾"
   },
   "information_desk_person_tone5": {
     "unicode": "1F481-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "information desk person tone 5",
     "shortname": ":information_desk_person_tone5:",
     "category": "people",
@@ -15157,7 +16795,8 @@
       "unimpressed",
       "attitude",
       "snarky"
-    ]
+    ],
+    "moji": "💁🏿"
   },
   "information_source": {
     "unicode": "2139",
@@ -15166,13 +16805,14 @@
     ],
     "name": "information source",
     "shortname": ":information_source:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "blue-square",
-      "letter"
+      "letter",
+      "symbol"
     ],
     "moji": "ℹ"
   },
@@ -15181,7 +16821,7 @@
     "unicode_alternates": [],
     "name": "smiling face with halo",
     "shortname": ":innocent:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "O:-)",
@@ -15202,12 +16842,12 @@
       "angel",
       "face",
       "halo",
-      "halo",
-      "angel",
       "innocent",
       "ring",
       "circle",
-      "heaven"
+      "heaven",
+      "smiley",
+      "emotion"
     ],
     "moji": "😇"
   },
@@ -15218,13 +16858,14 @@
     ],
     "name": "exclamation question mark",
     "shortname": ":interrobang:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "punctuation",
       "surprise",
-      "wat"
+      "wat",
+      "symbol"
     ],
     "moji": "⁉"
   },
@@ -15240,7 +16881,10 @@
       "apple",
       "dial",
       "gadgets",
-      "technology"
+      "technology",
+      "electronics",
+      "phone",
+      "selfie"
     ],
     "moji": "📱"
   },
@@ -15249,7 +16893,7 @@
     "unicode_alternates": [],
     "name": "desert island",
     "shortname": ":island:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":desert_island:"
     ],
@@ -15257,15 +16901,22 @@
     "keywords": [
       "land",
       "solitude",
-      "alone"
-    ]
+      "alone",
+      "places",
+      "travel",
+      "vacation",
+      "tropical",
+      "beach",
+      "swim"
+    ],
+    "moji": "🏝"
   },
   "izakaya_lantern": {
     "unicode": "1F3EE",
     "unicode_alternates": [],
     "name": "izakaya lantern",
     "shortname": ":izakaya_lantern:",
-    "category": "places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15277,7 +16928,9 @@
       "alcohol",
       "bar",
       "sake",
-      "restaurant"
+      "restaurant",
+      "object",
+      "japan"
     ],
     "moji": "🏮"
   },
@@ -15286,14 +16939,13 @@
     "unicode_alternates": [],
     "name": "jack-o-lantern",
     "shortname": ":jack_o_lantern:",
-    "category": "objects",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "halloween",
       "jack-o-lantern",
       "pumpkin",
-      "halloween",
       "holiday",
       "carve",
       "autumn",
@@ -15305,7 +16957,8 @@
       "horror",
       "scary",
       "scared",
-      "dead"
+      "dead",
+      "holidays"
     ],
     "moji": "🎃"
   },
@@ -15314,11 +16967,16 @@
     "unicode_alternates": [],
     "name": "silhouette of japan",
     "shortname": ":japan:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "nation"
+      "nation",
+      "places",
+      "travel",
+      "map",
+      "vacation",
+      "tropical"
     ],
     "moji": "🗾"
   },
@@ -15327,7 +16985,7 @@
     "unicode_alternates": [],
     "name": "japanese castle",
     "shortname": ":japanese_castle:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15339,7 +16997,10 @@
       "royalty",
       "fort",
       "fortified",
-      "fortress"
+      "fortress",
+      "places",
+      "travel",
+      "vacation"
     ],
     "moji": "🏯"
   },
@@ -15348,7 +17009,7 @@
     "unicode_alternates": [],
     "name": "japanese goblin",
     "shortname": ":japanese_goblin:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15361,13 +17022,14 @@
       "avian",
       "demon",
       "goblin",
-      "mask",
       "theater",
       "nose",
       "frown",
       "mustache",
       "anger",
-      "frustration"
+      "frustration",
+      "angry",
+      "monster"
     ],
     "moji": "👺"
   },
@@ -15376,7 +17038,7 @@
     "unicode_alternates": [],
     "name": "japanese ogre",
     "shortname": ":japanese_ogre:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15387,7 +17049,6 @@
       "troll",
       "ogre",
       "folklore",
-      "monster",
       "devil",
       "mask",
       "theater",
@@ -15401,7 +17062,7 @@
     "unicode_alternates": [],
     "name": "jeans",
     "shortname": ":jeans:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15419,26 +17080,12 @@
     ],
     "moji": "👖"
   },
-  "jet_up": {
-    "unicode": "1F6E6",
-    "unicode_alternates": [],
-    "name": "up-pointing military airplane",
-    "shortname": ":jet_up:",
-    "category": "travel_places",
-    "aliases": [
-      ":up_pointing_military_airplane:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "jet"
-    ]
-  },
   "joy": {
     "unicode": "1F602",
     "unicode_alternates": [],
     "name": "face with tears of joy",
     "shortname": ":joy:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":')",
@@ -15450,11 +17097,13 @@
       "haha",
       "happy",
       "tears",
-      "tears",
-      "cry",
       "joy",
-      "happy",
-      "weep"
+      "weep",
+      "silly",
+      "smiley",
+      "laugh",
+      "emotion",
+      "sarcastic"
     ],
     "moji": "😂"
   },
@@ -15463,7 +17112,7 @@
     "unicode_alternates": [],
     "name": "cat face with tears of joy",
     "shortname": ":joy_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15472,36 +17121,126 @@
       "haha",
       "happy",
       "tears",
-      "happy",
-      "tears",
       "cry",
-      "joy"
+      "joy",
+      "silly",
+      "laugh",
+      "cat",
+      "sarcastic"
+    ],
+    "moji": "😹"
+  },
+  "joystick": {
+    "unicode": "1F579",
+    "unicode_alternates": [],
+    "name": "joystick",
+    "shortname": ":joystick:",
+    "category": "objects",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [
+      "games",
+      "atari",
+      "controller",
+      "electronics",
+      "game",
+      "boys night"
+    ],
+    "moji": "🕹"
+  },
+  "juggling": {
+    "unicode": "1F939",
+    "unicode_alternates": [],
+    "name": "juggling",
+    "shortname": ":juggling:",
+    "category": "activity",
+    "aliases": [
+      ":juggler:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤹"
+  },
+  "juggling_tone1": {
+    "unicode": "1F939-1F3FB",
+    "unicode_alternates": [],
+    "name": "juggling tone 1",
+    "shortname": ":juggling_tone1:",
+    "category": "activity",
+    "aliases": [
+      ":juggler_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤹🏻"
+  },
+  "juggling_tone2": {
+    "unicode": "1F939-1F3FC",
+    "unicode_alternates": [],
+    "name": "juggling tone 2",
+    "shortname": ":juggling_tone2:",
+    "category": "activity",
+    "aliases": [
+      ":juggler_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤹🏼"
+  },
+  "juggling_tone3": {
+    "unicode": "1F939-1F3FD",
+    "unicode_alternates": [],
+    "name": "juggling tone 3",
+    "shortname": ":juggling_tone3:",
+    "category": "activity",
+    "aliases": [
+      ":juggler_tone3:"
     ],
-    "moji": "😹"
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤹🏽"
   },
-  "joystick": {
-    "unicode": "1F579",
+  "juggling_tone4": {
+    "unicode": "1F939-1F3FE",
     "unicode_alternates": [],
-    "name": "joystick",
-    "shortname": ":joystick:",
-    "category": "objects_symbols",
-    "aliases": [],
+    "name": "juggling tone 4",
+    "shortname": ":juggling_tone4:",
+    "category": "activity",
+    "aliases": [
+      ":juggler_tone4:"
+    ],
     "aliases_ascii": [],
-    "keywords": [
-      "games",
-      "atari",
-      "controller"
-    ]
+    "keywords": [],
+    "moji": "🤹🏾"
+  },
+  "juggling_tone5": {
+    "unicode": "1F939-1F3FF",
+    "unicode_alternates": [],
+    "name": "juggling tone 5",
+    "shortname": ":juggling_tone5:",
+    "category": "activity",
+    "aliases": [
+      ":juggler_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤹🏿"
   },
   "kaaba": {
     "unicode": "1F54B",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "kaaba",
     "shortname": ":kaaba:",
     "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "places",
+      "religion",
+      "building",
+      "condolence"
+    ],
+    "moji": "🕋"
   },
   "key": {
     "unicode": "1F511",
@@ -15514,7 +17253,8 @@
     "keywords": [
       "door",
       "lock",
-      "password"
+      "password",
+      "object"
     ],
     "moji": "🔑"
   },
@@ -15523,7 +17263,7 @@
     "unicode_alternates": [],
     "name": "old key",
     "shortname": ":key2:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":old_key:"
     ],
@@ -15532,79 +17272,34 @@
       "door",
       "lock",
       "password",
-      "skeleton"
-    ]
-  },
-  "keyboard": {
-    "unicode": "1F5AE",
-    "unicode_alternates": [],
-    "name": "wired keyboard",
-    "shortname": ":keyboard:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":wired_keyboard:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "typing",
-      "keys",
-      "input",
-      "device"
-    ]
-  },
-  "keyboard_mouse": {
-    "unicode": "1F5A6",
-    "unicode_alternates": [],
-    "name": "keyboard and mouse",
-    "shortname": ":keyboard_mouse:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":keyboard_and_mouse:"
+      "skeleton",
+      "object"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "computer",
-      "input",
-      "desktop"
-    ]
+    "moji": "🗝"
   },
-  "keyboard_with_jacks": {
-    "unicode": "1F398",
-    "unicode_alternates": [],
-    "name": "musical keyboard with jacks",
-    "shortname": ":keyboard_with_jacks:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":musical_keyboard_with_jacks:"
+  "keyboard": {
+    "unicode": "2328",
+    "unicode_alternates": [
+      "2328-FE0F"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "music",
-      "instrument",
-      "midi"
-    ]
-  },
-  "keycap_ten": {
-    "unicode": "1F51F",
-    "unicode_alternates": [],
-    "name": "keycap ten",
-    "shortname": ":keycap_ten:",
-    "category": "other",
+    "name": "keyboard",
+    "shortname": ":keyboard:",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "10",
-      "blue-square",
-      "numbers"
+      "electronics",
+      "work",
+      "office"
     ],
-    "moji": "🔟"
+    "moji": "⌨"
   },
   "kimono": {
     "unicode": "1F458",
     "unicode_alternates": [],
     "name": "kimono",
     "shortname": ":kimono:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15621,7 +17316,7 @@
     "unicode_alternates": [],
     "name": "kiss mark",
     "shortname": ":kiss:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15630,7 +17325,12 @@
       "like",
       "lips",
       "love",
-      "valentines"
+      "valentines",
+      "women",
+      "sexy",
+      "lip",
+      "beautiful",
+      "girls night"
     ],
     "moji": "💋"
   },
@@ -15652,8 +17352,14 @@
       "love",
       "marriage",
       "valentines",
-      "couple"
-    ]
+      "couple",
+      "people",
+      "gay",
+      "men",
+      "sex",
+      "lgbt"
+    ],
+    "moji": "👨‍❤️‍💋‍👨"
   },
   "kiss_ww": {
     "unicode": "1F469-2764-1F48B-1F469",
@@ -15673,15 +17379,21 @@
       "love",
       "marriage",
       "valentines",
-      "couple"
-    ]
+      "couple",
+      "people",
+      "women",
+      "sex",
+      "lgbt",
+      "lesbian"
+    ],
+    "moji": "👩‍❤️‍💋‍👩"
   },
   "kissing": {
     "unicode": "1F617",
     "unicode_alternates": [],
     "name": "kissing face",
     "shortname": ":kissing:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15695,7 +17407,9 @@
       "kiss",
       "pucker",
       "lips",
-      "smooch"
+      "smooch",
+      "smiley",
+      "sexy"
     ],
     "moji": "😗"
   },
@@ -15704,7 +17418,7 @@
     "unicode_alternates": [],
     "name": "kissing cat face with closed eyes",
     "shortname": ":kissing_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15714,7 +17428,8 @@
       "kiss",
       "puckered",
       "heart",
-      "love"
+      "love",
+      "cat"
     ],
     "moji": "😽"
   },
@@ -15723,7 +17438,7 @@
     "unicode_alternates": [],
     "name": "kissing face with closed eyes",
     "shortname": ":kissing_closed_eyes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15738,8 +17453,9 @@
       "passion",
       "puckered",
       "heart",
-      "love",
-      "smooch"
+      "smooch",
+      "smiley",
+      "sexy"
     ],
     "moji": "😚"
   },
@@ -15748,7 +17464,7 @@
     "unicode_alternates": [],
     "name": "face throwing a kiss",
     "shortname": ":kissing_heart:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":*",
@@ -15766,8 +17482,9 @@
       "love",
       "lips",
       "like",
-      "love",
-      "valentines"
+      "valentines",
+      "smiley",
+      "sexy"
     ],
     "moji": "😘"
   },
@@ -15776,7 +17493,7 @@
     "unicode_alternates": [],
     "name": "kissing face with smiling eyes",
     "shortname": ":kissing_smiling_eyes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15789,10 +17506,25 @@
       "smile",
       "pucker",
       "lips",
-      "smooch"
+      "smooch",
+      "smiley",
+      "sexy"
     ],
     "moji": "😙"
   },
+  "kiwi": {
+    "unicode": "1F95D",
+    "unicode_alternates": [],
+    "name": "kiwifruit",
+    "shortname": ":kiwi:",
+    "category": "food",
+    "aliases": [
+      ":kiwifruit:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥝"
+  },
   "knife": {
     "unicode": "1F52A",
     "unicode_alternates": [],
@@ -15801,7 +17533,10 @@
     "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "object",
+      "weapon"
+    ],
     "moji": "🔪"
   },
   "koala": {
@@ -15814,7 +17549,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐨"
   },
@@ -15823,7 +17559,7 @@
     "unicode_alternates": [],
     "name": "squared katakana koko",
     "shortname": ":koko:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -15831,7 +17567,8 @@
       "destination",
       "here",
       "japanese",
-      "katakana"
+      "katakana",
+      "symbol"
     ],
     "moji": "🈁"
   },
@@ -15840,22 +17577,28 @@
     "unicode_alternates": [],
     "name": "label",
     "shortname": ":label:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "tag"
-    ]
+      "tag",
+      "object"
+    ],
+    "moji": "🏷"
   },
   "large_blue_circle": {
     "unicode": "1F535",
     "unicode_alternates": [],
     "name": "large blue circle",
     "shortname": ":large_blue_circle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "shapes",
+      "symbol",
+      "circle"
+    ],
     "moji": "🔵"
   },
   "large_blue_diamond": {
@@ -15863,11 +17606,13 @@
     "unicode_alternates": [],
     "name": "large blue diamond",
     "shortname": ":large_blue_diamond:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol"
     ],
     "moji": "🔷"
   },
@@ -15876,11 +17621,13 @@
     "unicode_alternates": [],
     "name": "large orange diamond",
     "shortname": ":large_orange_diamond:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol"
     ],
     "moji": "🔶"
   },
@@ -15900,7 +17647,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌗"
   },
@@ -15922,7 +17670,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌜"
   },
@@ -15931,7 +17680,7 @@
     "unicode_alternates": [],
     "name": "smiling face with open mouth and tightly-closed ey",
     "shortname": ":laughing:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":satisfied:"
     ],
@@ -15947,7 +17696,9 @@
       "lol",
       "smiling",
       "laughing",
-      "laugh"
+      "laugh",
+      "smiley",
+      "emotion"
     ],
     "moji": "😆"
   },
@@ -15984,16 +17735,97 @@
     "aliases_ascii": [],
     "keywords": [
       "notes",
-      "paper"
+      "paper",
+      "object",
+      "office",
+      "write"
     ],
     "moji": "📒"
   },
+  "left_facing_fist": {
+    "unicode": "1F91B",
+    "unicode_alternates": [],
+    "name": "left-facing fist",
+    "shortname": ":left_facing_fist:",
+    "category": "people",
+    "aliases": [
+      ":left_fist:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛"
+  },
+  "left_facing_fist_tone1": {
+    "unicode": "1F91B-1F3FB",
+    "unicode_alternates": [],
+    "name": "left facing fist tone 1",
+    "shortname": ":left_facing_fist_tone1:",
+    "category": "people",
+    "aliases": [
+      ":left_fist_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛🏻"
+  },
+  "left_facing_fist_tone2": {
+    "unicode": "1F91B-1F3FC",
+    "unicode_alternates": [],
+    "name": "left facing fist tone 2",
+    "shortname": ":left_facing_fist_tone2:",
+    "category": "people",
+    "aliases": [
+      ":left_fist_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛🏼"
+  },
+  "left_facing_fist_tone3": {
+    "unicode": "1F91B-1F3FD",
+    "unicode_alternates": [],
+    "name": "left facing fist tone 3",
+    "shortname": ":left_facing_fist_tone3:",
+    "category": "people",
+    "aliases": [
+      ":left_fist_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛🏽"
+  },
+  "left_facing_fist_tone4": {
+    "unicode": "1F91B-1F3FE",
+    "unicode_alternates": [],
+    "name": "left facing fist tone 4",
+    "shortname": ":left_facing_fist_tone4:",
+    "category": "people",
+    "aliases": [
+      ":left_fist_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛🏾"
+  },
+  "left_facing_fist_tone5": {
+    "unicode": "1F91B-1F3FF",
+    "unicode_alternates": [],
+    "name": "left facing fist tone 5",
+    "shortname": ":left_facing_fist_tone5:",
+    "category": "people",
+    "aliases": [
+      ":left_fist_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤛🏿"
+  },
   "left_luggage": {
     "unicode": "1F6C5",
     "unicode_alternates": [],
     "name": "left luggage",
     "shortname": ":left_luggage:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16002,26 +17834,10 @@
       "bag",
       "baggage",
       "luggage",
-      "travel"
+      "symbol"
     ],
     "moji": "🛅"
   },
-  "left_receiver": {
-    "unicode": "1F57B",
-    "unicode_alternates": [],
-    "name": "left hand telephone receiver",
-    "shortname": ":left_receiver:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":left_hand_telephone_receiver:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "dial",
-      "technology"
-    ]
-  },
   "left_right_arrow": {
     "unicode": "2194",
     "unicode_alternates": [
@@ -16029,11 +17845,13 @@
     ],
     "name": "left right arrow",
     "shortname": ":left_right_arrow:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "arrow",
+      "symbol"
     ],
     "moji": "↔"
   },
@@ -16044,10 +17862,13 @@
     ],
     "name": "leftwards arrow with hook",
     "shortname": ":leftwards_arrow_with_hook:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "arrow",
+      "symbol"
+    ],
     "moji": "↩"
   },
   "lemon": {
@@ -16055,7 +17876,7 @@
     "unicode_alternates": [],
     "name": "lemon",
     "shortname": ":lemon:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16063,7 +17884,8 @@
       "nature",
       "lemon",
       "yellow",
-      "citrus"
+      "citrus",
+      "food"
     ],
     "moji": "🍋"
   },
@@ -16074,7 +17896,7 @@
     ],
     "name": "leo",
     "shortname": ":leo:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16087,9 +17909,8 @@
       "zodiac",
       "sign",
       "purple-square",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♌"
   },
@@ -16108,7 +17929,9 @@
       "cat",
       "spot",
       "spotted",
-      "sexy"
+      "sexy",
+      "wildlife",
+      "roar"
     ],
     "moji": "🐆"
   },
@@ -16117,27 +17940,31 @@
     "unicode_alternates": [],
     "name": "level slider",
     "shortname": ":level_slider:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "controls"
-    ]
+    ],
+    "moji": "🎚"
   },
   "levitate": {
     "unicode": "1F574",
     "unicode_alternates": [],
     "name": "man in business suit levitating",
     "shortname": ":levitate:",
-    "category": "people",
+    "category": "activity",
     "aliases": [
       ":man_in_business_suit_levitating:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "hover",
-      "exclamation"
-    ]
+      "exclamation",
+      "men",
+      "job"
+    ],
+    "moji": "🕴"
   },
   "libra": {
     "unicode": "264E",
@@ -16146,7 +17973,7 @@
     ],
     "name": "libra",
     "shortname": ":libra:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16159,9 +17986,8 @@
       "zodiac",
       "sign",
       "purple-square",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♎"
   },
@@ -16179,12 +18005,20 @@
       "bench",
       "press",
       "squats",
-      "deadlift"
-    ]
+      "deadlift",
+      "men",
+      "workout",
+      "flex",
+      "sport",
+      "weight lifting",
+      "win",
+      "diversity"
+    ],
+    "moji": "🏋"
   },
   "lifter_tone1": {
     "unicode": "1F3CB-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "weight lifter tone 1",
     "shortname": ":lifter_tone1:",
     "category": "activity",
@@ -16197,11 +18031,12 @@
       "press",
       "squats",
       "deadlift"
-    ]
+    ],
+    "moji": "🏋🏻"
   },
   "lifter_tone2": {
     "unicode": "1F3CB-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "weight lifter tone 2",
     "shortname": ":lifter_tone2:",
     "category": "activity",
@@ -16214,11 +18049,12 @@
       "press",
       "squats",
       "deadlift"
-    ]
+    ],
+    "moji": "🏋🏼"
   },
   "lifter_tone3": {
     "unicode": "1F3CB-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "weight lifter tone 3",
     "shortname": ":lifter_tone3:",
     "category": "activity",
@@ -16231,11 +18067,12 @@
       "press",
       "squats",
       "deadlift"
-    ]
+    ],
+    "moji": "🏋🏽"
   },
   "lifter_tone4": {
     "unicode": "1F3CB-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "weight lifter tone 4",
     "shortname": ":lifter_tone4:",
     "category": "activity",
@@ -16248,11 +18085,12 @@
       "press",
       "squats",
       "deadlift"
-    ]
+    ],
+    "moji": "🏋🏾"
   },
   "lifter_tone5": {
     "unicode": "1F3CB-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "weight lifter tone 5",
     "shortname": ":lifter_tone5:",
     "category": "activity",
@@ -16265,28 +18103,15 @@
       "press",
       "squats",
       "deadlift"
-    ]
-  },
-  "light_check_mark": {
-    "unicode": "1F5F8",
-    "unicode_alternates": [],
-    "name": "light check mark",
-    "shortname": ":light_check_mark:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":light_mark:"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "vote"
-    ]
+    "moji": "🏋🏿"
   },
   "light_rail": {
     "unicode": "1F688",
     "unicode_alternates": [],
     "name": "light rail",
     "shortname": ":light_rail:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16294,7 +18119,8 @@
       "vehicle",
       "train",
       "rail",
-      "light"
+      "light",
+      "travel"
     ],
     "moji": "🚈"
   },
@@ -16303,18 +18129,20 @@
     "unicode_alternates": [],
     "name": "link symbol",
     "shortname": ":link:",
-    "category": "other",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "rings",
-      "url"
+      "url",
+      "symbol",
+      "office"
     ],
     "moji": "🔗"
   },
   "lion_face": {
     "unicode": "1F981",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "lion face",
     "shortname": ":lion_face:",
     "category": "nature",
@@ -16322,50 +18150,62 @@
       ":lion:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "wildlife",
+      "roar",
+      "cat",
+      "animal"
+    ],
+    "moji": "🦁"
   },
   "lips": {
     "unicode": "1F444",
     "unicode_alternates": [],
     "name": "mouth",
     "shortname": ":lips:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "kiss",
-      "mouth"
+      "mouth",
+      "women",
+      "body",
+      "sexy",
+      "lip"
     ],
     "moji": "👄"
   },
-  "lips2": {
-    "unicode": "1F5E2",
-    "unicode_alternates": [],
-    "name": "lips",
-    "shortname": ":lips2:",
-    "category": "people",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "kiss",
-      "mouth"
-    ]
-  },
   "lipstick": {
     "unicode": "1F484",
     "unicode_alternates": [],
     "name": "lipstick",
     "shortname": ":lipstick:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fashion",
       "female",
-      "girl"
+      "girl",
+      "object",
+      "women",
+      "sexy",
+      "lip"
     ],
     "moji": "💄"
   },
+  "lizard": {
+    "unicode": "1F98E",
+    "unicode_alternates": [],
+    "name": "lizard",
+    "shortname": ":lizard:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦎"
+  },
   "lock": {
     "unicode": "1F512",
     "unicode_alternates": [],
@@ -16376,7 +18216,9 @@
     "aliases_ascii": [],
     "keywords": [
       "password",
-      "security"
+      "security",
+      "object",
+      "lock"
     ],
     "moji": "🔒"
   },
@@ -16390,7 +18232,9 @@
     "aliases_ascii": [],
     "keywords": [
       "secret",
-      "security"
+      "security",
+      "object",
+      "lock"
     ],
     "moji": "🔏"
   },
@@ -16399,7 +18243,7 @@
     "unicode_alternates": [],
     "name": "lollipop",
     "shortname": ":lollipop:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16410,9 +18254,8 @@
       "lollipop",
       "stick",
       "lick",
-      "sweet",
       "sugar",
-      "candy"
+      "halloween"
     ],
     "moji": "🍭"
   },
@@ -16421,11 +18264,12 @@
     "unicode_alternates": [],
     "name": "double curly loop",
     "shortname": ":loop:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "curly"
+      "curly",
+      "symbol"
     ],
     "moji": "➿"
   },
@@ -16434,10 +18278,13 @@
     "unicode_alternates": [],
     "name": "speaker with three sound waves",
     "shortname": ":loud_sound:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": [],
+    "keywords": [
+      "alarm",
+      "symbol"
+    ],
     "moji": "🔊"
   },
   "loudspeaker": {
@@ -16445,12 +18292,15 @@
     "unicode_alternates": [],
     "name": "public address loudspeaker",
     "shortname": ":loudspeaker:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "sound",
-      "volume"
+      "volume",
+      "object",
+      "alarm",
+      "symbol"
     ],
     "moji": "📢"
   },
@@ -16459,7 +18309,7 @@
     "unicode_alternates": [],
     "name": "love hotel",
     "shortname": ":love_hotel:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16468,7 +18318,6 @@
       "like",
       "love",
       "hotel",
-      "love",
       "sex",
       "romance",
       "leisure",
@@ -16476,7 +18325,9 @@
       "prostitution",
       "hospital",
       "birth",
-      "happy"
+      "happy",
+      "places",
+      "building"
     ],
     "moji": "🏩"
   },
@@ -16485,7 +18336,7 @@
     "unicode_alternates": [],
     "name": "love letter",
     "shortname": ":love_letter:",
-    "category": "emoticons",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16497,7 +18348,8 @@
       "love",
       "letter",
       "kiss",
-      "heart"
+      "heart",
+      "object"
     ],
     "moji": "💌"
   },
@@ -16506,15 +18358,29 @@
     "unicode_alternates": [],
     "name": "low brightness symbol",
     "shortname": ":low_brightness:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "summer",
-      "sun"
+      "sun",
+      "symbol"
     ],
     "moji": "🔅"
   },
+  "lying_face": {
+    "unicode": "1F925",
+    "unicode_alternates": [],
+    "name": "lying face",
+    "shortname": ":lying_face:",
+    "category": "people",
+    "aliases": [
+      ":liar:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤥"
+  },
   "m": {
     "unicode": "24C2",
     "unicode_alternates": [
@@ -16522,13 +18388,14 @@
     ],
     "name": "circled latin capital letter m",
     "shortname": ":m:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "blue-circle",
-      "letter"
+      "letter",
+      "symbol"
     ],
     "moji": "Ⓜ"
   },
@@ -16546,7 +18413,8 @@
       "detective",
       "investigator",
       "detail",
-      "details"
+      "details",
+      "object"
     ],
     "moji": "🔍"
   },
@@ -16564,7 +18432,8 @@
       "detective",
       "investigator",
       "detail",
-      "details"
+      "details",
+      "object"
     ],
     "moji": "🔎"
   },
@@ -16575,13 +18444,15 @@
     ],
     "name": "mahjong tile red dragon",
     "shortname": ":mahjong:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "game",
-      "kanji"
+      "kanji",
+      "object",
+      "symbol"
     ],
     "moji": "🀄"
   },
@@ -16596,7 +18467,8 @@
     "keywords": [
       "communication",
       "email",
-      "inbox"
+      "inbox",
+      "object"
     ],
     "moji": "📫"
   },
@@ -16611,7 +18483,9 @@
     "keywords": [
       "communication",
       "email",
-      "inbox"
+      "inbox",
+      "object",
+      "office"
     ],
     "moji": "📪"
   },
@@ -16626,7 +18500,8 @@
     "keywords": [
       "communication",
       "email",
-      "inbox"
+      "inbox",
+      "object"
     ],
     "moji": "📬"
   },
@@ -16640,7 +18515,8 @@
     "aliases_ascii": [],
     "keywords": [
       "email",
-      "inbox"
+      "inbox",
+      "object"
     ],
     "moji": "📭"
   },
@@ -16649,7 +18525,7 @@
     "unicode_alternates": [],
     "name": "man",
     "shortname": ":man:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16657,13 +18533,173 @@
       "dad",
       "father",
       "guy",
-      "mustashe"
+      "mustashe",
+      "people",
+      "men",
+      "sex",
+      "diversity",
+      "selfie",
+      "boys night"
     ],
     "moji": "👨"
   },
+  "man_dancing": {
+    "unicode": "1F57A",
+    "unicode_alternates": [],
+    "name": "man dancing",
+    "shortname": ":man_dancing:",
+    "category": "people",
+    "aliases": [
+      ":male_dancer:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺"
+  },
+  "man_dancing_tone1": {
+    "unicode": "1F57A-1F3FB",
+    "unicode_alternates": [],
+    "name": "man dancing tone 1",
+    "shortname": ":man_dancing_tone1:",
+    "category": "activity",
+    "aliases": [
+      ":male_dancer_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺🏻"
+  },
+  "man_dancing_tone2": {
+    "unicode": "1F57A-1F3FC",
+    "unicode_alternates": [],
+    "name": "man dancing tone 2",
+    "shortname": ":man_dancing_tone2:",
+    "category": "activity",
+    "aliases": [
+      ":male_dancer_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺🏼"
+  },
+  "man_dancing_tone3": {
+    "unicode": "1F57A-1F3FD",
+    "unicode_alternates": [],
+    "name": "man dancing tone 3",
+    "shortname": ":man_dancing_tone3:",
+    "category": "activity",
+    "aliases": [
+      ":male_dancer_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺🏽"
+  },
+  "man_dancing_tone4": {
+    "unicode": "1F57A-1F3FE",
+    "unicode_alternates": [],
+    "name": "man dancing tone 4",
+    "shortname": ":man_dancing_tone4:",
+    "category": "activity",
+    "aliases": [
+      ":male_dancer_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺🏾"
+  },
+  "man_dancing_tone5": {
+    "unicode": "1F57A-1F3FF",
+    "unicode_alternates": [],
+    "name": "man dancing tone 5",
+    "shortname": ":man_dancing_tone5:",
+    "category": "activity",
+    "aliases": [
+      ":male_dancer_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🕺🏿"
+  },
+  "man_in_tuxedo": {
+    "unicode": "1F935",
+    "unicode_alternates": [],
+    "name": "man in tuxedo",
+    "shortname": ":man_in_tuxedo:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵"
+  },
+  "man_in_tuxedo_tone1": {
+    "unicode": "1F935-1F3FB",
+    "unicode_alternates": [],
+    "name": "man in tuxedo tone 1",
+    "shortname": ":man_in_tuxedo_tone1:",
+    "category": "people",
+    "aliases": [
+      ":tuxedo_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵🏻"
+  },
+  "man_in_tuxedo_tone2": {
+    "unicode": "1F935-1F3FC",
+    "unicode_alternates": [],
+    "name": "man in tuxedo tone 2",
+    "shortname": ":man_in_tuxedo_tone2:",
+    "category": "people",
+    "aliases": [
+      ":tuxedo_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵🏼"
+  },
+  "man_in_tuxedo_tone3": {
+    "unicode": "1F935-1F3FD",
+    "unicode_alternates": [],
+    "name": "man in tuxedo tone 3",
+    "shortname": ":man_in_tuxedo_tone3:",
+    "category": "people",
+    "aliases": [
+      ":tuxedo_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵🏽"
+  },
+  "man_in_tuxedo_tone4": {
+    "unicode": "1F935-1F3FE",
+    "unicode_alternates": [],
+    "name": "man in tuxedo tone 4",
+    "shortname": ":man_in_tuxedo_tone4:",
+    "category": "people",
+    "aliases": [
+      ":tuxedo_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵🏾"
+  },
+  "man_in_tuxedo_tone5": {
+    "unicode": "1F935-1F3FF",
+    "unicode_alternates": [],
+    "name": "man in tuxedo tone 5",
+    "shortname": ":man_in_tuxedo_tone5:",
+    "category": "people",
+    "aliases": [
+      ":tuxedo_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤵🏿"
+  },
   "man_tone1": {
     "unicode": "1F468-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man tone 1",
     "shortname": ":man_tone1:",
     "category": "people",
@@ -16675,11 +18711,12 @@
       "father",
       "guy",
       "mustache"
-    ]
+    ],
+    "moji": "👨🏻"
   },
   "man_tone2": {
     "unicode": "1F468-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man tone 2",
     "shortname": ":man_tone2:",
     "category": "people",
@@ -16691,11 +18728,12 @@
       "father",
       "guy",
       "mustache"
-    ]
+    ],
+    "moji": "👨🏼"
   },
   "man_tone3": {
     "unicode": "1F468-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man tone 3",
     "shortname": ":man_tone3:",
     "category": "people",
@@ -16707,11 +18745,12 @@
       "father",
       "guy",
       "mustache"
-    ]
+    ],
+    "moji": "👨🏽"
   },
   "man_tone4": {
     "unicode": "1F468-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man tone 4",
     "shortname": ":man_tone4:",
     "category": "people",
@@ -16723,11 +18762,12 @@
       "father",
       "guy",
       "mustache"
-    ]
+    ],
+    "moji": "👨🏾"
   },
   "man_tone5": {
     "unicode": "1F468-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man tone 5",
     "shortname": ":man_tone5:",
     "category": "people",
@@ -16739,14 +18779,15 @@
       "father",
       "guy",
       "mustache"
-    ]
+    ],
+    "moji": "👨🏿"
   },
   "man_with_gua_pi_mao": {
     "unicode": "1F472",
     "unicode_alternates": [],
     "name": "man with gua pi mao",
     "shortname": ":man_with_gua_pi_mao:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16755,13 +18796,17 @@
       "skullcap",
       "chinese",
       "asian",
-      "qing"
+      "qing",
+      "people",
+      "hat",
+      "men",
+      "diversity"
     ],
     "moji": "👲"
   },
   "man_with_gua_pi_mao_tone1": {
     "unicode": "1F472-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with gua pi mao tone 1",
     "shortname": ":man_with_gua_pi_mao_tone1:",
     "category": "people",
@@ -16774,11 +18819,12 @@
       "chinese",
       "asian",
       "qing"
-    ]
+    ],
+    "moji": "👲🏻"
   },
   "man_with_gua_pi_mao_tone2": {
     "unicode": "1F472-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with gua pi mao tone 2",
     "shortname": ":man_with_gua_pi_mao_tone2:",
     "category": "people",
@@ -16791,11 +18837,12 @@
       "chinese",
       "asian",
       "qing"
-    ]
+    ],
+    "moji": "👲🏼"
   },
   "man_with_gua_pi_mao_tone3": {
     "unicode": "1F472-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with gua pi mao tone 3",
     "shortname": ":man_with_gua_pi_mao_tone3:",
     "category": "people",
@@ -16808,11 +18855,12 @@
       "chinese",
       "asian",
       "qing"
-    ]
+    ],
+    "moji": "👲🏽"
   },
   "man_with_gua_pi_mao_tone4": {
     "unicode": "1F472-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with gua pi mao tone 4",
     "shortname": ":man_with_gua_pi_mao_tone4:",
     "category": "people",
@@ -16825,11 +18873,12 @@
       "chinese",
       "asian",
       "qing"
-    ]
+    ],
+    "moji": "👲🏾"
   },
   "man_with_gua_pi_mao_tone5": {
     "unicode": "1F472-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with gua pi mao tone 5",
     "shortname": ":man_with_gua_pi_mao_tone5:",
     "category": "people",
@@ -16842,14 +18891,15 @@
       "chinese",
       "asian",
       "qing"
-    ]
+    ],
+    "moji": "👲🏿"
   },
   "man_with_turban": {
     "unicode": "1F473",
     "unicode_alternates": [],
     "name": "man with turban",
     "shortname": ":man_with_turban:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -16862,13 +18912,16 @@
       "indian",
       "mummy",
       "wisdom",
-      "peace"
+      "peace",
+      "people",
+      "hat",
+      "diversity"
     ],
     "moji": "👳"
   },
   "man_with_turban_tone1": {
     "unicode": "1F473-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with turban tone 1",
     "shortname": ":man_with_turban_tone1:",
     "category": "people",
@@ -16884,11 +18937,12 @@
       "mummy",
       "wisdom",
       "peace"
-    ]
+    ],
+    "moji": "👳🏻"
   },
   "man_with_turban_tone2": {
     "unicode": "1F473-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with turban tone 2",
     "shortname": ":man_with_turban_tone2:",
     "category": "people",
@@ -16904,11 +18958,12 @@
       "mummy",
       "wisdom",
       "peace"
-    ]
+    ],
+    "moji": "👳🏼"
   },
   "man_with_turban_tone3": {
     "unicode": "1F473-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with turban tone 3",
     "shortname": ":man_with_turban_tone3:",
     "category": "people",
@@ -16924,11 +18979,12 @@
       "mummy",
       "wisdom",
       "peace"
-    ]
+    ],
+    "moji": "👳🏽"
   },
   "man_with_turban_tone4": {
     "unicode": "1F473-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with turban tone 4",
     "shortname": ":man_with_turban_tone4:",
     "category": "people",
@@ -16944,11 +19000,12 @@
       "mummy",
       "wisdom",
       "peace"
-    ]
+    ],
+    "moji": "👳🏾"
   },
   "man_with_turban_tone5": {
     "unicode": "1F473-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "man with turban tone 5",
     "shortname": ":man_with_turban_tone5:",
     "category": "people",
@@ -16964,19 +19021,22 @@
       "mummy",
       "wisdom",
       "peace"
-    ]
+    ],
+    "moji": "👳🏿"
   },
   "mans_shoe": {
     "unicode": "1F45E",
     "unicode_alternates": [],
     "name": "mans shoe",
     "shortname": ":mans_shoe:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fashion",
-      "male"
+      "male",
+      "shoe",
+      "accessories"
     ],
     "moji": "👞"
   },
@@ -16985,7 +19045,7 @@
     "unicode_alternates": [],
     "name": "world map",
     "shortname": ":map:",
-    "category": "travel_places",
+    "category": "objects",
     "aliases": [
       ":world_map:"
     ],
@@ -16993,8 +19053,12 @@
     "keywords": [
       "atlas",
       "earth",
-      "cartography"
-    ]
+      "cartography",
+      "travel",
+      "map",
+      "vacation"
+    ],
+    "moji": "🗺"
   },
   "maple_leaf": {
     "unicode": "1F341",
@@ -17012,28 +19076,42 @@
       "maple",
       "leaf",
       "syrup",
-      "canada",
       "tree"
     ],
     "moji": "🍁"
   },
+  "martial_arts_uniform": {
+    "unicode": "1F94B",
+    "unicode_alternates": [],
+    "name": "martial arts uniform",
+    "shortname": ":martial_arts_uniform:",
+    "category": "activity",
+    "aliases": [
+      ":karate_uniform:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥋"
+  },
   "mask": {
     "unicode": "1F637",
     "unicode_alternates": [],
     "name": "face with medical mask",
     "shortname": ":mask:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "face",
       "ill",
       "sick",
-      "sick",
       "virus",
       "flu",
       "medical",
-      "mask"
+      "mask",
+      "smiley",
+      "dead",
+      "health"
     ],
     "moji": "😷"
   },
@@ -17042,19 +19120,22 @@
     "unicode_alternates": [],
     "name": "face massage",
     "shortname": ":massage:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "female",
       "girl",
-      "woman"
+      "woman",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "💆"
   },
   "massage_tone1": {
     "unicode": "1F486-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face massage tone 1",
     "shortname": ":massage_tone1:",
     "category": "people",
@@ -17064,11 +19145,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💆🏻"
   },
   "massage_tone2": {
     "unicode": "1F486-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face massage tone 2",
     "shortname": ":massage_tone2:",
     "category": "people",
@@ -17078,11 +19160,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💆🏼"
   },
   "massage_tone3": {
     "unicode": "1F486-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face massage tone 3",
     "shortname": ":massage_tone3:",
     "category": "people",
@@ -17092,11 +19175,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💆🏽"
   },
   "massage_tone4": {
     "unicode": "1F486-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face massage tone 4",
     "shortname": ":massage_tone4:",
     "category": "people",
@@ -17106,11 +19190,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💆🏾"
   },
   "massage_tone5": {
     "unicode": "1F486-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face massage tone 5",
     "shortname": ":massage_tone5:",
     "category": "people",
@@ -17120,14 +19205,15 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "💆🏿"
   },
   "meat_on_bone": {
     "unicode": "1F356",
     "unicode_alternates": [],
     "name": "meat on bone",
     "shortname": ":meat_on_bone:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17160,21 +19246,27 @@
       "first",
       "show",
       "reward",
-      "achievement"
-    ]
+      "achievement",
+      "object",
+      "sport",
+      "perfect"
+    ],
+    "moji": "🏅"
   },
   "mega": {
     "unicode": "1F4E3",
     "unicode_alternates": [],
     "name": "cheering megaphone",
     "shortname": ":mega:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "sound",
       "speaker",
-      "volume"
+      "volume",
+      "object",
+      "sport"
     ],
     "moji": "📣"
   },
@@ -17183,7 +19275,7 @@
     "unicode_alternates": [],
     "name": "melon",
     "shortname": ":melon:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17192,26 +19284,34 @@
       "nature",
       "melon",
       "cantaloupe",
-      "honeydew"
+      "honeydew",
+      "boobs"
     ],
     "moji": "🍈"
   },
   "menorah": {
     "unicode": "1F54E",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "menorah with nine branches",
     "shortname": ":menorah:",
     "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "religion",
+      "object",
+      "jew",
+      "symbol",
+      "holidays"
+    ],
+    "moji": "🕎"
   },
   "mens": {
     "unicode": "1F6B9",
     "unicode_alternates": [],
     "name": "mens symbol",
     "shortname": ":mens:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17220,17 +19320,17 @@
       "wc",
       "men",
       "bathroom",
-      "restroom",
       "sign",
       "boy",
       "male",
-      "avatar"
+      "avatar",
+      "symbol"
     ],
     "moji": "🚹"
   },
   "metal": {
     "unicode": "1F918",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns",
     "shortname": ":metal:",
     "category": "people",
@@ -17242,12 +19342,19 @@
       "band",
       "concert",
       "fingers",
-      "rocknroll"
-    ]
+      "rocknroll",
+      "body",
+      "hands",
+      "hi",
+      "diversity",
+      "boys night",
+      "parties"
+    ],
+    "moji": "🤘"
   },
   "metal_tone1": {
     "unicode": "1F918-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns tone 1",
     "shortname": ":metal_tone1:",
     "category": "people",
@@ -17260,11 +19367,12 @@
       "concert",
       "fingers",
       "rocknroll"
-    ]
+    ],
+    "moji": "🤘🏻"
   },
   "metal_tone2": {
     "unicode": "1F918-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns tone 2",
     "shortname": ":metal_tone2:",
     "category": "people",
@@ -17277,11 +19385,12 @@
       "concert",
       "fingers",
       "rocknroll"
-    ]
+    ],
+    "moji": "🤘🏼"
   },
   "metal_tone3": {
     "unicode": "1F918-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns tone 3",
     "shortname": ":metal_tone3:",
     "category": "people",
@@ -17294,11 +19403,12 @@
       "concert",
       "fingers",
       "rocknroll"
-    ]
+    ],
+    "moji": "🤘🏽"
   },
   "metal_tone4": {
     "unicode": "1F918-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns tone 4",
     "shortname": ":metal_tone4:",
     "category": "people",
@@ -17311,11 +19421,12 @@
       "concert",
       "fingers",
       "rocknroll"
-    ]
+    ],
+    "moji": "🤘🏾"
   },
   "metal_tone5": {
     "unicode": "1F918-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sign of the horns tone 5",
     "shortname": ":metal_tone5:",
     "category": "people",
@@ -17328,14 +19439,15 @@
       "concert",
       "fingers",
       "rocknroll"
-    ]
+    ],
+    "moji": "🤘🏿"
   },
   "metro": {
     "unicode": "1F687",
     "unicode_alternates": [],
     "name": "metro",
     "shortname": ":metro:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17346,8 +19458,8 @@
       "underground",
       "metro",
       "subway",
-      "underground",
-      "train"
+      "train",
+      "travel"
     ],
     "moji": "🚇"
   },
@@ -17356,7 +19468,7 @@
     "unicode_alternates": [],
     "name": "microphone",
     "shortname": ":microphone:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17366,9 +19478,9 @@
       "microphone",
       "mic",
       "audio",
-      "sound",
       "voice",
-      "karaoke"
+      "karaoke",
+      "instruments"
     ],
     "moji": "🎤"
   },
@@ -17377,7 +19489,7 @@
     "unicode_alternates": [],
     "name": "studio microphone",
     "shortname": ":microphone2:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":studio_microphone:"
     ],
@@ -17385,8 +19497,11 @@
     "keywords": [
       "mic",
       "audio",
-      "recording"
-    ]
+      "recording",
+      "electronics",
+      "object"
+    ],
+    "moji": "🎙"
   },
   "microscope": {
     "unicode": "1F52C",
@@ -17399,7 +19514,9 @@
     "keywords": [
       "experiment",
       "laboratory",
-      "zoomin"
+      "zoomin",
+      "object",
+      "science"
     ],
     "moji": "🔬"
   },
@@ -17414,12 +19531,17 @@
     ],
     "aliases_ascii": [],
     "keywords": [
-      "fu"
-    ]
+      "fu",
+      "body",
+      "hands",
+      "middle finger",
+      "diversity"
+    ],
+    "moji": "🖕"
   },
   "middle_finger_tone1": {
     "unicode": "1F595-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "reversed hand with middle finger extended tone 1",
     "shortname": ":middle_finger_tone1:",
     "category": "people",
@@ -17429,11 +19551,12 @@
     "aliases_ascii": [],
     "keywords": [
       "fu"
-    ]
+    ],
+    "moji": "🖕🏻"
   },
   "middle_finger_tone2": {
     "unicode": "1F595-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "reversed hand with middle finger extended tone 2",
     "shortname": ":middle_finger_tone2:",
     "category": "people",
@@ -17443,11 +19566,12 @@
     "aliases_ascii": [],
     "keywords": [
       "fu"
-    ]
+    ],
+    "moji": "🖕🏼"
   },
   "middle_finger_tone3": {
     "unicode": "1F595-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "reversed hand with middle finger extended tone 3",
     "shortname": ":middle_finger_tone3:",
     "category": "people",
@@ -17457,11 +19581,12 @@
     "aliases_ascii": [],
     "keywords": [
       "fu"
-    ]
+    ],
+    "moji": "🖕🏽"
   },
   "middle_finger_tone4": {
     "unicode": "1F595-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "reversed hand with middle finger extended tone 4",
     "shortname": ":middle_finger_tone4:",
     "category": "people",
@@ -17471,11 +19596,12 @@
     "aliases_ascii": [],
     "keywords": [
       "fu"
-    ]
+    ],
+    "moji": "🖕🏾"
   },
   "middle_finger_tone5": {
     "unicode": "1F595-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "reversed hand with middle finger extended tone 5",
     "shortname": ":middle_finger_tone5:",
     "category": "people",
@@ -17485,14 +19611,15 @@
     "aliases_ascii": [],
     "keywords": [
       "fu"
-    ]
+    ],
+    "moji": "🖕🏿"
   },
   "military_medal": {
     "unicode": "1F396",
     "unicode_alternates": [],
     "name": "military medal",
     "shortname": ":military_medal:",
-    "category": "celebration",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17500,15 +19627,32 @@
       "acknowledgment",
       "purple heart",
       "heroism",
-      "veteran"
-    ]
+      "veteran",
+      "object",
+      "award",
+      "win"
+    ],
+    "moji": "🎖"
+  },
+  "milk": {
+    "unicode": "1F95B",
+    "unicode_alternates": [],
+    "name": "glass of milk",
+    "shortname": ":milk:",
+    "category": "food",
+    "aliases": [
+      ":glass_of_milk:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥛"
   },
   "milky_way": {
     "unicode": "1F30C",
     "unicode_alternates": [],
     "name": "milky way",
     "shortname": ":milky_way:",
-    "category": "nature",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17519,8 +19663,10 @@
       "star",
       "stars",
       "planets",
-      "space",
-      "sky"
+      "sky",
+      "places",
+      "travel",
+      "vacation"
     ],
     "moji": "🌌"
   },
@@ -17529,7 +19675,7 @@
     "unicode_alternates": [],
     "name": "minibus",
     "shortname": ":minibus:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17538,8 +19684,7 @@
       "vehicle",
       "bus",
       "city",
-      "transport",
-      "transportation"
+      "transport"
     ],
     "moji": "🚐"
   },
@@ -17556,7 +19701,8 @@
       "disc",
       "disk",
       "record",
-      "technology"
+      "technology",
+      "electronics"
     ],
     "moji": "💽"
   },
@@ -17565,17 +19711,18 @@
     "unicode_alternates": [],
     "name": "mobile phone off",
     "shortname": ":mobile_phone_off:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "mute"
+      "mute",
+      "symbol"
     ],
     "moji": "📴"
   },
   "money_mouth": {
     "unicode": "1F911",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "money-mouth face",
     "shortname": ":money_mouth:",
     "category": "people",
@@ -17583,7 +19730,14 @@
       ":money_mouth_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "smiley",
+      "win",
+      "money",
+      "emotion",
+      "boys night"
+    ],
+    "moji": "🤑"
   },
   "money_with_wings": {
     "unicode": "1F4B8",
@@ -17607,7 +19761,7 @@
       "burned",
       "gift",
       "cash",
-      "dollar"
+      "boys night"
     ],
     "moji": "💸"
   },
@@ -17622,7 +19776,10 @@
     "keywords": [
       "coins",
       "dollar",
-      "payment"
+      "payment",
+      "bag",
+      "award",
+      "money"
     ],
     "moji": "💰"
   },
@@ -17640,7 +19797,8 @@
       "monkey",
       "primate",
       "banana",
-      "silly"
+      "silly",
+      "wildlife"
     ],
     "moji": "🐒"
   },
@@ -17663,7 +19821,7 @@
     "unicode_alternates": [],
     "name": "monorail",
     "shortname": ":monorail:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17672,66 +19830,18 @@
       "train",
       "mono",
       "rail",
-      "transport"
+      "transport",
+      "travel",
+      "vacation"
     ],
     "moji": "🚝"
   },
-  "mood_bubble": {
-    "unicode": "1F5F0",
-    "unicode_alternates": [],
-    "name": "mood bubble",
-    "shortname": ":mood_bubble:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "conversation",
-      "communication",
-      "comic",
-      "feeling"
-    ]
-  },
-  "mood_bubble_lightning": {
-    "unicode": "1F5F1",
-    "unicode_alternates": [],
-    "name": "lightning mood bubble",
-    "shortname": ":mood_bubble_lightning:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":lightning_mood_bubble:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "conversation",
-      "communication",
-      "comic",
-      "feeling"
-    ]
-  },
-  "mood_lightning": {
-    "unicode": "1F5F2",
-    "unicode_alternates": [],
-    "name": "lightning mood",
-    "shortname": ":mood_lightning:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":lightning_mood:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "zap",
-      "electric",
-      "current"
-    ]
-  },
   "mortar_board": {
     "unicode": "1F393",
     "unicode_alternates": [],
     "name": "graduation cap",
     "shortname": ":mortar_board:",
-    "category": "objects",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17742,33 +19852,55 @@
       "hat",
       "school",
       "university",
-      "graduation",
-      "cap",
       "mortarboard",
       "academic",
       "education",
       "ceremony",
       "square",
-      "tassel"
+      "tassel",
+      "office",
+      "accessories"
     ],
     "moji": "🎓"
   },
   "mosque": {
     "unicode": "1F54C",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mosque",
     "shortname": ":mosque:",
     "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "places",
+      "religion",
+      "building",
+      "vacation",
+      "condolence"
+    ],
+    "moji": "🕌"
+  },
+  "motor_scooter": {
+    "unicode": "1F6F5",
+    "unicode_alternates": [],
+    "name": "motor scooter",
+    "shortname": ":motor_scooter:",
+    "category": "travel",
+    "aliases": [
+      ":motorbike:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [
+      "moped"
+    ],
+    "moji": "🛵"
   },
   "motorboat": {
     "unicode": "1F6E5",
     "unicode_alternates": [],
     "name": "motorboat",
     "shortname": ":motorboat:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17776,30 +19908,35 @@
       "vehicle",
       "boat",
       "speedboat",
-      "powerboat"
-    ]
+      "powerboat",
+      "travel"
+    ],
+    "moji": "🛥"
   },
   "motorcycle": {
     "unicode": "1F3CD",
     "unicode_alternates": [],
     "name": "racing motorcycle",
     "shortname": ":motorcycle:",
-    "category": "activity",
+    "category": "travel",
     "aliases": [
       ":racing_motorcycle:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "bike",
-      "speed"
-    ]
+      "speed",
+      "transportation",
+      "travel"
+    ],
+    "moji": "🏍"
   },
   "motorway": {
     "unicode": "1F6E3",
     "unicode_alternates": [],
     "name": "motorway",
     "shortname": ":motorway:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17807,43 +19944,56 @@
       "highway",
       "freeway",
       "traffic",
-      "travel"
-    ]
+      "travel",
+      "vacation",
+      "camp"
+    ],
+    "moji": "🛣"
   },
   "mount_fuji": {
     "unicode": "1F5FB",
     "unicode_alternates": [],
     "name": "mount fuji",
     "shortname": ":mount_fuji:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "japan",
       "mountain",
       "nature",
-      "photo"
+      "photo",
+      "places",
+      "travel",
+      "vacation",
+      "cold",
+      "camp"
     ],
     "moji": "🗻"
   },
   "mountain": {
     "unicode": "26F0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain",
     "shortname": ":mountain:",
     "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "place"
-    ]
+      "place",
+      "places",
+      "travel",
+      "vacation",
+      "camp"
+    ],
+    "moji": "⛰"
   },
   "mountain_bicyclist": {
     "unicode": "1F6B5",
     "unicode_alternates": [],
     "name": "mountain bicyclist",
     "shortname": ":mountain_bicyclist:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17855,13 +20005,15 @@
       "bike",
       "pedal",
       "bicycle",
-      "transportation"
+      "men",
+      "sport",
+      "diversity"
     ],
     "moji": "🚵"
   },
   "mountain_bicyclist_tone1": {
     "unicode": "1F6B5-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain bicyclist tone 1",
     "shortname": ":mountain_bicyclist_tone1:",
     "category": "activity",
@@ -17872,13 +20024,13 @@
       "transportation",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
-    ]
+      "bicycle"
+    ],
+    "moji": "🚵🏻"
   },
   "mountain_bicyclist_tone2": {
     "unicode": "1F6B5-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain bicyclist tone 2",
     "shortname": ":mountain_bicyclist_tone2:",
     "category": "activity",
@@ -17889,13 +20041,13 @@
       "transportation",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
-    ]
+      "bicycle"
+    ],
+    "moji": "🚵🏼"
   },
   "mountain_bicyclist_tone3": {
     "unicode": "1F6B5-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain bicyclist tone 3",
     "shortname": ":mountain_bicyclist_tone3:",
     "category": "activity",
@@ -17906,13 +20058,13 @@
       "transportation",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
-    ]
+      "bicycle"
+    ],
+    "moji": "🚵🏽"
   },
   "mountain_bicyclist_tone4": {
     "unicode": "1F6B5-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain bicyclist tone 4",
     "shortname": ":mountain_bicyclist_tone4:",
     "category": "activity",
@@ -17923,13 +20075,13 @@
       "transportation",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
-    ]
+      "bicycle"
+    ],
+    "moji": "🚵🏾"
   },
   "mountain_bicyclist_tone5": {
     "unicode": "1F6B5-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "mountain bicyclist tone 5",
     "shortname": ":mountain_bicyclist_tone5:",
     "category": "activity",
@@ -17940,16 +20092,16 @@
       "transportation",
       "bike",
       "pedal",
-      "bicycle",
-      "transportation"
-    ]
+      "bicycle"
+    ],
+    "moji": "🚵🏿"
   },
   "mountain_cableway": {
     "unicode": "1F6A0",
     "unicode_alternates": [],
     "name": "mountain cableway",
     "shortname": ":mountain_cableway:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17959,7 +20111,8 @@
       "cable",
       "rail",
       "train",
-      "railway"
+      "railway",
+      "travel"
     ],
     "moji": "🚠"
   },
@@ -17968,7 +20121,7 @@
     "unicode_alternates": [],
     "name": "mountain railway",
     "shortname": ":mountain_railway:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -17977,7 +20130,8 @@
       "railway",
       "rail",
       "train",
-      "transport"
+      "transport",
+      "travel"
     ],
     "moji": "🚞"
   },
@@ -17986,7 +20140,7 @@
     "unicode_alternates": [],
     "name": "snow capped mountain",
     "shortname": ":mountain_snow:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":snow_capped_mountain:"
     ],
@@ -17995,8 +20149,13 @@
       "cold",
       "elevation",
       "hiking",
-      "peak"
-    ]
+      "peak",
+      "places",
+      "travel",
+      "vacation",
+      "camp"
+    ],
+    "moji": "🏔"
   },
   "mouse": {
     "unicode": "1F42D",
@@ -18029,25 +20188,9 @@
     ],
     "moji": "🐁"
   },
-  "mouse_one": {
-    "unicode": "1F5AF",
-    "unicode_alternates": [],
-    "name": "one button mouse",
-    "shortname": ":mouse_one:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":one_button_mouse:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "computer",
-      "input",
-      "device"
-    ]
-  },
   "mouse_three_button": {
     "unicode": "1F5B1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "three button mouse",
     "shortname": ":mouse_three_button:",
     "category": "objects",
@@ -18059,8 +20202,12 @@
       "3",
       "computer",
       "object",
-      "office"
-    ]
+      "office",
+      "electronics",
+      "work",
+      "game"
+    ],
+    "moji": "🖱"
   },
   "movie_camera": {
     "unicode": "1F3A5",
@@ -18078,7 +20225,8 @@
       "camcorder",
       "video",
       "motion",
-      "picture"
+      "picture",
+      "object"
     ],
     "moji": "🎥"
   },
@@ -18087,21 +20235,101 @@
     "unicode_alternates": [],
     "name": "moyai",
     "shortname": ":moyai:",
-    "category": "places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "island",
-      "stone"
+      "stone",
+      "travel",
+      "vacation"
     ],
     "moji": "🗿"
   },
+  "mrs_claus": {
+    "unicode": "1F936",
+    "unicode_alternates": [],
+    "name": "mother christmas",
+    "shortname": ":mrs_claus:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶"
+  },
+  "mrs_claus_tone1": {
+    "unicode": "1F936-1F3FB",
+    "unicode_alternates": [],
+    "name": "mother christmas tone 1",
+    "shortname": ":mrs_claus_tone1:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶🏻"
+  },
+  "mrs_claus_tone2": {
+    "unicode": "1F936-1F3FC",
+    "unicode_alternates": [],
+    "name": "mother christmas tone 2",
+    "shortname": ":mrs_claus_tone2:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶🏼"
+  },
+  "mrs_claus_tone3": {
+    "unicode": "1F936-1F3FD",
+    "unicode_alternates": [],
+    "name": "mother christmas tone 3",
+    "shortname": ":mrs_claus_tone3:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶🏽"
+  },
+  "mrs_claus_tone4": {
+    "unicode": "1F936-1F3FE",
+    "unicode_alternates": [],
+    "name": "mother christmas tone 4",
+    "shortname": ":mrs_claus_tone4:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶🏾"
+  },
+  "mrs_claus_tone5": {
+    "unicode": "1F936-1F3FF",
+    "unicode_alternates": [],
+    "name": "mother christmas tone 5",
+    "shortname": ":mrs_claus_tone5:",
+    "category": "people",
+    "aliases": [
+      ":mother_christmas_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤶🏿"
+  },
   "muscle": {
     "unicode": "1F4AA",
     "unicode_alternates": [],
     "name": "flexed biceps",
     "shortname": ":muscle:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18110,13 +20338,20 @@
       "hand",
       "strong",
       "muscle",
-      "bicep"
+      "bicep",
+      "body",
+      "hands",
+      "workout",
+      "win",
+      "diversity",
+      "feminist",
+      "boys night"
     ],
     "moji": "💪"
   },
   "muscle_tone1": {
     "unicode": "1F4AA-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "flexed biceps tone 1",
     "shortname": ":muscle_tone1:",
     "category": "people",
@@ -18129,11 +20364,12 @@
       "strong",
       "muscle",
       "bicep"
-    ]
+    ],
+    "moji": "💪🏻"
   },
   "muscle_tone2": {
     "unicode": "1F4AA-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "flexed biceps tone 2",
     "shortname": ":muscle_tone2:",
     "category": "people",
@@ -18146,11 +20382,12 @@
       "strong",
       "muscle",
       "bicep"
-    ]
+    ],
+    "moji": "💪🏼"
   },
   "muscle_tone3": {
     "unicode": "1F4AA-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "flexed biceps tone 3",
     "shortname": ":muscle_tone3:",
     "category": "people",
@@ -18163,11 +20400,12 @@
       "strong",
       "muscle",
       "bicep"
-    ]
+    ],
+    "moji": "💪🏽"
   },
   "muscle_tone4": {
     "unicode": "1F4AA-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "flexed biceps tone 4",
     "shortname": ":muscle_tone4:",
     "category": "people",
@@ -18180,11 +20418,12 @@
       "strong",
       "muscle",
       "bicep"
-    ]
+    ],
+    "moji": "💪🏾"
   },
   "muscle_tone5": {
     "unicode": "1F4AA-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "flexed biceps tone 5",
     "shortname": ":muscle_tone5:",
     "category": "people",
@@ -18197,7 +20436,8 @@
       "strong",
       "muscle",
       "bicep"
-    ]
+    ],
+    "moji": "💪🏿"
   },
   "mushroom": {
     "unicode": "1F344",
@@ -18213,7 +20453,9 @@
       "mushroom",
       "fungi",
       "food",
-      "fungus"
+      "fungus",
+      "nature",
+      "drugs"
     ],
     "moji": "🍄"
   },
@@ -18222,7 +20464,7 @@
     "unicode_alternates": [],
     "name": "musical keyboard",
     "shortname": ":musical_keyboard:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18230,10 +20472,9 @@
       "piano",
       "music",
       "keyboard",
-      "piano",
       "organ",
-      "instrument",
-      "electric"
+      "electric",
+      "instruments"
     ],
     "moji": "🎹"
   },
@@ -18242,7 +20483,7 @@
     "unicode_alternates": [],
     "name": "musical note",
     "shortname": ":musical_note:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18250,8 +20491,9 @@
       "musical",
       "music",
       "note",
-      "music",
-      "sound"
+      "sound",
+      "instruments",
+      "symbol"
     ],
     "moji": "🎵"
   },
@@ -18260,7 +20502,7 @@
     "unicode_alternates": [],
     "name": "musical score",
     "shortname": ":musical_score:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18269,10 +20511,10 @@
       "music",
       "musical",
       "score",
-      "clef",
       "g-clef",
       "stave",
-      "staff"
+      "staff",
+      "instruments"
     ],
     "moji": "🎼"
   },
@@ -18281,12 +20523,14 @@
     "unicode_alternates": [],
     "name": "speaker with cancellation stroke",
     "shortname": ":mute:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "sound",
-      "volume"
+      "volume",
+      "alarm",
+      "symbol"
     ],
     "moji": "🔇"
   },
@@ -18295,18 +20539,24 @@
     "unicode_alternates": [],
     "name": "nail polish",
     "shortname": ":nail_care:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "beauty",
-      "manicure"
+      "manicure",
+      "women",
+      "body",
+      "hands",
+      "nailpolish",
+      "diversity",
+      "girls night"
     ],
     "moji": "💅"
   },
   "nail_care_tone1": {
     "unicode": "1F485-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nail polish tone 1",
     "shortname": ":nail_care_tone1:",
     "category": "people",
@@ -18315,11 +20565,12 @@
     "keywords": [
       "beauty",
       "manicure"
-    ]
+    ],
+    "moji": "💅🏻"
   },
   "nail_care_tone2": {
     "unicode": "1F485-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nail polish tone 2",
     "shortname": ":nail_care_tone2:",
     "category": "people",
@@ -18328,11 +20579,12 @@
     "keywords": [
       "beauty",
       "manicure"
-    ]
+    ],
+    "moji": "💅🏼"
   },
   "nail_care_tone3": {
     "unicode": "1F485-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nail polish tone 3",
     "shortname": ":nail_care_tone3:",
     "category": "people",
@@ -18341,11 +20593,12 @@
     "keywords": [
       "beauty",
       "manicure"
-    ]
+    ],
+    "moji": "💅🏽"
   },
   "nail_care_tone4": {
     "unicode": "1F485-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nail polish tone 4",
     "shortname": ":nail_care_tone4:",
     "category": "people",
@@ -18354,11 +20607,12 @@
     "keywords": [
       "beauty",
       "manicure"
-    ]
+    ],
+    "moji": "💅🏾"
   },
   "nail_care_tone5": {
     "unicode": "1F485-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nail polish tone 5",
     "shortname": ":nail_care_tone5:",
     "category": "people",
@@ -18367,28 +20621,43 @@
     "keywords": [
       "beauty",
       "manicure"
-    ]
+    ],
+    "moji": "💅🏿"
   },
   "name_badge": {
     "unicode": "1F4DB",
     "unicode_alternates": [],
     "name": "name badge",
     "shortname": ":name_badge:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fire",
-      "forbid"
+      "forbid",
+      "work"
     ],
     "moji": "📛"
   },
+  "nauseated_face": {
+    "unicode": "1F922",
+    "unicode_alternates": [],
+    "name": "nauseated face",
+    "shortname": ":nauseated_face:",
+    "category": "people",
+    "aliases": [
+      ":sick:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤢"
+  },
   "necktie": {
     "unicode": "1F454",
     "unicode_alternates": [],
     "name": "necktie",
     "shortname": ":necktie:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18405,20 +20674,21 @@
     "unicode_alternates": [],
     "name": "negative squared cross mark",
     "shortname": ":negative_squared_cross_mark:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "deny",
       "green-square",
       "no",
-      "x"
+      "x",
+      "symbol"
     ],
     "moji": "❎"
   },
   "nerd": {
     "unicode": "1F913",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nerd face",
     "shortname": ":nerd:",
     "category": "people",
@@ -18426,31 +20696,18 @@
       ":nerd_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
-  },
-  "network": {
-    "unicode": "1F5A7",
-    "unicode_alternates": [],
-    "name": "three networked computers",
-    "shortname": ":network:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":three_networked_computers:"
-    ],
-    "aliases_ascii": [],
     "keywords": [
-      "lan",
-      "wan",
-      "network",
-      "technology"
-    ]
+      "smiley",
+      "glasses"
+    ],
+    "moji": "🤓"
   },
   "neutral_face": {
     "unicode": "1F610",
     "unicode_alternates": [],
     "name": "neutral face",
     "shortname": ":neutral_face:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18459,7 +20716,11 @@
       "neutral",
       "objective",
       "impartial",
-      "blank"
+      "blank",
+      "mad",
+      "smiley",
+      "shrug",
+      "emotion"
     ],
     "moji": "😐"
   },
@@ -18468,11 +20729,12 @@
     "unicode_alternates": [],
     "name": "squared new",
     "shortname": ":new:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "🆕"
   },
@@ -18491,7 +20753,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌑"
   },
@@ -18512,7 +20775,9 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space",
+      "goodnight"
     ],
     "moji": "🌚"
   },
@@ -18526,7 +20791,9 @@
     "aliases_ascii": [],
     "keywords": [
       "headline",
-      "press"
+      "press",
+      "office",
+      "write"
     ],
     "moji": "📰"
   },
@@ -18535,19 +20802,22 @@
     "unicode_alternates": [],
     "name": "rolled-up newspaper",
     "shortname": ":newspaper2:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":rolled_up_newspaper:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "headline",
-      "press"
-    ]
+      "press",
+      "office",
+      "write"
+    ],
+    "moji": "🗞"
   },
   "ng": {
     "unicode": "1F196",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "squared ng",
     "shortname": ":ng:",
     "category": "symbols",
@@ -18558,14 +20828,15 @@
       "no good",
       "symbol",
       "word"
-    ]
+    ],
+    "moji": "🆖"
   },
   "night_with_stars": {
     "unicode": "1F303",
     "unicode_alternates": [],
     "name": "night with stars",
     "shortname": ":night_with_stars:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18575,7 +20846,11 @@
       "evening",
       "planets",
       "space",
-      "sky"
+      "sky",
+      "places",
+      "building",
+      "vacation",
+      "goodnight"
     ],
     "moji": "🌃"
   },
@@ -18585,15 +20860,18 @@
     "unicode_alternates": [
       "0039-FE0F-20E3"
     ],
-    "name": "digit nine",
+    "name": "keycap digit nine",
     "shortname": ":nine:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "9",
       "blue-square",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "no_bell": {
@@ -18601,13 +20879,15 @@
     "unicode_alternates": [],
     "name": "bell with cancellation stroke",
     "shortname": ":no_bell:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "mute",
       "sound",
-      "volume"
+      "volume",
+      "alarm",
+      "symbol"
     ],
     "moji": "🔕"
   },
@@ -18616,7 +20896,7 @@
     "unicode_alternates": [],
     "name": "no bicycles",
     "shortname": ":no_bicycles:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18624,7 +20904,8 @@
       "prohibited",
       "bicycle",
       "bike pedal",
-      "no"
+      "no",
+      "symbol"
     ],
     "moji": "🚳"
   },
@@ -18635,7 +20916,7 @@
     ],
     "name": "no entry",
     "shortname": ":no_entry:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18644,7 +20925,9 @@
       "limit",
       "privacy",
       "security",
-      "stop"
+      "stop",
+      "symbol",
+      "circle"
     ],
     "moji": "⛔"
   },
@@ -18653,7 +20936,7 @@
     "unicode_alternates": [],
     "name": "no entry sign",
     "shortname": ":no_entry_sign:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18663,8 +20946,9 @@
       "limit",
       "stop",
       "no",
-      "stop",
-      "entry"
+      "entry",
+      "symbol",
+      "circle"
     ],
     "moji": "🚫"
   },
@@ -18673,7 +20957,7 @@
     "unicode_alternates": [],
     "name": "face with no good gesture",
     "shortname": ":no_good:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18684,13 +20968,17 @@
       "stop",
       "nope",
       "don&#039;t",
-      "not"
+      "not",
+      "people",
+      "women",
+      "diversity",
+      "girls night"
     ],
     "moji": "🙅"
   },
   "no_good_tone1": {
     "unicode": "1F645-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with no good gesture tone 1",
     "shortname": ":no_good_tone1:",
     "category": "people",
@@ -18708,11 +20996,12 @@
       "hand",
       "person",
       "prohibited"
-    ]
+    ],
+    "moji": "🙅🏻"
   },
   "no_good_tone2": {
     "unicode": "1F645-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with no good gesture tone 2",
     "shortname": ":no_good_tone2:",
     "category": "people",
@@ -18730,11 +21019,12 @@
       "hand",
       "person",
       "prohibited"
-    ]
+    ],
+    "moji": "🙅🏼"
   },
   "no_good_tone3": {
     "unicode": "1F645-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with no good gesture tone 3",
     "shortname": ":no_good_tone3:",
     "category": "people",
@@ -18752,11 +21042,12 @@
       "hand",
       "person",
       "prohibited"
-    ]
+    ],
+    "moji": "🙅🏽"
   },
   "no_good_tone4": {
     "unicode": "1F645-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with no good gesture tone 4",
     "shortname": ":no_good_tone4:",
     "category": "people",
@@ -18774,11 +21065,12 @@
       "hand",
       "person",
       "prohibited"
-    ]
+    ],
+    "moji": "🙅🏾"
   },
   "no_good_tone5": {
     "unicode": "1F645-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with no good gesture tone 5",
     "shortname": ":no_good_tone5:",
     "category": "people",
@@ -18796,19 +21088,22 @@
       "hand",
       "person",
       "prohibited"
-    ]
+    ],
+    "moji": "🙅🏿"
   },
   "no_mobile_phones": {
     "unicode": "1F4F5",
     "unicode_alternates": [],
     "name": "no mobile phones",
     "shortname": ":no_mobile_phones:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "iphone",
-      "mute"
+      "mute",
+      "symbol",
+      "phone"
     ],
     "moji": "📵"
   },
@@ -18817,7 +21112,7 @@
     "unicode_alternates": [],
     "name": "face without mouth",
     "shortname": ":no_mouth:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":-X",
@@ -18835,7 +21130,11 @@
       "hellokitty",
       "mouth",
       "silent",
-      "vapid"
+      "vapid",
+      "mad",
+      "smiley",
+      "neutral",
+      "emotion"
     ],
     "moji": "😶"
   },
@@ -18844,7 +21143,7 @@
     "unicode_alternates": [],
     "name": "no pedestrians",
     "shortname": ":no_pedestrians:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18857,7 +21156,8 @@
       "stroll",
       "stride",
       "foot",
-      "feet"
+      "feet",
+      "symbol"
     ],
     "moji": "🚷"
   },
@@ -18866,20 +21166,20 @@
     "unicode_alternates": [],
     "name": "no smoking symbol",
     "shortname": ":no_smoking:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cigarette",
       "no",
       "smoking",
-      "cigarette",
       "smoke",
       "cancer",
       "lungs",
       "inhale",
       "tar",
-      "nicotine"
+      "nicotine",
+      "symbol"
     ],
     "moji": "🚭"
   },
@@ -18888,7 +21188,7 @@
     "unicode_alternates": [],
     "name": "non-potable water symbol",
     "shortname": ":non-potable_water:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -18901,7 +21201,8 @@
       "dirty",
       "gross",
       "aqua",
-      "h20"
+      "h20",
+      "symbol"
     ],
     "moji": "🚱"
   },
@@ -18910,18 +21211,20 @@
     "unicode_alternates": [],
     "name": "nose",
     "shortname": ":nose:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "smell",
-      "sniff"
+      "sniff",
+      "body",
+      "diversity"
     ],
     "moji": "👃"
   },
   "nose_tone1": {
     "unicode": "1F443-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nose tone 1",
     "shortname": ":nose_tone1:",
     "category": "people",
@@ -18930,11 +21233,12 @@
     "keywords": [
       "smell",
       "sniff"
-    ]
+    ],
+    "moji": "👃🏻"
   },
   "nose_tone2": {
     "unicode": "1F443-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nose tone 2",
     "shortname": ":nose_tone2:",
     "category": "people",
@@ -18943,11 +21247,12 @@
     "keywords": [
       "smell",
       "sniff"
-    ]
+    ],
+    "moji": "👃🏼"
   },
   "nose_tone3": {
     "unicode": "1F443-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nose tone 3",
     "shortname": ":nose_tone3:",
     "category": "people",
@@ -18956,11 +21261,12 @@
     "keywords": [
       "smell",
       "sniff"
-    ]
+    ],
+    "moji": "👃🏽"
   },
   "nose_tone4": {
     "unicode": "1F443-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nose tone 4",
     "shortname": ":nose_tone4:",
     "category": "people",
@@ -18969,11 +21275,12 @@
     "keywords": [
       "smell",
       "sniff"
-    ]
+    ],
+    "moji": "👃🏾"
   },
   "nose_tone5": {
     "unicode": "1F443-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "nose tone 5",
     "shortname": ":nose_tone5:",
     "category": "people",
@@ -18982,37 +21289,8 @@
     "keywords": [
       "smell",
       "sniff"
-    ]
-  },
-  "note": {
-    "unicode": "1F5C9",
-    "unicode_alternates": [],
-    "name": "note page",
-    "shortname": ":note:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":note_page:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "stationery",
-      "post-it"
-    ]
-  },
-  "note_empty": {
-    "unicode": "1F5C6",
-    "unicode_alternates": [],
-    "name": "empty note page",
-    "shortname": ":note_empty:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":empty_note_page:"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "stationery",
-      "post-it"
-    ]
+    "moji": "👃🏿"
   },
   "notebook": {
     "unicode": "1F4D3",
@@ -19026,7 +21304,10 @@
       "notes",
       "paper",
       "record",
-      "stationery"
+      "stationery",
+      "object",
+      "office",
+      "write"
     ],
     "moji": "📓"
   },
@@ -19042,71 +21323,48 @@
       "classroom",
       "notes",
       "paper",
-      "record"
+      "record",
+      "object",
+      "office",
+      "write"
     ],
     "moji": "📔"
   },
-  "notepad": {
-    "unicode": "1F5CA",
-    "unicode_alternates": [],
-    "name": "note pad",
-    "shortname": ":notepad:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":note_pad:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "stationery",
-      "post-it"
-    ]
-  },
-  "notepad_empty": {
-    "unicode": "1F5C7",
-    "unicode_alternates": [],
-    "name": "empty note pad",
-    "shortname": ":notepad_empty:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":empty_note_pad:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "stationery",
-      "post-it"
-    ]
-  },
   "notepad_spiral": {
     "unicode": "1F5D2",
     "unicode_alternates": [],
     "name": "spiral note pad",
     "shortname": ":notepad_spiral:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":spiral_note_pad:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "stationery"
-    ]
+      "stationery",
+      "work",
+      "office",
+      "write"
+    ],
+    "moji": "🗒"
   },
   "notes": {
     "unicode": "1F3B6",
     "unicode_alternates": [],
     "name": "multiple musical notes",
     "shortname": ":notes:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "music",
       "score",
       "musical",
-      "music",
       "notes",
-      "music",
       "sound",
-      "melody"
+      "melody",
+      "instruments",
+      "symbol"
     ],
     "moji": "🎶"
   },
@@ -19120,7 +21378,10 @@
     "aliases_ascii": [],
     "keywords": [
       "handy",
-      "tools"
+      "tools",
+      "object",
+      "tool",
+      "nutcase"
     ],
     "moji": "🔩"
   },
@@ -19131,12 +21392,13 @@
     ],
     "name": "heavy large circle",
     "shortname": ":o:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "circle",
-      "round"
+      "round",
+      "symbol"
     ],
     "moji": "⭕"
   },
@@ -19145,13 +21407,14 @@
     "unicode_alternates": [],
     "name": "negative squared latin capital letter o",
     "shortname": ":o2:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "letter",
-      "red-square"
+      "red-square",
+      "symbol"
     ],
     "moji": "🅾"
   },
@@ -19168,13 +21431,29 @@
       "water",
       "wave",
       "ocean",
-      "wave",
       "surf",
       "beach",
-      "tide"
+      "tide",
+      "weather",
+      "boat",
+      "tropical",
+      "swim"
     ],
     "moji": "🌊"
   },
+  "octagonal_sign": {
+    "unicode": "1F6D1",
+    "unicode_alternates": [],
+    "name": "octagonal sign",
+    "shortname": ":octagonal_sign:",
+    "category": "symbols",
+    "aliases": [
+      ":stop_sign:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🛑"
+  },
   "octopus": {
     "unicode": "1F419",
     "unicode_alternates": [],
@@ -19187,7 +21466,8 @@
       "animal",
       "creature",
       "ocean",
-      "sea"
+      "sea",
+      "wildlife"
     ],
     "moji": "🐙"
   },
@@ -19196,7 +21476,7 @@
     "unicode_alternates": [],
     "name": "oden",
     "shortname": ":oden:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19214,13 +21494,14 @@
     "unicode_alternates": [],
     "name": "office building",
     "shortname": ":office:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "building",
       "bureau",
-      "work"
+      "work",
+      "places"
     ],
     "moji": "🏢"
   },
@@ -19229,28 +21510,31 @@
     "unicode_alternates": [],
     "name": "oil drum",
     "shortname": ":oil:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":oil_drum:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "petroleum"
-    ]
+      "petroleum",
+      "object"
+    ],
+    "moji": "🛢"
   },
   "ok": {
     "unicode": "1F197",
     "unicode_alternates": [],
     "name": "squared ok",
     "shortname": ":ok:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "agree",
       "blue-square",
       "good",
-      "yes"
+      "yes",
+      "symbol"
     ],
     "moji": "🆗"
   },
@@ -19259,7 +21543,7 @@
     "unicode_alternates": [],
     "name": "ok hand sign",
     "shortname": ":ok_hand:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19273,13 +21557,19 @@
       "marijuana",
       "joint",
       "pot",
-      "420"
+      "420",
+      "body",
+      "hands",
+      "hi",
+      "diversity",
+      "good",
+      "beautiful"
     ],
     "moji": "👌"
   },
   "ok_hand_tone1": {
     "unicode": "1F44C-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ok hand sign tone 1",
     "shortname": ":ok_hand_tone1:",
     "category": "people",
@@ -19296,11 +21586,12 @@
       "joint",
       "pot",
       "420"
-    ]
+    ],
+    "moji": "👌🏻"
   },
   "ok_hand_tone2": {
     "unicode": "1F44C-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ok hand sign tone 2",
     "shortname": ":ok_hand_tone2:",
     "category": "people",
@@ -19317,11 +21608,12 @@
       "joint",
       "pot",
       "420"
-    ]
+    ],
+    "moji": "👌🏼"
   },
   "ok_hand_tone3": {
     "unicode": "1F44C-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ok hand sign tone 3",
     "shortname": ":ok_hand_tone3:",
     "category": "people",
@@ -19338,11 +21630,12 @@
       "joint",
       "pot",
       "420"
-    ]
+    ],
+    "moji": "👌🏽"
   },
   "ok_hand_tone4": {
     "unicode": "1F44C-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ok hand sign tone 4",
     "shortname": ":ok_hand_tone4:",
     "category": "people",
@@ -19359,11 +21652,12 @@
       "joint",
       "pot",
       "420"
-    ]
+    ],
+    "moji": "👌🏾"
   },
   "ok_hand_tone5": {
     "unicode": "1F44C-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "ok hand sign tone 5",
     "shortname": ":ok_hand_tone5:",
     "category": "people",
@@ -19380,14 +21674,15 @@
       "joint",
       "pot",
       "420"
-    ]
+    ],
+    "moji": "👌🏿"
   },
   "ok_woman": {
     "unicode": "1F646",
     "unicode_alternates": [],
     "name": "face with ok gesture",
     "shortname": ":ok_woman:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "*\\0/*",
@@ -19404,13 +21699,15 @@
       "yes",
       "ok",
       "okay",
-      "accept"
+      "accept",
+      "people",
+      "diversity"
     ],
     "moji": "🙆"
   },
   "ok_woman_tone1": {
     "unicode": "1F646-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with ok gesture tone1",
     "shortname": ":ok_woman_tone1:",
     "category": "people",
@@ -19425,11 +21722,12 @@
       "yes",
       "okay",
       "accept"
-    ]
+    ],
+    "moji": "🙆🏻"
   },
   "ok_woman_tone2": {
     "unicode": "1F646-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with ok gesture tone2",
     "shortname": ":ok_woman_tone2:",
     "category": "people",
@@ -19444,11 +21742,12 @@
       "yes",
       "okay",
       "accept"
-    ]
+    ],
+    "moji": "🙆🏼"
   },
   "ok_woman_tone3": {
     "unicode": "1F646-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with ok gesture tone3",
     "shortname": ":ok_woman_tone3:",
     "category": "people",
@@ -19463,11 +21762,12 @@
       "yes",
       "okay",
       "accept"
-    ]
+    ],
+    "moji": "🙆🏽"
   },
   "ok_woman_tone4": {
     "unicode": "1F646-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with ok gesture tone4",
     "shortname": ":ok_woman_tone4:",
     "category": "people",
@@ -19482,11 +21782,12 @@
       "yes",
       "okay",
       "accept"
-    ]
+    ],
+    "moji": "🙆🏾"
   },
   "ok_woman_tone5": {
     "unicode": "1F646-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with ok gesture tone5",
     "shortname": ":ok_woman_tone5:",
     "category": "people",
@@ -19501,26 +21802,30 @@
       "yes",
       "okay",
       "accept"
-    ]
+    ],
+    "moji": "🙆🏿"
   },
   "older_man": {
     "unicode": "1F474",
     "unicode_alternates": [],
     "name": "older man",
     "shortname": ":older_man:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "human",
       "male",
-      "men"
+      "men",
+      "people",
+      "old people",
+      "diversity"
     ],
     "moji": "👴"
   },
   "older_man_tone1": {
     "unicode": "1F474-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older man tone 1",
     "shortname": ":older_man_tone1:",
     "category": "people",
@@ -19531,11 +21836,12 @@
       "men",
       "grandpa",
       "grandfather"
-    ]
+    ],
+    "moji": "👴🏻"
   },
   "older_man_tone2": {
     "unicode": "1F474-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older man tone 2",
     "shortname": ":older_man_tone2:",
     "category": "people",
@@ -19546,11 +21852,12 @@
       "men",
       "grandpa",
       "grandfather"
-    ]
+    ],
+    "moji": "👴🏼"
   },
   "older_man_tone3": {
     "unicode": "1F474-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older man tone 3",
     "shortname": ":older_man_tone3:",
     "category": "people",
@@ -19561,11 +21868,12 @@
       "men",
       "grandpa",
       "grandfather"
-    ]
+    ],
+    "moji": "👴🏽"
   },
   "older_man_tone4": {
     "unicode": "1F474-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older man tone 4",
     "shortname": ":older_man_tone4:",
     "category": "people",
@@ -19576,11 +21884,12 @@
       "men",
       "grandpa",
       "grandfather"
-    ]
+    ],
+    "moji": "👴🏾"
   },
   "older_man_tone5": {
     "unicode": "1F474-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older man tone 5",
     "shortname": ":older_man_tone5:",
     "category": "people",
@@ -19591,14 +21900,15 @@
       "men",
       "grandpa",
       "grandfather"
-    ]
+    ],
+    "moji": "👴🏿"
   },
   "older_woman": {
     "unicode": "1F475",
     "unicode_alternates": [],
     "name": "older woman",
     "shortname": ":older_woman:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":grandma:"
     ],
@@ -19608,13 +21918,16 @@
       "girl",
       "women",
       "grandma",
-      "grandmother"
+      "grandmother",
+      "people",
+      "old people",
+      "diversity"
     ],
     "moji": "👵"
   },
   "older_woman_tone1": {
     "unicode": "1F475-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older woman tone 1",
     "shortname": ":older_woman_tone1:",
     "category": "people",
@@ -19628,11 +21941,12 @@
       "lady",
       "grandma",
       "grandmother"
-    ]
+    ],
+    "moji": "👵🏻"
   },
   "older_woman_tone2": {
     "unicode": "1F475-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older woman tone 2",
     "shortname": ":older_woman_tone2:",
     "category": "people",
@@ -19646,11 +21960,12 @@
       "lady",
       "grandma",
       "grandmother"
-    ]
+    ],
+    "moji": "👵🏼"
   },
   "older_woman_tone3": {
     "unicode": "1F475-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older woman tone 3",
     "shortname": ":older_woman_tone3:",
     "category": "people",
@@ -19664,11 +21979,12 @@
       "lady",
       "grandma",
       "grandmother"
-    ]
+    ],
+    "moji": "👵🏽"
   },
   "older_woman_tone4": {
     "unicode": "1F475-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older woman tone 4",
     "shortname": ":older_woman_tone4:",
     "category": "people",
@@ -19682,11 +21998,12 @@
       "lady",
       "grandma",
       "grandmother"
-    ]
+    ],
+    "moji": "👵🏾"
   },
   "older_woman_tone5": {
     "unicode": "1F475-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "older woman tone 5",
     "shortname": ":older_woman_tone5:",
     "category": "people",
@@ -19700,14 +22017,15 @@
       "lady",
       "grandma",
       "grandmother"
-    ]
+    ],
+    "moji": "👵🏿"
   },
   "om_symbol": {
     "unicode": "1F549",
     "unicode_alternates": [],
     "name": "om symbol",
     "shortname": ":om_symbol:",
-    "category": "objects_symbols",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19718,20 +22036,24 @@
       "dharmic",
       "buddhism",
       "jainism",
-      "meditate"
-    ]
+      "meditate",
+      "religion",
+      "symbol"
+    ],
+    "moji": "🕉"
   },
   "on": {
     "unicode": "1F51B",
     "unicode_alternates": [],
     "name": "on with exclamation mark with left right arrow abo",
     "shortname": ":on:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🔛"
   },
@@ -19740,7 +22062,7 @@
     "unicode_alternates": [],
     "name": "oncoming automobile",
     "shortname": ":oncoming_automobile:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19748,8 +22070,8 @@
       "transportation",
       "vehicle",
       "sedan",
-      "car",
-      "automobile"
+      "automobile",
+      "travel"
     ],
     "moji": "🚘"
   },
@@ -19758,7 +22080,7 @@
     "unicode_alternates": [],
     "name": "oncoming bus",
     "shortname": ":oncoming_bus:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19767,8 +22089,8 @@
       "bus",
       "school",
       "city",
-      "transportation",
-      "public"
+      "public",
+      "travel"
     ],
     "moji": "🚍"
   },
@@ -19777,7 +22099,7 @@
     "unicode_alternates": [],
     "name": "oncoming police car",
     "shortname": ":oncoming_police_car:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19791,7 +22113,9 @@
       "citation",
       "crime",
       "help",
-      "officer"
+      "officer",
+      "transportation",
+      "911"
     ],
     "moji": "🚔"
   },
@@ -19800,7 +22124,7 @@
     "unicode_alternates": [],
     "name": "oncoming taxi",
     "shortname": ":oncoming_taxi:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19812,7 +22136,9 @@
       "automobile",
       "city",
       "transport",
-      "service"
+      "service",
+      "transportation",
+      "travel"
     ],
     "moji": "🚖"
   },
@@ -19822,15 +22148,18 @@
     "unicode_alternates": [
       "0031-FE0F-20E3"
     ],
-    "name": "digit one",
+    "name": "keycap digit one",
     "shortname": ":one:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "1",
       "blue-square",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "open_file_folder": {
@@ -19843,7 +22172,9 @@
     "aliases_ascii": [],
     "keywords": [
       "documents",
-      "load"
+      "load",
+      "work",
+      "office"
     ],
     "moji": "📂"
   },
@@ -19852,18 +22183,22 @@
     "unicode_alternates": [],
     "name": "open hands sign",
     "shortname": ":open_hands:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "butterfly",
-      "fingers"
+      "fingers",
+      "body",
+      "hands",
+      "diversity",
+      "condolence"
     ],
     "moji": "👐"
   },
   "open_hands_tone1": {
     "unicode": "1F450-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "open hands sign tone 1",
     "shortname": ":open_hands_tone1:",
     "category": "people",
@@ -19872,11 +22207,12 @@
     "keywords": [
       "butterfly",
       "fingers"
-    ]
+    ],
+    "moji": "👐🏻"
   },
   "open_hands_tone2": {
     "unicode": "1F450-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "open hands sign tone 2",
     "shortname": ":open_hands_tone2:",
     "category": "people",
@@ -19885,11 +22221,12 @@
     "keywords": [
       "butterfly",
       "fingers"
-    ]
+    ],
+    "moji": "👐🏼"
   },
   "open_hands_tone3": {
     "unicode": "1F450-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "open hands sign tone 3",
     "shortname": ":open_hands_tone3:",
     "category": "people",
@@ -19898,11 +22235,12 @@
     "keywords": [
       "butterfly",
       "fingers"
-    ]
+    ],
+    "moji": "👐🏽"
   },
   "open_hands_tone4": {
     "unicode": "1F450-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "open hands sign tone 4",
     "shortname": ":open_hands_tone4:",
     "category": "people",
@@ -19911,11 +22249,12 @@
     "keywords": [
       "butterfly",
       "fingers"
-    ]
+    ],
+    "moji": "👐🏾"
   },
   "open_hands_tone5": {
     "unicode": "1F450-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "open hands sign tone 5",
     "shortname": ":open_hands_tone5:",
     "category": "people",
@@ -19924,14 +22263,15 @@
     "keywords": [
       "butterfly",
       "fingers"
-    ]
+    ],
+    "moji": "👐🏿"
   },
   "open_mouth": {
     "unicode": "1F62E",
     "unicode_alternates": [],
     "name": "face with open mouth",
     "shortname": ":open_mouth:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":-O",
@@ -19949,7 +22289,10 @@
       "jaw",
       "gapping",
       "surprise",
-      "wow"
+      "wow",
+      "smiley",
+      "surprised",
+      "emotion"
     ],
     "moji": "😮"
   },
@@ -19958,7 +22301,7 @@
     "unicode_alternates": [],
     "name": "ophiuchus",
     "shortname": ":ophiuchus:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -19972,28 +22315,11 @@
       "zodiac",
       "purple-square",
       "sign",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "⛎"
   },
-  "optical_disk": {
-    "unicode": "1F5B8",
-    "unicode_alternates": [],
-    "name": "optical disc icon",
-    "shortname": ":optical_disk:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":optical_disc_icon:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "cd",
-      "dvd",
-      "disc",
-      "disk",
-      "technology"
-    ]
-  },
   "orange_book": {
     "unicode": "1F4D9",
     "unicode_alternates": [],
@@ -20005,13 +22331,17 @@
     "keywords": [
       "knowledge",
       "library",
-      "read"
+      "read",
+      "object",
+      "office",
+      "write",
+      "book"
     ],
     "moji": "📙"
   },
   "orthodox_cross": {
     "unicode": "2626",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "orthodox cross",
     "shortname": ":orthodox_cross:",
     "category": "symbols",
@@ -20021,7 +22351,8 @@
       "christian",
       "religion",
       "symbol"
-    ]
+    ],
+    "moji": "☦"
   },
   "outbox_tray": {
     "unicode": "1F4E4",
@@ -20033,10 +22364,23 @@
     "aliases_ascii": [],
     "keywords": [
       "email",
-      "inbox"
+      "inbox",
+      "work",
+      "office"
     ],
     "moji": "📤"
   },
+  "owl": {
+    "unicode": "1F989",
+    "unicode_alternates": [],
+    "name": "owl",
+    "shortname": ":owl:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦉"
+  },
   "ox": {
     "unicode": "1F402",
     "unicode_alternates": [],
@@ -20062,22 +22406,12 @@
     "aliases_ascii": [],
     "keywords": [
       "gift",
-      "mail"
+      "mail",
+      "object",
+      "office"
     ],
     "moji": "📦"
   },
-  "page": {
-    "unicode": "1F5CF",
-    "unicode_alternates": [],
-    "name": "page",
-    "shortname": ":page:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "document"
-    ]
-  },
   "page_facing_up": {
     "unicode": "1F4C4",
     "unicode_alternates": [],
@@ -20087,7 +22421,10 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "documents"
+      "documents",
+      "work",
+      "office",
+      "write"
     ],
     "moji": "📄"
   },
@@ -20100,7 +22437,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "documents"
+      "documents",
+      "office",
+      "write"
     ],
     "moji": "📃"
   },
@@ -20114,28 +22453,18 @@
     "aliases_ascii": [],
     "keywords": [
       "bbcall",
-      "oldschool"
+      "oldschool",
+      "electronics",
+      "work"
     ],
     "moji": "📟"
   },
-  "pages": {
-    "unicode": "1F5D0",
-    "unicode_alternates": [],
-    "name": "pages",
-    "shortname": ":pages:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "documents"
-    ]
-  },
   "paintbrush": {
     "unicode": "1F58C",
     "unicode_alternates": [],
     "name": "lower left paintbrush",
     "shortname": ":paintbrush:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":lower_left_paintbrush:"
     ],
@@ -20143,8 +22472,12 @@
     "keywords": [
       "brush",
       "art",
-      "painting"
-    ]
+      "painting",
+      "object",
+      "office",
+      "write"
+    ],
+    "moji": "🖌"
   },
   "palm_tree": {
     "unicode": "1F334",
@@ -20163,10 +22496,22 @@
       "coconuts",
       "fronds",
       "warm",
-      "tropical"
+      "tropical",
+      "trees"
     ],
     "moji": "🌴"
   },
+  "pancakes": {
+    "unicode": "1F95E",
+    "unicode_alternates": [],
+    "name": "pancakes",
+    "shortname": ":pancakes:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥞"
+  },
   "panda_face": {
     "unicode": "1F43C",
     "unicode_alternates": [],
@@ -20189,7 +22534,9 @@
       "bamboo",
       "china",
       "black",
-      "white"
+      "white",
+      "wildlife",
+      "roar"
     ],
     "moji": "🐼"
   },
@@ -20203,7 +22550,10 @@
     "aliases_ascii": [],
     "keywords": [
       "documents",
-      "stationery"
+      "stationery",
+      "object",
+      "work",
+      "office"
     ],
     "moji": "📎"
   },
@@ -20212,22 +22562,26 @@
     "unicode_alternates": [],
     "name": "linked paperclips",
     "shortname": ":paperclips:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":linked_paperclips:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "documents",
-      "stationery"
-    ]
+      "stationery",
+      "object",
+      "work",
+      "office"
+    ],
+    "moji": "🖇"
   },
   "park": {
     "unicode": "1F3DE",
     "unicode_alternates": [],
     "name": "national park",
     "shortname": ":park:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":national_park:"
     ],
@@ -20238,8 +22592,13 @@
       "wildlife",
       "forest",
       "wilderness",
-      "national"
-    ]
+      "national",
+      "travel",
+      "vacation",
+      "park",
+      "camp"
+    ],
+    "moji": "🏞"
   },
   "parking": {
     "unicode": "1F17F",
@@ -20248,14 +22607,15 @@
     ],
     "name": "negative squared latin capital letter p",
     "shortname": ":parking:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
       "blue-square",
       "cars",
-      "letter"
+      "letter",
+      "symbol"
     ],
     "moji": "🅿"
   },
@@ -20266,7 +22626,7 @@
     ],
     "name": "part alternation mark",
     "shortname": ":part_alternation_mark:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20279,7 +22639,8 @@
       "cue",
       "letter",
       "m",
-      "japanese"
+      "japanese",
+      "symbol"
     ],
     "moji": "〽"
   },
@@ -20297,7 +22658,9 @@
       "cloud",
       "morning",
       "nature",
-      "weather"
+      "weather",
+      "sky",
+      "sun"
     ],
     "moji": "⛅"
   },
@@ -20306,7 +22669,7 @@
     "unicode_alternates": [],
     "name": "passport control",
     "shortname": ":passport_control:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20317,13 +22680,14 @@
       "travel",
       "control",
       "foreign",
-      "identification"
+      "identification",
+      "symbol"
     ],
     "moji": "🛂"
   },
   "pause_button": {
     "unicode": "23F8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "double vertical bar",
     "shortname": ":pause_button:",
     "category": "symbols",
@@ -20335,11 +22699,12 @@
       "pause",
       "sound",
       "symbol"
-    ]
+    ],
+    "moji": "⏸"
   },
   "peace": {
     "unicode": "262E",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "peace symbol",
     "shortname": ":peace:",
     "category": "symbols",
@@ -20348,15 +22713,19 @@
     ],
     "aliases_ascii": [],
     "keywords": [
-      "sign"
-    ]
+      "sign",
+      "symbol",
+      "peace",
+      "drugs"
+    ],
+    "moji": "☮"
   },
   "peach": {
     "unicode": "1F351",
     "unicode_alternates": [],
     "name": "peach",
     "shortname": ":peach:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20364,26 +22733,39 @@
       "fruit",
       "nature",
       "peach",
-      "fruit",
       "juicy",
-      "pit"
+      "pit",
+      "butt"
     ],
     "moji": "🍑"
   },
+  "peanuts": {
+    "unicode": "1F95C",
+    "unicode_alternates": [],
+    "name": "peanuts",
+    "shortname": ":peanuts:",
+    "category": "food",
+    "aliases": [
+      ":shelled_peanut:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥜"
+  },
   "pear": {
     "unicode": "1F350",
     "unicode_alternates": [],
     "name": "pear",
     "shortname": ":pear:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fruit",
       "nature",
       "pear",
-      "fruit",
-      "shape"
+      "shape",
+      "food"
     ],
     "moji": "🍐"
   },
@@ -20392,7 +22774,7 @@
     "unicode_alternates": [],
     "name": "lower left ballpoint pen",
     "shortname": ":pen_ballpoint:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":lower_left_ballpoint_pen:"
     ],
@@ -20400,15 +22782,18 @@
     "keywords": [
       "write",
       "bic",
-      "ink"
-    ]
+      "ink",
+      "object",
+      "office"
+    ],
+    "moji": "🖊"
   },
   "pen_fountain": {
     "unicode": "1F58B",
     "unicode_alternates": [],
     "name": "lower left fountain pen",
     "shortname": ":pen_fountain:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":lower_left_fountain_pen:"
     ],
@@ -20416,8 +22801,11 @@
     "keywords": [
       "write",
       "calligraphy",
-      "ink"
-    ]
+      "ink",
+      "object",
+      "office"
+    ],
+    "moji": "🖋"
   },
   "pencil": {
     "unicode": "1F4DD",
@@ -20433,7 +22821,9 @@
       "documents",
       "paper",
       "station",
-      "write"
+      "write",
+      "work",
+      "office"
     ],
     "moji": "📝"
   },
@@ -20450,26 +22840,12 @@
     "keywords": [
       "paper",
       "stationery",
-      "write"
+      "write",
+      "object",
+      "office"
     ],
     "moji": "✏"
   },
-  "pencil3": {
-    "unicode": "1F589",
-    "unicode_alternates": [],
-    "name": "lower left pencil",
-    "shortname": ":pencil3:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":lower_left_pencil:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "paper",
-      "stationery",
-      "write"
-    ]
-  },
   "penguin": {
     "unicode": "1F427",
     "unicode_alternates": [],
@@ -20480,46 +22856,17 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐧"
   },
-  "pennant_black": {
-    "unicode": "1F3F2",
-    "unicode_alternates": [],
-    "name": "black pennant",
-    "shortname": ":pennant_black:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":black_pennant:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "flag",
-      "athletics"
-    ]
-  },
-  "pennant_white": {
-    "unicode": "1F3F1",
-    "unicode_alternates": [],
-    "name": "white pennant",
-    "shortname": ":pennant_white:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":white_pennant:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "flag",
-      "athletics"
-    ]
-  },
   "pensive": {
     "unicode": "1F614",
     "unicode_alternates": [],
     "name": "pensive face",
     "shortname": ":pensive:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20532,7 +22879,10 @@
       "reflective",
       "wistful",
       "meditate",
-      "serious"
+      "serious",
+      "smiley",
+      "emotion",
+      "rip"
     ],
     "moji": "😔"
   },
@@ -20541,7 +22891,7 @@
     "unicode_alternates": [],
     "name": "performing arts",
     "shortname": ":performing_arts:",
-    "category": "places",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20552,10 +22902,11 @@
       "arts",
       "performance",
       "entertainment",
-      "acting",
       "story",
       "mask",
-      "masks"
+      "masks",
+      "theatre",
+      "movie"
     ],
     "moji": "🎭"
   },
@@ -20564,7 +22915,7 @@
     "unicode_alternates": [],
     "name": "persevering face",
     "shortname": ":persevere:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ">.<"
@@ -20575,7 +22926,11 @@
       "face",
       "no",
       "sick",
-      "upset"
+      "upset",
+      "sad",
+      "smiley",
+      "angry",
+      "emotion"
     ],
     "moji": "😣"
   },
@@ -20584,7 +22939,7 @@
     "unicode_alternates": [],
     "name": "person frowning",
     "shortname": ":person_frowning:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20594,13 +22949,16 @@
       "dejected",
       "rejected",
       "sad",
-      "frown"
+      "frown",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "🙍"
   },
   "person_frowning_tone1": {
     "unicode": "1F64D-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person frowning tone 1",
     "shortname": ":person_frowning_tone1:",
     "category": "people",
@@ -20614,11 +22972,12 @@
       "rejected",
       "sad",
       "frown"
-    ]
+    ],
+    "moji": "🙍🏻"
   },
   "person_frowning_tone2": {
     "unicode": "1F64D-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person frowning tone 2",
     "shortname": ":person_frowning_tone2:",
     "category": "people",
@@ -20632,11 +22991,12 @@
       "rejected",
       "sad",
       "frown"
-    ]
+    ],
+    "moji": "🙍🏼"
   },
   "person_frowning_tone3": {
     "unicode": "1F64D-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person frowning tone 3",
     "shortname": ":person_frowning_tone3:",
     "category": "people",
@@ -20650,11 +23010,12 @@
       "rejected",
       "sad",
       "frown"
-    ]
+    ],
+    "moji": "🙍🏽"
   },
   "person_frowning_tone4": {
     "unicode": "1F64D-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person frowning tone 4",
     "shortname": ":person_frowning_tone4:",
     "category": "people",
@@ -20668,11 +23029,12 @@
       "rejected",
       "sad",
       "frown"
-    ]
+    ],
+    "moji": "🙍🏾"
   },
   "person_frowning_tone5": {
     "unicode": "1F64D-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person frowning tone 5",
     "shortname": ":person_frowning_tone5:",
     "category": "people",
@@ -20686,14 +23048,15 @@
       "rejected",
       "sad",
       "frown"
-    ]
+    ],
+    "moji": "🙍🏿"
   },
   "person_with_blond_hair": {
     "unicode": "1F471",
     "unicode_alternates": [],
     "name": "person with blond hair",
     "shortname": ":person_with_blond_hair:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20703,13 +23066,16 @@
       "young",
       "western",
       "westerner",
-      "occidental"
+      "occidental",
+      "people",
+      "men",
+      "diversity"
     ],
     "moji": "👱"
   },
   "person_with_blond_hair_tone1": {
     "unicode": "1F471-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with blond hair tone 1",
     "shortname": ":person_with_blond_hair_tone1:",
     "category": "people",
@@ -20723,11 +23089,12 @@
       "western",
       "westerner",
       "occidental"
-    ]
+    ],
+    "moji": "👱🏻"
   },
   "person_with_blond_hair_tone2": {
     "unicode": "1F471-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with blond hair tone 2",
     "shortname": ":person_with_blond_hair_tone2:",
     "category": "people",
@@ -20741,11 +23108,12 @@
       "western",
       "westerner",
       "occidental"
-    ]
+    ],
+    "moji": "👱🏼"
   },
   "person_with_blond_hair_tone3": {
     "unicode": "1F471-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with blond hair tone 3",
     "shortname": ":person_with_blond_hair_tone3:",
     "category": "people",
@@ -20759,11 +23127,12 @@
       "western",
       "westerner",
       "occidental"
-    ]
+    ],
+    "moji": "👱🏽"
   },
   "person_with_blond_hair_tone4": {
     "unicode": "1F471-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with blond hair tone 4",
     "shortname": ":person_with_blond_hair_tone4:",
     "category": "people",
@@ -20777,11 +23146,12 @@
       "western",
       "westerner",
       "occidental"
-    ]
+    ],
+    "moji": "👱🏾"
   },
   "person_with_blond_hair_tone5": {
     "unicode": "1F471-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with blond hair tone 5",
     "shortname": ":person_with_blond_hair_tone5:",
     "category": "people",
@@ -20795,14 +23165,15 @@
       "western",
       "westerner",
       "occidental"
-    ]
+    ],
+    "moji": "👱🏿"
   },
   "person_with_pouting_face": {
     "unicode": "1F64E",
     "unicode_alternates": [],
     "name": "person with pouting face",
     "shortname": ":person_with_pouting_face:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -20812,13 +23183,16 @@
       "pout",
       "sexy",
       "cute",
-      "annoyed"
+      "annoyed",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "🙎"
   },
   "person_with_pouting_face_tone1": {
     "unicode": "1F64E-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with pouting face tone1",
     "shortname": ":person_with_pouting_face_tone1:",
     "category": "people",
@@ -20832,11 +23206,12 @@
       "sexy",
       "cute",
       "annoyed"
-    ]
+    ],
+    "moji": "🙎🏻"
   },
   "person_with_pouting_face_tone2": {
     "unicode": "1F64E-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with pouting face tone2",
     "shortname": ":person_with_pouting_face_tone2:",
     "category": "people",
@@ -20850,11 +23225,12 @@
       "sexy",
       "cute",
       "annoyed"
-    ]
+    ],
+    "moji": "🙎🏼"
   },
   "person_with_pouting_face_tone3": {
     "unicode": "1F64E-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with pouting face tone3",
     "shortname": ":person_with_pouting_face_tone3:",
     "category": "people",
@@ -20868,11 +23244,12 @@
       "sexy",
       "cute",
       "annoyed"
-    ]
+    ],
+    "moji": "🙎🏽"
   },
   "person_with_pouting_face_tone4": {
     "unicode": "1F64E-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with pouting face tone4",
     "shortname": ":person_with_pouting_face_tone4:",
     "category": "people",
@@ -20886,11 +23263,12 @@
       "sexy",
       "cute",
       "annoyed"
-    ]
+    ],
+    "moji": "🙎🏾"
   },
   "person_with_pouting_face_tone5": {
     "unicode": "1F64E-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with pouting face tone5",
     "shortname": ":person_with_pouting_face_tone5:",
     "category": "people",
@@ -20904,11 +23282,12 @@
       "sexy",
       "cute",
       "annoyed"
-    ]
+    ],
+    "moji": "🙎🏿"
   },
   "pick": {
     "unicode": "26CF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pick",
     "shortname": ":pick:",
     "category": "objects",
@@ -20917,8 +23296,10 @@
     "keywords": [
       "mining",
       "object",
-      "tool"
-    ]
+      "tool",
+      "weapon"
+    ],
+    "moji": "⛏"
   },
   "pig": {
     "unicode": "1F437",
@@ -20976,7 +23357,6 @@
       "food",
       "eat",
       "cute",
-      "oink",
       "pink",
       "smell",
       "truffle"
@@ -20993,7 +23373,9 @@
     "aliases_ascii": [],
     "keywords": [
       "health",
-      "medicine"
+      "medicine",
+      "object",
+      "drugs"
     ],
     "moji": "💊"
   },
@@ -21002,7 +23384,7 @@
     "unicode_alternates": [],
     "name": "pineapple",
     "shortname": ":pineapple:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21018,7 +23400,7 @@
   },
   "ping_pong": {
     "unicode": "1F3D3",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "table tennis paddle and ball",
     "shortname": ":ping_pong:",
     "category": "activity",
@@ -21026,22 +23408,13 @@
       ":table_tennis:"
     ],
     "aliases_ascii": [],
-    "keywords": []
-  },
-  "piracy": {
-    "unicode": "1F572",
-    "unicode_alternates": [],
-    "name": "no piracy",
-    "shortname": ":piracy:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":no_piracy:"
-    ],
-    "aliases_ascii": [],
     "keywords": [
-      "theft",
-      "rule"
-    ]
+      "game",
+      "ball",
+      "sport",
+      "ping pong"
+    ],
+    "moji": "🏓"
   },
   "pisces": {
     "unicode": "2653",
@@ -21050,7 +23423,7 @@
     ],
     "name": "pisces",
     "shortname": ":pisces:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21063,9 +23436,8 @@
       "zodiac",
       "sign",
       "purple-square",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♓"
   },
@@ -21074,7 +23446,7 @@
     "unicode_alternates": [],
     "name": "slice of pizza",
     "shortname": ":pizza:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21086,13 +23458,14 @@
       "italian",
       "italy",
       "slice",
-      "peperoni"
+      "peperoni",
+      "boys night"
     ],
     "moji": "🍕"
   },
   "place_of_worship": {
     "unicode": "1F6D0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "place of worship",
     "shortname": ":place_of_worship:",
     "category": "symbols",
@@ -21100,11 +23473,16 @@
       ":worship_symbol:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "religion",
+      "symbol",
+      "pray"
+    ],
+    "moji": "🛐"
   },
   "play_pause": {
     "unicode": "23EF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "black right-pointing double triangle with double vertical bar",
     "shortname": ":play_pause:",
     "category": "symbols",
@@ -21117,26 +23495,30 @@
       "right",
       "sound",
       "symbol"
-    ]
+    ],
+    "moji": "⏯"
   },
   "point_down": {
     "unicode": "1F447",
     "unicode_alternates": [],
     "name": "white down pointing backhand index",
     "shortname": ":point_down:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "direction",
       "fingers",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "diversity"
     ],
     "moji": "👇"
   },
   "point_down_tone1": {
     "unicode": "1F447-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white down pointing backhand index tone 1",
     "shortname": ":point_down_tone1:",
     "category": "people",
@@ -21146,11 +23528,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👇🏻"
   },
   "point_down_tone2": {
     "unicode": "1F447-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white down pointing backhand index tone 2",
     "shortname": ":point_down_tone2:",
     "category": "people",
@@ -21160,11 +23543,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👇🏼"
   },
   "point_down_tone3": {
     "unicode": "1F447-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white down pointing backhand index tone 3",
     "shortname": ":point_down_tone3:",
     "category": "people",
@@ -21174,11 +23558,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👇🏽"
   },
   "point_down_tone4": {
     "unicode": "1F447-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white down pointing backhand index tone 4",
     "shortname": ":point_down_tone4:",
     "category": "people",
@@ -21188,11 +23573,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👇🏾"
   },
   "point_down_tone5": {
     "unicode": "1F447-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white down pointing backhand index tone 5",
     "shortname": ":point_down_tone5:",
     "category": "people",
@@ -21202,26 +23588,31 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👇🏿"
   },
   "point_left": {
     "unicode": "1F448",
     "unicode_alternates": [],
     "name": "white left pointing backhand index",
     "shortname": ":point_left:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "direction",
       "fingers",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "hi",
+      "diversity"
     ],
     "moji": "👈"
   },
   "point_left_tone1": {
     "unicode": "1F448-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white left pointing backhand index tone 1",
     "shortname": ":point_left_tone1:",
     "category": "people",
@@ -21231,11 +23622,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👈🏻"
   },
   "point_left_tone2": {
     "unicode": "1F448-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white left pointing backhand index tone 2",
     "shortname": ":point_left_tone2:",
     "category": "people",
@@ -21245,11 +23637,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👈🏼"
   },
   "point_left_tone3": {
     "unicode": "1F448-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white left pointing backhand index tone 3",
     "shortname": ":point_left_tone3:",
     "category": "people",
@@ -21259,11 +23652,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👈🏽"
   },
   "point_left_tone4": {
     "unicode": "1F448-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white left pointing backhand index tone 4",
     "shortname": ":point_left_tone4:",
     "category": "people",
@@ -21273,11 +23667,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👈🏾"
   },
   "point_left_tone5": {
     "unicode": "1F448-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white left pointing backhand index tone 5",
     "shortname": ":point_left_tone5:",
     "category": "people",
@@ -21287,26 +23682,31 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👈🏿"
   },
   "point_right": {
     "unicode": "1F449",
     "unicode_alternates": [],
     "name": "white right pointing backhand index",
     "shortname": ":point_right:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "direction",
       "fingers",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "hi",
+      "diversity"
     ],
     "moji": "👉"
   },
   "point_right_tone1": {
     "unicode": "1F449-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white right pointing backhand index tone 1",
     "shortname": ":point_right_tone1:",
     "category": "people",
@@ -21316,11 +23716,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👉🏻"
   },
   "point_right_tone2": {
     "unicode": "1F449-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white right pointing backhand index tone 2",
     "shortname": ":point_right_tone2:",
     "category": "people",
@@ -21330,11 +23731,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👉🏼"
   },
   "point_right_tone3": {
     "unicode": "1F449-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white right pointing backhand index tone 3",
     "shortname": ":point_right_tone3:",
     "category": "people",
@@ -21344,11 +23746,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👉🏽"
   },
   "point_right_tone4": {
     "unicode": "1F449-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white right pointing backhand index tone 4",
     "shortname": ":point_right_tone4:",
     "category": "people",
@@ -21358,11 +23761,12 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👉🏾"
   },
   "point_right_tone5": {
     "unicode": "1F449-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white right pointing backhand index tone 5",
     "shortname": ":point_right_tone5:",
     "category": "people",
@@ -21372,7 +23776,8 @@
       "direction",
       "finger",
       "hand"
-    ]
+    ],
+    "moji": "👉🏿"
   },
   "point_up": {
     "unicode": "261D",
@@ -21381,13 +23786,17 @@
     ],
     "name": "white up pointing index",
     "shortname": ":point_up:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "direction",
       "fingers",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "emojione",
+      "diversity"
     ],
     "moji": "☝"
   },
@@ -21396,19 +23805,22 @@
     "unicode_alternates": [],
     "name": "white up pointing backhand index",
     "shortname": ":point_up_2:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "direction",
       "fingers",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "diversity"
     ],
     "moji": "👆"
   },
   "point_up_2_tone1": {
     "unicode": "1F446-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing backhand index tone 1",
     "shortname": ":point_up_2_tone1:",
     "category": "people",
@@ -21419,11 +23831,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "👆🏻"
   },
   "point_up_2_tone2": {
     "unicode": "1F446-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing backhand index tone 2",
     "shortname": ":point_up_2_tone2:",
     "category": "people",
@@ -21434,11 +23847,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "👆🏼"
   },
   "point_up_2_tone3": {
     "unicode": "1F446-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing backhand index tone 3",
     "shortname": ":point_up_2_tone3:",
     "category": "people",
@@ -21449,11 +23863,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "👆🏽"
   },
   "point_up_2_tone4": {
     "unicode": "1F446-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing backhand index tone 4",
     "shortname": ":point_up_2_tone4:",
     "category": "people",
@@ -21464,11 +23879,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "👆🏾"
   },
   "point_up_2_tone5": {
     "unicode": "1F446-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing backhand index tone 5",
     "shortname": ":point_up_2_tone5:",
     "category": "people",
@@ -21479,11 +23895,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "👆🏿"
   },
   "point_up_tone1": {
     "unicode": "261D-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing index tone 1",
     "shortname": ":point_up_tone1:",
     "category": "people",
@@ -21494,11 +23911,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "☝🏻"
   },
   "point_up_tone2": {
     "unicode": "261D-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing index tone 2",
     "shortname": ":point_up_tone2:",
     "category": "people",
@@ -21509,11 +23927,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "☝🏼"
   },
   "point_up_tone3": {
     "unicode": "261D-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing index tone 3",
     "shortname": ":point_up_tone3:",
     "category": "people",
@@ -21524,11 +23943,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "☝🏽"
   },
   "point_up_tone4": {
     "unicode": "261D-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing index tone 4",
     "shortname": ":point_up_tone4:",
     "category": "people",
@@ -21539,11 +23959,12 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "☝🏾"
   },
   "point_up_tone5": {
     "unicode": "261D-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white up pointing index tone 5",
     "shortname": ":point_up_tone5:",
     "category": "people",
@@ -21554,14 +23975,15 @@
       "finger",
       "hand",
       "one"
-    ]
+    ],
+    "moji": "☝🏿"
   },
   "police_car": {
     "unicode": "1F693",
     "unicode_alternates": [],
     "name": "police car",
     "shortname": ":police_car:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21577,7 +23999,8 @@
       "citation",
       "crime",
       "help",
-      "officer"
+      "officer",
+      "911"
     ],
     "moji": "🚓"
   },
@@ -21595,7 +24018,6 @@
       "dog",
       "nature",
       "poodle",
-      "dog",
       "clip",
       "showy",
       "sophisticated",
@@ -21608,7 +24030,7 @@
     "unicode_alternates": [],
     "name": "pile of poo",
     "shortname": ":poop:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":shit:",
       ":hankey:",
@@ -21620,32 +24042,41 @@
       "shit",
       "shitface",
       "turd",
-      "poo"
+      "poo",
+      "bathroom",
+      "sol",
+      "diarrhea"
     ],
     "moji": "💩"
   },
   "popcorn": {
     "unicode": "1F37F",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "popcorn",
     "shortname": ":popcorn:",
-    "category": "foods",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "food",
+      "parties"
+    ],
+    "moji": "🍿"
   },
   "post_office": {
     "unicode": "1F3E3",
     "unicode_alternates": [],
     "name": "japanese post office",
     "shortname": ":post_office:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "building",
       "communication",
-      "email"
+      "email",
+      "places",
+      "post office"
     ],
     "moji": "🏣"
   },
@@ -21659,7 +24090,8 @@
     "aliases_ascii": [],
     "keywords": [
       "instrument",
-      "music"
+      "music",
+      "object"
     ],
     "moji": "📯"
   },
@@ -21674,7 +24106,8 @@
     "keywords": [
       "email",
       "envelope",
-      "letter"
+      "letter",
+      "object"
     ],
     "moji": "📮"
   },
@@ -21683,7 +24116,7 @@
     "unicode_alternates": [],
     "name": "potable water symbol",
     "shortname": ":potable_water:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21699,27 +24132,40 @@
       "clear",
       "clean",
       "aqua",
-      "h20"
+      "h20",
+      "symbol"
     ],
     "moji": "🚰"
   },
+  "potato": {
+    "unicode": "1F954",
+    "unicode_alternates": [],
+    "name": "potato",
+    "shortname": ":potato:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥔"
+  },
   "pouch": {
     "unicode": "1F45D",
     "unicode_alternates": [],
     "name": "pouch",
     "shortname": ":pouch:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "accessories",
       "bag",
       "pouch",
-      "bag",
       "cosmetic",
       "packing",
       "grandma",
-      "makeup"
+      "makeup",
+      "women",
+      "fashion"
     ],
     "moji": "👝"
   },
@@ -21728,7 +24174,7 @@
     "unicode_alternates": [],
     "name": "poultry leg",
     "shortname": ":poultry_leg:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21737,7 +24183,8 @@
       "poultry",
       "leg",
       "chicken",
-      "fried"
+      "fried",
+      "holidays"
     ],
     "moji": "🍗"
   },
@@ -21759,13 +24206,9 @@
       "uk",
       "pound",
       "britain",
-      "british",
       "banknote",
-      "money",
-      "currency",
       "paper",
-      "cash",
-      "bills"
+      "cash"
     ],
     "moji": "💷"
   },
@@ -21774,7 +24217,7 @@
     "unicode_alternates": [],
     "name": "pouting cat face",
     "shortname": ":pouting_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21784,7 +24227,8 @@
       "annoyed",
       "miffed",
       "glower",
-      "frown"
+      "frown",
+      "cat"
     ],
     "moji": "😾"
   },
@@ -21793,7 +24237,7 @@
     "unicode_alternates": [],
     "name": "person with folded hands",
     "shortname": ":pray:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21807,13 +24251,19 @@
       "hands",
       "sorrow",
       "regret",
-      "sorry"
+      "sorry",
+      "body",
+      "hi",
+      "luck",
+      "thank you",
+      "diversity",
+      "scientology"
     ],
     "moji": "🙏"
   },
   "pray_tone1": {
     "unicode": "1F64F-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with folded hands tone 1",
     "shortname": ":pray_tone1:",
     "category": "people",
@@ -21830,11 +24280,12 @@
       "sorrow",
       "regret",
       "sorry"
-    ]
+    ],
+    "moji": "🙏🏻"
   },
   "pray_tone2": {
     "unicode": "1F64F-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with folded hands tone 2",
     "shortname": ":pray_tone2:",
     "category": "people",
@@ -21851,11 +24302,12 @@
       "sorrow",
       "regret",
       "sorry"
-    ]
+    ],
+    "moji": "🙏🏼"
   },
   "pray_tone3": {
     "unicode": "1F64F-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with folded hands tone 3",
     "shortname": ":pray_tone3:",
     "category": "people",
@@ -21872,11 +24324,12 @@
       "sorrow",
       "regret",
       "sorry"
-    ]
+    ],
+    "moji": "🙏🏽"
   },
   "pray_tone4": {
     "unicode": "1F64F-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with folded hands tone 4",
     "shortname": ":pray_tone4:",
     "category": "people",
@@ -21893,11 +24346,12 @@
       "sorrow",
       "regret",
       "sorry"
-    ]
+    ],
+    "moji": "🙏🏾"
   },
   "pray_tone5": {
     "unicode": "1F64F-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person with folded hands tone 5",
     "shortname": ":pray_tone5:",
     "category": "people",
@@ -21914,24 +24368,173 @@
       "sorrow",
       "regret",
       "sorry"
-    ]
+    ],
+    "moji": "🙏🏿"
   },
   "prayer_beads": {
     "unicode": "1F4FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "prayer beads",
     "shortname": ":prayer_beads:",
     "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "object",
+      "rosary"
+    ],
+    "moji": "📿"
+  },
+  "pregnant_woman": {
+    "unicode": "1F930",
+    "unicode_alternates": [],
+    "name": "pregnant woman",
+    "shortname": ":pregnant_woman:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰"
+  },
+  "pregnant_woman_tone1": {
+    "unicode": "1F930-1F3FB",
+    "unicode_alternates": [],
+    "name": "pregnant woman tone 1",
+    "shortname": ":pregnant_woman_tone1:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰🏻"
+  },
+  "pregnant_woman_tone2": {
+    "unicode": "1F930-1F3FC",
+    "unicode_alternates": [],
+    "name": "pregnant woman tone 2",
+    "shortname": ":pregnant_woman_tone2:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰🏼"
+  },
+  "pregnant_woman_tone3": {
+    "unicode": "1F930-1F3FD",
+    "unicode_alternates": [],
+    "name": "pregnant woman tone 3",
+    "shortname": ":pregnant_woman_tone3:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰🏽"
+  },
+  "pregnant_woman_tone4": {
+    "unicode": "1F930-1F3FE",
+    "unicode_alternates": [],
+    "name": "pregnant woman tone 4",
+    "shortname": ":pregnant_woman_tone4:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰🏾"
+  },
+  "pregnant_woman_tone5": {
+    "unicode": "1F930-1F3FF",
+    "unicode_alternates": [],
+    "name": "pregnant woman tone 5",
+    "shortname": ":pregnant_woman_tone5:",
+    "category": "people",
+    "aliases": [
+      ":expecting_woman_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤰🏿"
+  },
+  "prince": {
+    "unicode": "1F934",
+    "unicode_alternates": [],
+    "name": "prince",
+    "shortname": ":prince:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴"
+  },
+  "prince_tone1": {
+    "unicode": "1F934-1F3FB",
+    "unicode_alternates": [],
+    "name": "prince tone 1",
+    "shortname": ":prince_tone1:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴🏻"
+  },
+  "prince_tone2": {
+    "unicode": "1F934-1F3FC",
+    "unicode_alternates": [],
+    "name": "prince tone 2",
+    "shortname": ":prince_tone2:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴🏼"
+  },
+  "prince_tone3": {
+    "unicode": "1F934-1F3FD",
+    "unicode_alternates": [],
+    "name": "prince tone 3",
+    "shortname": ":prince_tone3:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴🏽"
+  },
+  "prince_tone4": {
+    "unicode": "1F934-1F3FE",
+    "unicode_alternates": [],
+    "name": "prince tone 4",
+    "shortname": ":prince_tone4:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴🏾"
+  },
+  "prince_tone5": {
+    "unicode": "1F934-1F3FF",
+    "unicode_alternates": [],
+    "name": "prince tone 5",
+    "shortname": ":prince_tone5:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤴🏿"
   },
   "princess": {
     "unicode": "1F478",
     "unicode_alternates": [],
     "name": "princess",
     "shortname": ":princess:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -21947,13 +24550,18 @@
       "queen",
       "daughter",
       "disney",
-      "high-maintenance"
+      "high-maintenance",
+      "people",
+      "women",
+      "diversity",
+      "beautiful",
+      "girls night"
     ],
     "moji": "👸"
   },
   "princess_tone1": {
     "unicode": "1F478-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "princess tone 1",
     "shortname": ":princess_tone1:",
     "category": "people",
@@ -21972,11 +24580,12 @@
       "daughter",
       "disney",
       "high-maintenance"
-    ]
+    ],
+    "moji": "👸🏻"
   },
   "princess_tone2": {
     "unicode": "1F478-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "princess tone 2",
     "shortname": ":princess_tone2:",
     "category": "people",
@@ -21995,11 +24604,12 @@
       "daughter",
       "disney",
       "high-maintenance"
-    ]
+    ],
+    "moji": "👸🏼"
   },
   "princess_tone3": {
     "unicode": "1F478-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "princess tone 3",
     "shortname": ":princess_tone3:",
     "category": "people",
@@ -22018,11 +24628,12 @@
       "daughter",
       "disney",
       "high-maintenance"
-    ]
+    ],
+    "moji": "👸🏽"
   },
   "princess_tone4": {
     "unicode": "1F478-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "princess tone 4",
     "shortname": ":princess_tone4:",
     "category": "people",
@@ -22041,11 +24652,12 @@
       "daughter",
       "disney",
       "high-maintenance"
-    ]
+    ],
+    "moji": "👸🏾"
   },
   "princess_tone5": {
     "unicode": "1F478-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "princess tone 5",
     "shortname": ":princess_tone5:",
     "category": "people",
@@ -22064,49 +24676,34 @@
       "daughter",
       "disney",
       "high-maintenance"
-    ]
+    ],
+    "moji": "👸🏿"
   },
   "printer": {
     "unicode": "1F5A8",
     "unicode_alternates": [],
     "name": "printer",
     "shortname": ":printer:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "hardcopy",
       "paper",
       "inkjet",
-      "laser"
-    ]
-  },
-  "prohibited": {
-    "unicode": "1F6C7",
-    "unicode_alternates": [],
-    "name": "prohibited sign",
-    "shortname": ":prohibited:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":prohibited_sign:"
+      "laser",
+      "electronics",
+      "work",
+      "office"
     ],
-    "aliases_ascii": [],
-    "keywords": [
-      "no",
-      "not",
-      "denied",
-      "disallow",
-      "forbid",
-      "limit",
-      "stop"
-    ]
+    "moji": "🖨"
   },
   "projector": {
     "unicode": "1F4FD",
     "unicode_alternates": [],
     "name": "film projector",
     "shortname": ":projector:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":film_projector:"
     ],
@@ -22117,26 +24714,35 @@
       "motion",
       "picture",
       "8mm",
-      "16mm"
-    ]
+      "16mm",
+      "object",
+      "camera"
+    ],
+    "moji": "📽"
   },
   "punch": {
     "unicode": "1F44A",
     "unicode_alternates": [],
     "name": "fisted hand sign",
     "shortname": ":punch:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fist",
-      "hand"
+      "hand",
+      "body",
+      "hands",
+      "hi",
+      "fist bump",
+      "diversity",
+      "boys night"
     ],
     "moji": "👊"
   },
   "punch_tone1": {
     "unicode": "1F44A-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fisted hand sign tone 1",
     "shortname": ":punch_tone1:",
     "category": "people",
@@ -22145,11 +24751,12 @@
     "keywords": [
       "fist",
       "punch"
-    ]
+    ],
+    "moji": "👊🏻"
   },
   "punch_tone2": {
     "unicode": "1F44A-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fisted hand sign tone 2",
     "shortname": ":punch_tone2:",
     "category": "people",
@@ -22158,11 +24765,12 @@
     "keywords": [
       "fist",
       "punch"
-    ]
+    ],
+    "moji": "👊🏼"
   },
   "punch_tone3": {
     "unicode": "1F44A-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fisted hand sign tone 3",
     "shortname": ":punch_tone3:",
     "category": "people",
@@ -22171,11 +24779,12 @@
     "keywords": [
       "fist",
       "punch"
-    ]
+    ],
+    "moji": "👊🏽"
   },
   "punch_tone4": {
     "unicode": "1F44A-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fisted hand sign tone 4",
     "shortname": ":punch_tone4:",
     "category": "people",
@@ -22184,11 +24793,12 @@
     "keywords": [
       "fist",
       "punch"
-    ]
+    ],
+    "moji": "👊🏾"
   },
   "punch_tone5": {
     "unicode": "1F44A-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "fisted hand sign tone 5",
     "shortname": ":punch_tone5:",
     "category": "people",
@@ -22197,14 +24807,15 @@
     "keywords": [
       "fist",
       "punch"
-    ]
+    ],
+    "moji": "👊🏿"
   },
   "purple_heart": {
     "unicode": "1F49C",
     "unicode_alternates": [],
     "name": "purple heart",
     "shortname": ":purple_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22215,7 +24826,6 @@
       "purple",
       "violet",
       "heart",
-      "love",
       "sensitive",
       "understanding",
       "compassionate",
@@ -22224,7 +24834,8 @@
       "honor",
       "royalty",
       "veteran",
-      "sacrifice"
+      "sacrifice",
+      "symbol"
     ],
     "moji": "💜"
   },
@@ -22233,7 +24844,7 @@
     "unicode_alternates": [],
     "name": "purse",
     "shortname": ":purse:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22246,9 +24857,9 @@
       "handbag",
       "coin bag",
       "accessory",
-      "money",
       "ladies",
-      "shopping"
+      "shopping",
+      "women"
     ],
     "moji": "👛"
   },
@@ -22261,28 +24872,18 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "stationery"
+      "stationery",
+      "object",
+      "office"
     ],
     "moji": "📌"
   },
-  "pushpin_black": {
-    "unicode": "1F588",
-    "unicode_alternates": [],
-    "name": "black pushpin",
-    "shortname": ":pushpin_black:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "stationery"
-    ]
-  },
   "put_litter_in_its_place": {
     "unicode": "1F6AE",
     "unicode_alternates": [],
     "name": "put litter in its place symbol",
     "shortname": ":put_litter_in_its_place:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22292,7 +24893,8 @@
       "trash",
       "garbage",
       "receptacle",
-      "can"
+      "can",
+      "symbol"
     ],
     "moji": "🚮"
   },
@@ -22301,12 +24903,15 @@
     "unicode_alternates": [],
     "name": "black question mark ornament",
     "shortname": ":question:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "confused",
-      "doubt"
+      "doubt",
+      "symbol",
+      "punctuation",
+      "wth"
     ],
     "moji": "❓"
   },
@@ -22320,7 +24925,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife"
     ],
     "moji": "🐰"
   },
@@ -22339,7 +24945,8 @@
       "bunny",
       "easter",
       "reproduction",
-      "prolific"
+      "prolific",
+      "wildlife"
     ],
     "moji": "🐇"
   },
@@ -22348,7 +24955,7 @@
     "unicode_alternates": [],
     "name": "racing car",
     "shortname": ":race_car:",
-    "category": "activity",
+    "category": "travel",
     "aliases": [
       ":racing_car:"
     ],
@@ -22359,8 +24966,11 @@
       "stock",
       "nascar",
       "speed",
-      "drive"
-    ]
+      "drive",
+      "transportation",
+      "car"
+    ],
+    "moji": "🏎"
   },
   "racehorse": {
     "unicode": "1F40E",
@@ -22391,7 +25001,8 @@
       "gelding",
       "yearling",
       "thoroughbred",
-      "pony"
+      "pony",
+      "wildlife"
     ],
     "moji": "🐎"
   },
@@ -22407,7 +25018,8 @@
       "communication",
       "music",
       "podcast",
-      "program"
+      "program",
+      "electronics"
     ],
     "moji": "📻"
   },
@@ -22416,17 +25028,19 @@
     "unicode_alternates": [],
     "name": "radio button",
     "shortname": ":radio_button:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "input"
+      "input",
+      "symbol",
+      "circle"
     ],
     "moji": "🔘"
   },
   "radioactive": {
     "unicode": "2622",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "radioactive sign",
     "shortname": ":radioactive:",
     "category": "symbols",
@@ -22435,15 +25049,17 @@
     ],
     "aliases_ascii": [],
     "keywords": [
-      "symbol"
-    ]
+      "symbol",
+      "science"
+    ],
+    "moji": "☢"
   },
   "rage": {
     "unicode": "1F621",
     "unicode_alternates": [],
     "name": "pouting face",
     "shortname": ":rage:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22454,7 +25070,9 @@
       "pout",
       "anger",
       "rage",
-      "irate"
+      "irate",
+      "smiley",
+      "emotion"
     ],
     "moji": "😡"
   },
@@ -22463,7 +25081,7 @@
     "unicode_alternates": [],
     "name": "railway car",
     "shortname": ":railway_car:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22473,7 +25091,8 @@
       "rail",
       "car",
       "coach",
-      "train"
+      "train",
+      "travel"
     ],
     "moji": "🚃"
   },
@@ -22482,7 +25101,7 @@
     "unicode_alternates": [],
     "name": "railway track",
     "shortname": ":railway_track:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [
       ":railroad_track:"
     ],
@@ -22492,15 +25111,18 @@
       "trolley",
       "subway",
       "locomotive",
-      "transit"
-    ]
+      "transit",
+      "travel",
+      "vacation"
+    ],
+    "moji": "🛤"
   },
   "rainbow": {
     "unicode": "1F308",
     "unicode_alternates": [],
     "name": "rainbow",
     "shortname": ":rainbow:",
-    "category": "nature",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22516,28 +25138,114 @@
       "spectrum",
       "refract",
       "leprechaun",
-      "gold"
+      "gold",
+      "weather",
+      "gay",
+      "rain"
     ],
     "moji": "🌈"
   },
+  "raised_back_of_hand": {
+    "unicode": "1F91A",
+    "unicode_alternates": [],
+    "name": "raised back of hand",
+    "shortname": ":raised_back_of_hand:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚"
+  },
+  "raised_back_of_hand_tone1": {
+    "unicode": "1F91A-1F3FB",
+    "unicode_alternates": [],
+    "name": "raised back of hand tone 1",
+    "shortname": ":raised_back_of_hand_tone1:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚🏻"
+  },
+  "raised_back_of_hand_tone2": {
+    "unicode": "1F91A-1F3FC",
+    "unicode_alternates": [],
+    "name": "raised back of hand tone 2",
+    "shortname": ":raised_back_of_hand_tone2:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚🏼"
+  },
+  "raised_back_of_hand_tone3": {
+    "unicode": "1F91A-1F3FD",
+    "unicode_alternates": [],
+    "name": "raised back of hand tone 3",
+    "shortname": ":raised_back_of_hand_tone3:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚🏽"
+  },
+  "raised_back_of_hand_tone4": {
+    "unicode": "1F91A-1F3FE",
+    "unicode_alternates": [],
+    "name": "raised back of hand tone 4",
+    "shortname": ":raised_back_of_hand_tone4:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚🏾"
+  },
+  "raised_back_of_hand_tone5": {
+    "unicode": "1F91A-1F3FF",
+    "unicode_alternates": [],
+    "name": "raised back of hand tone 5",
+    "shortname": ":raised_back_of_hand_tone5:",
+    "category": "people",
+    "aliases": [
+      ":back_of_hand_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤚🏿"
+  },
   "raised_hand": {
     "unicode": "270B",
     "unicode_alternates": [],
     "name": "raised hand",
     "shortname": ":raised_hand:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "female",
       "girl",
-      "woman"
+      "woman",
+      "body",
+      "hands",
+      "hi",
+      "diversity",
+      "girls night"
     ],
     "moji": "✋"
   },
   "raised_hand_tone1": {
     "unicode": "270B-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand tone 1",
     "shortname": ":raised_hand_tone1:",
     "category": "people",
@@ -22547,11 +25255,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "✋🏻"
   },
   "raised_hand_tone2": {
     "unicode": "270B-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand tone 2",
     "shortname": ":raised_hand_tone2:",
     "category": "people",
@@ -22561,11 +25270,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "✋🏼"
   },
   "raised_hand_tone3": {
     "unicode": "270B-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand tone 3",
     "shortname": ":raised_hand_tone3:",
     "category": "people",
@@ -22575,11 +25285,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "✋🏽"
   },
   "raised_hand_tone4": {
     "unicode": "270B-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand tone 4",
     "shortname": ":raised_hand_tone4:",
     "category": "people",
@@ -22589,11 +25300,12 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "✋🏾"
   },
   "raised_hand_tone5": {
     "unicode": "270B-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand tone 5",
     "shortname": ":raised_hand_tone5:",
     "category": "people",
@@ -22603,14 +25315,15 @@
       "female",
       "girl",
       "woman"
-    ]
+    ],
+    "moji": "✋🏿"
   },
   "raised_hands": {
     "unicode": "1F64C",
     "unicode_alternates": [],
     "name": "person raising both hands in celebration",
     "shortname": ":raised_hands:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22619,13 +25332,19 @@
       "winning",
       "woot",
       "yay",
-      "banzai"
+      "banzai",
+      "body",
+      "hands",
+      "diversity",
+      "perfect",
+      "good",
+      "parties"
     ],
     "moji": "🙌"
   },
   "raised_hands_tone1": {
     "unicode": "1F64C-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person raising both hands in celebration tone 1",
     "shortname": ":raised_hands_tone1:",
     "category": "people",
@@ -22639,11 +25358,12 @@
       "yay",
       "banzai",
       "raised"
-    ]
+    ],
+    "moji": "🙌🏻"
   },
   "raised_hands_tone2": {
     "unicode": "1F64C-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person raising both hands in celebration tone 2",
     "shortname": ":raised_hands_tone2:",
     "category": "people",
@@ -22657,11 +25377,12 @@
       "yay",
       "banzai",
       "raised"
-    ]
+    ],
+    "moji": "🙌🏼"
   },
   "raised_hands_tone3": {
     "unicode": "1F64C-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person raising both hands in celebration tone 3",
     "shortname": ":raised_hands_tone3:",
     "category": "people",
@@ -22675,11 +25396,12 @@
       "yay",
       "banzai",
       "raised"
-    ]
+    ],
+    "moji": "🙌🏽"
   },
   "raised_hands_tone4": {
     "unicode": "1F64C-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person raising both hands in celebration tone 4",
     "shortname": ":raised_hands_tone4:",
     "category": "people",
@@ -22693,11 +25415,12 @@
       "yay",
       "banzai",
       "raised"
-    ]
+    ],
+    "moji": "🙌🏾"
   },
   "raised_hands_tone5": {
     "unicode": "1F64C-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "person raising both hands in celebration tone 5",
     "shortname": ":raised_hands_tone5:",
     "category": "people",
@@ -22711,14 +25434,15 @@
       "yay",
       "banzai",
       "raised"
-    ]
+    ],
+    "moji": "🙌🏿"
   },
   "raising_hand": {
     "unicode": "1F64B",
     "unicode_alternates": [],
     "name": "happy person raising one hand",
     "shortname": ":raising_hand:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22729,13 +25453,16 @@
       "raise",
       "notice",
       "attention",
-      "answer"
+      "answer",
+      "people",
+      "women",
+      "diversity"
     ],
     "moji": "🙋"
   },
   "raising_hand_tone1": {
     "unicode": "1F64B-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "happy person raising one hand tone1",
     "shortname": ":raising_hand_tone1:",
     "category": "people",
@@ -22749,11 +25476,12 @@
       "notice",
       "attention",
       "answer"
-    ]
+    ],
+    "moji": "🙋🏻"
   },
   "raising_hand_tone2": {
     "unicode": "1F64B-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "happy person raising one hand tone2",
     "shortname": ":raising_hand_tone2:",
     "category": "people",
@@ -22767,11 +25495,12 @@
       "notice",
       "attention",
       "answer"
-    ]
+    ],
+    "moji": "🙋🏼"
   },
   "raising_hand_tone3": {
     "unicode": "1F64B-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "happy person raising one hand tone3",
     "shortname": ":raising_hand_tone3:",
     "category": "people",
@@ -22785,11 +25514,12 @@
       "notice",
       "attention",
       "answer"
-    ]
+    ],
+    "moji": "🙋🏽"
   },
   "raising_hand_tone4": {
     "unicode": "1F64B-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "happy person raising one hand tone4",
     "shortname": ":raising_hand_tone4:",
     "category": "people",
@@ -22803,11 +25533,12 @@
       "notice",
       "attention",
       "answer"
-    ]
+    ],
+    "moji": "🙋🏾"
   },
   "raising_hand_tone5": {
     "unicode": "1F64B-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "happy person raising one hand tone5",
     "shortname": ":raising_hand_tone5:",
     "category": "people",
@@ -22821,7 +25552,8 @@
       "notice",
       "attention",
       "answer"
-    ]
+    ],
+    "moji": "🙋🏿"
   },
   "ram": {
     "unicode": "1F40F",
@@ -22836,10 +25568,10 @@
       "nature",
       "sheep",
       "ram",
-      "sheep",
       "male",
       "horn",
-      "horns"
+      "horns",
+      "wildlife"
     ],
     "moji": "🐏"
   },
@@ -22848,7 +25580,7 @@
     "unicode_alternates": [],
     "name": "steaming bowl",
     "shortname": ":ramen:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22860,7 +25592,8 @@
       "noodles",
       "bowl",
       "steaming",
-      "soup"
+      "soup",
+      "japan"
     ],
     "moji": "🍜"
   },
@@ -22884,7 +25617,7 @@
   },
   "record_button": {
     "unicode": "23FA",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "black circle for record",
     "shortname": ":record_button:",
     "category": "symbols",
@@ -22892,8 +25625,10 @@
     "aliases_ascii": [],
     "keywords": [
       "sound",
-      "symbol"
-    ]
+      "symbol",
+      "circle"
+    ],
+    "moji": "⏺"
   },
   "recycle": {
     "unicode": "267B",
@@ -22902,14 +25637,15 @@
     ],
     "name": "black universal recycling symbol",
     "shortname": ":recycle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
       "environment",
       "garbage",
-      "trash"
+      "trash",
+      "symbol"
     ],
     "moji": "♻"
   },
@@ -22918,12 +25654,14 @@
     "unicode_alternates": [],
     "name": "automobile",
     "shortname": ":red_car:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "transportation",
-      "vehicle"
+      "vehicle",
+      "car",
+      "travel"
     ],
     "moji": "🚗"
   },
@@ -22932,11 +25670,14 @@
     "unicode_alternates": [],
     "name": "large red circle",
     "shortname": ":red_circle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "circle"
     ],
     "moji": "🔴"
   },
@@ -22946,12 +25687,13 @@
     "unicode_alternates": [],
     "name": "registered sign",
     "shortname": ":registered:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "alphabet",
-      "circle"
+      "circle",
+      "symbol"
     ]
   },
   "relaxed": {
@@ -22961,7 +25703,7 @@
     ],
     "name": "white smiling face",
     "shortname": ":relaxed:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22969,7 +25711,9 @@
       "face",
       "happiness",
       "massage",
-      "smile"
+      "smile",
+      "happy",
+      "smiley"
     ],
     "moji": "☺"
   },
@@ -22978,7 +25722,7 @@
     "unicode_alternates": [],
     "name": "relieved face",
     "shortname": ":relieved:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -22989,8 +25733,9 @@
       "relaxed",
       "relieved",
       "satisfied",
-      "phew",
-      "relief"
+      "relief",
+      "smiley",
+      "emotion"
     ],
     "moji": "😌"
   },
@@ -22999,24 +25744,28 @@
     "unicode_alternates": [],
     "name": "reminder ribbon",
     "shortname": ":reminder_ribbon:",
-    "category": "celebration",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "awareness"
-    ]
+      "awareness",
+      "award"
+    ],
+    "moji": "🎗"
   },
   "repeat": {
     "unicode": "1F501",
     "unicode_alternates": [],
     "name": "clockwise rightwards and leftwards open circle arr",
     "shortname": ":repeat:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "loop",
-      "record"
+      "record",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔁"
   },
@@ -23025,12 +25774,14 @@
     "unicode_alternates": [],
     "name": "clockwise rightwards and leftwards open circle arr",
     "shortname": ":repeat_one:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "loop"
+      "loop",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔂"
   },
@@ -23039,7 +25790,7 @@
     "unicode_alternates": [],
     "name": "restroom",
     "shortname": ":restroom:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23051,7 +25802,8 @@
       "restroom",
       "sign",
       "shared",
-      "toilet"
+      "toilet",
+      "symbol"
     ],
     "moji": "🚻"
   },
@@ -23060,7 +25812,7 @@
     "unicode_alternates": [],
     "name": "revolving hearts",
     "shortname": ":revolving_hearts:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23074,7 +25826,8 @@
       "moving",
       "circle",
       "multiple",
-      "lovers"
+      "lovers",
+      "symbol"
     ],
     "moji": "💞"
   },
@@ -23083,21 +25836,36 @@
     "unicode_alternates": [],
     "name": "black left-pointing double triangle",
     "shortname": ":rewind:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "play"
+      "play",
+      "arrow",
+      "symbol"
     ],
     "moji": "⏪"
   },
+  "rhino": {
+    "unicode": "1F98F",
+    "unicode_alternates": [],
+    "name": "rhinoceros",
+    "shortname": ":rhino:",
+    "category": "nature",
+    "aliases": [
+      ":rhinoceros:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦏"
+  },
   "ribbon": {
     "unicode": "1F380",
     "unicode_alternates": [],
     "name": "ribbon",
     "shortname": ":ribbon:",
-    "category": "emoticons",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23108,7 +25876,10 @@
       "ribbon",
       "lace",
       "wrap",
-      "decorate"
+      "decorate",
+      "object",
+      "gift",
+      "birthday"
     ],
     "moji": "🎀"
   },
@@ -23117,7 +25888,7 @@
     "unicode_alternates": [],
     "name": "cooked rice",
     "shortname": ":rice:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23125,8 +25896,9 @@
       "rice",
       "white",
       "grain",
-      "food",
-      "bowl"
+      "bowl",
+      "sushi",
+      "japan"
     ],
     "moji": "🍚"
   },
@@ -23135,7 +25907,7 @@
     "unicode_alternates": [],
     "name": "rice ball",
     "shortname": ":rice_ball:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23146,7 +25918,8 @@
       "white",
       "nori",
       "seaweed",
-      "japanese"
+      "sushi",
+      "japan"
     ],
     "moji": "🍙"
   },
@@ -23155,7 +25928,7 @@
     "unicode_alternates": [],
     "name": "rice cracker",
     "shortname": ":rice_cracker:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23164,8 +25937,7 @@
       "rice",
       "cracker",
       "seaweed",
-      "food",
-      "japanese"
+      "sushi"
     ],
     "moji": "🍘"
   },
@@ -23174,7 +25946,7 @@
     "unicode_alternates": [],
     "name": "moon viewing ceremony",
     "shortname": ":rice_scene:",
-    "category": "objects",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23187,92 +25959,115 @@
       "rice",
       "scene",
       "festival",
-      "autumn"
+      "autumn",
+      "places",
+      "space",
+      "sky",
+      "travel"
     ],
     "moji": "🎑"
   },
-  "right_speaker": {
-    "unicode": "1F568",
+  "right_facing_fist": {
+    "unicode": "1F91C",
     "unicode_alternates": [],
-    "name": "right speaker",
-    "shortname": ":right_speaker:",
-    "category": "objects_symbols",
-    "aliases": [],
+    "name": "right-facing fist",
+    "shortname": ":right_facing_fist:",
+    "category": "people",
+    "aliases": [
+      ":right_fist:"
+    ],
     "aliases_ascii": [],
-    "keywords": [
-      "sound",
-      "listen",
-      "hear",
-      "noise",
-      "volume"
-    ]
+    "keywords": [],
+    "moji": "🤜"
   },
-  "right_speaker_one": {
-    "unicode": "1F569",
+  "right_facing_fist_tone1": {
+    "unicode": "1F91C-1F3FB",
     "unicode_alternates": [],
-    "name": "right speaker with one sound wave",
-    "shortname": ":right_speaker_one:",
-    "category": "objects_symbols",
+    "name": "right facing fist tone 1",
+    "shortname": ":right_facing_fist_tone1:",
+    "category": "people",
     "aliases": [
-      ":right_speaker_with_one_sound_wave:"
+      ":right_fist_tone1:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "low",
-      "volume"
-    ]
+    "keywords": [],
+    "moji": "🤜🏻"
   },
-  "right_speaker_three": {
-    "unicode": "1F56A",
+  "right_facing_fist_tone2": {
+    "unicode": "1F91C-1F3FC",
     "unicode_alternates": [],
-    "name": "right speaker with three sound waves",
-    "shortname": ":right_speaker_three:",
-    "category": "objects_symbols",
+    "name": "right facing fist tone 2",
+    "shortname": ":right_facing_fist_tone2:",
+    "category": "people",
     "aliases": [
-      ":right_speaker_with_three_sound_waves:"
+      ":right_fist_tone2:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "loud",
-      "high",
-      "volume"
-    ]
+    "keywords": [],
+    "moji": "🤜🏼"
+  },
+  "right_facing_fist_tone3": {
+    "unicode": "1F91C-1F3FD",
+    "unicode_alternates": [],
+    "name": "right facing fist tone 3",
+    "shortname": ":right_facing_fist_tone3:",
+    "category": "people",
+    "aliases": [
+      ":right_fist_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤜🏽"
+  },
+  "right_facing_fist_tone4": {
+    "unicode": "1F91C-1F3FE",
+    "unicode_alternates": [],
+    "name": "right facing fist tone 4",
+    "shortname": ":right_facing_fist_tone4:",
+    "category": "people",
+    "aliases": [
+      ":right_fist_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤜🏾"
+  },
+  "right_facing_fist_tone5": {
+    "unicode": "1F91C-1F3FF",
+    "unicode_alternates": [],
+    "name": "right facing fist tone 5",
+    "shortname": ":right_facing_fist_tone5:",
+    "category": "people",
+    "aliases": [
+      ":right_fist_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤜🏿"
   },
   "ring": {
     "unicode": "1F48D",
     "unicode_alternates": [],
     "name": "ring",
     "shortname": ":ring:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "marriage",
       "propose",
       "valentines",
-      "wedding"
+      "wedding",
+      "object",
+      "fashion",
+      "gem",
+      "accessories"
     ],
     "moji": "💍"
   },
-  "ringing_bell": {
-    "unicode": "1F56D",
-    "unicode_alternates": [],
-    "name": "ringing bell",
-    "shortname": ":ringing_bell:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "alert",
-      "ding",
-      "volume",
-      "sound",
-      "chime"
-    ]
-  },
   "robot": {
     "unicode": "1F916",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "robot face",
     "shortname": ":robot:",
     "category": "people",
@@ -23280,14 +26075,18 @@
       ":robot_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "monster",
+      "robot"
+    ],
+    "moji": "🤖"
   },
   "rocket": {
     "unicode": "1F680",
     "unicode_alternates": [],
     "name": "rocket",
     "shortname": ":rocket:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23298,16 +26097,33 @@
       "space",
       "spacecraft",
       "astronaut",
-      "cosmonaut"
+      "cosmonaut",
+      "transportation",
+      "object",
+      "fly",
+      "blast"
     ],
     "moji": "🚀"
   },
+  "rofl": {
+    "unicode": "1F923",
+    "unicode_alternates": [],
+    "name": "rolling on the floor laughing",
+    "shortname": ":rofl:",
+    "category": "people",
+    "aliases": [
+      ":rolling_on_the_floor_laughing:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤣"
+  },
   "roller_coaster": {
     "unicode": "1F3A2",
     "unicode_alternates": [],
     "name": "roller coaster",
     "shortname": ":roller_coaster:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23322,13 +26138,16 @@
       "park",
       "fair",
       "ride",
-      "entertainment"
+      "entertainment",
+      "places",
+      "vacation",
+      "roller coaster"
     ],
     "moji": "🎢"
   },
   "rolling_eyes": {
     "unicode": "1F644",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with rolling eyes",
     "shortname": ":rolling_eyes:",
     "category": "people",
@@ -23336,7 +26155,14 @@
       ":face_with_rolling_eyes:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "mad",
+      "smiley",
+      "rolling eyes",
+      "emotion",
+      "sarcastic"
+    ],
+    "moji": "🙄"
   },
   "rooster": {
     "unicode": "1F413",
@@ -23375,9 +26201,13 @@
       "fragrant",
       "flower",
       "thorns",
-      "love",
       "petals",
-      "romance"
+      "romance",
+      "nature",
+      "plant",
+      "rip",
+      "condolence",
+      "beautiful"
     ],
     "moji": "🌹"
   },
@@ -23386,31 +26216,21 @@
     "unicode_alternates": [],
     "name": "rosette",
     "shortname": ":rosette:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "flower"
-    ]
-  },
-  "rosette_black": {
-    "unicode": "1F3F6",
-    "unicode_alternates": [],
-    "name": "black rosette",
-    "shortname": ":rosette_black:",
-    "category": "objects_symbols",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "flower"
-    ]
+      "flower",
+      "tropical"
+    ],
+    "moji": "🏵"
   },
   "rotating_light": {
     "unicode": "1F6A8",
     "unicode_alternates": [],
     "name": "police cars revolving light",
     "shortname": ":rotating_light:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23419,8 +26239,8 @@
       "emergency",
       "police",
       "light",
-      "police",
-      "emergency"
+      "transportation",
+      "object"
     ],
     "moji": "🚨"
   },
@@ -23429,11 +26249,13 @@
     "unicode_alternates": [],
     "name": "round pushpin",
     "shortname": ":round_pushpin:",
-    "category": "places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "stationery"
+      "stationery",
+      "object",
+      "office"
     ],
     "moji": "📍"
   },
@@ -23442,7 +26264,7 @@
     "unicode_alternates": [],
     "name": "rowboat",
     "shortname": ":rowboat:",
-    "category": "places",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23453,13 +26275,18 @@
       "boat",
       "row",
       "oar",
-      "paddle"
+      "paddle",
+      "men",
+      "workout",
+      "sport",
+      "rowing",
+      "diversity"
     ],
     "moji": "🚣"
   },
   "rowboat_tone1": {
     "unicode": "1F6A3-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "rowboat tone 1",
     "shortname": ":rowboat_tone1:",
     "category": "activity",
@@ -23473,11 +26300,12 @@
       "row",
       "oar",
       "paddle"
-    ]
+    ],
+    "moji": "🚣🏻"
   },
   "rowboat_tone2": {
     "unicode": "1F6A3-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "rowboat tone 2",
     "shortname": ":rowboat_tone2:",
     "category": "activity",
@@ -23491,11 +26319,12 @@
       "row",
       "oar",
       "paddle"
-    ]
+    ],
+    "moji": "🚣🏼"
   },
   "rowboat_tone3": {
     "unicode": "1F6A3-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "rowboat tone 3",
     "shortname": ":rowboat_tone3:",
     "category": "activity",
@@ -23509,11 +26338,12 @@
       "row",
       "oar",
       "paddle"
-    ]
+    ],
+    "moji": "🚣🏽"
   },
   "rowboat_tone4": {
     "unicode": "1F6A3-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "rowboat tone 4",
     "shortname": ":rowboat_tone4:",
     "category": "activity",
@@ -23527,11 +26357,12 @@
       "row",
       "oar",
       "paddle"
-    ]
+    ],
+    "moji": "🚣🏾"
   },
   "rowboat_tone5": {
     "unicode": "1F6A3-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "rowboat tone 5",
     "shortname": ":rowboat_tone5:",
     "category": "activity",
@@ -23545,14 +26376,15 @@
       "row",
       "oar",
       "paddle"
-    ]
+    ],
+    "moji": "🚣🏿"
   },
   "rugby_football": {
     "unicode": "1F3C9",
     "unicode_alternates": [],
     "name": "rugby football",
     "shortname": ":rugby_football:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23562,7 +26394,8 @@
       "ball",
       "sport",
       "team",
-      "england"
+      "england",
+      "game"
     ],
     "moji": "🏉"
   },
@@ -23571,7 +26404,7 @@
     "unicode_alternates": [],
     "name": "runner",
     "shortname": ":runner:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23581,16 +26414,19 @@
       "run",
       "runner",
       "jog",
-      "exercise",
       "sprint",
       "race",
-      "dash"
+      "dash",
+      "people",
+      "men",
+      "diversity",
+      "boys night"
     ],
     "moji": "🏃"
   },
   "runner_tone1": {
     "unicode": "1F3C3-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "runner tone 1",
     "shortname": ":runner_tone1:",
     "category": "people",
@@ -23605,11 +26441,12 @@
       "race",
       "dash",
       "marathon"
-    ]
+    ],
+    "moji": "🏃🏻"
   },
   "runner_tone2": {
     "unicode": "1F3C3-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "runner tone 2",
     "shortname": ":runner_tone2:",
     "category": "people",
@@ -23624,11 +26461,12 @@
       "race",
       "dash",
       "marathon"
-    ]
+    ],
+    "moji": "🏃🏼"
   },
   "runner_tone3": {
     "unicode": "1F3C3-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "runner tone 3",
     "shortname": ":runner_tone3:",
     "category": "people",
@@ -23643,11 +26481,12 @@
       "race",
       "dash",
       "marathon"
-    ]
+    ],
+    "moji": "🏃🏽"
   },
   "runner_tone4": {
     "unicode": "1F3C3-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "runner tone 4",
     "shortname": ":runner_tone4:",
     "category": "people",
@@ -23662,11 +26501,12 @@
       "race",
       "dash",
       "marathon"
-    ]
+    ],
+    "moji": "🏃🏾"
   },
   "runner_tone5": {
     "unicode": "1F3C3-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "runner tone 5",
     "shortname": ":runner_tone5:",
     "category": "people",
@@ -23681,14 +26521,15 @@
       "race",
       "dash",
       "marathon"
-    ]
+    ],
+    "moji": "🏃🏿"
   },
   "running_shirt_with_sash": {
     "unicode": "1F3BD",
     "unicode_alternates": [],
     "name": "running shirt with sash",
     "shortname": ":running_shirt_with_sash:",
-    "category": "emoticons",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23699,13 +26540,14 @@
       "shirt",
       "cloths",
       "compete",
-      "sports"
+      "sports",
+      "award"
     ],
     "moji": "🎽"
   },
   "sa": {
     "unicode": "1F202",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "squared katakana sa",
     "shortname": ":sa:",
     "category": "symbols",
@@ -23716,7 +26558,8 @@
       "japanese",
       "symbol",
       "word"
-    ]
+    ],
+    "moji": "🈂"
   },
   "sagittarius": {
     "unicode": "2650",
@@ -23725,7 +26568,7 @@
     ],
     "name": "sagittarius",
     "shortname": ":sagittarius:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23738,9 +26581,8 @@
       "stars",
       "zodiac",
       "sign",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♐"
   },
@@ -23751,12 +26593,15 @@
     ],
     "name": "sailboat",
     "shortname": ":sailboat:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "ship",
-      "transportation"
+      "transportation",
+      "travel",
+      "boat",
+      "vacation"
     ],
     "moji": "⛵"
   },
@@ -23765,7 +26610,7 @@
     "unicode_alternates": [],
     "name": "sake bottle and cup",
     "shortname": ":sake:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23774,26 +26619,41 @@
       "drunk",
       "wine",
       "sake",
-      "wine",
       "rice",
       "ferment",
       "alcohol",
       "japanese",
-      "drink"
+      "japan",
+      "girls night"
     ],
     "moji": "🍶"
   },
+  "salad": {
+    "unicode": "1F957",
+    "unicode_alternates": [],
+    "name": "green salad",
+    "shortname": ":salad:",
+    "category": "food",
+    "aliases": [
+      ":green_salad:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥗"
+  },
   "sandal": {
     "unicode": "1F461",
     "unicode_alternates": [],
     "name": "womans sandal",
     "shortname": ":sandal:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "fashion",
-      "shoes"
+      "shoes",
+      "shoe",
+      "accessories"
     ],
     "moji": "👡"
   },
@@ -23802,7 +26662,7 @@
     "unicode_alternates": [],
     "name": "father christmas",
     "shortname": ":santa:",
-    "category": "objects",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23823,14 +26683,18 @@
       "nice",
       "sleigh",
       "father",
-      "christmas",
-      "holiday"
+      "holiday",
+      "people",
+      "hat",
+      "winter",
+      "holidays",
+      "diversity"
     ],
     "moji": "🎅"
   },
   "santa_tone1": {
     "unicode": "1F385-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "father christmas tone 1",
     "shortname": ":santa_tone1:",
     "category": "people",
@@ -23852,11 +26716,12 @@
       "nice",
       "sleigh",
       "holiday"
-    ]
+    ],
+    "moji": "🎅🏻"
   },
   "santa_tone2": {
     "unicode": "1F385-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "father christmas tone 2",
     "shortname": ":santa_tone2:",
     "category": "people",
@@ -23878,11 +26743,12 @@
       "nice",
       "sleigh",
       "holiday"
-    ]
+    ],
+    "moji": "🎅🏼"
   },
   "santa_tone3": {
     "unicode": "1F385-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "father christmas tone 3",
     "shortname": ":santa_tone3:",
     "category": "people",
@@ -23904,11 +26770,12 @@
       "nice",
       "sleigh",
       "holiday"
-    ]
+    ],
+    "moji": "🎅🏽"
   },
   "santa_tone4": {
     "unicode": "1F385-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "father christmas tone 4",
     "shortname": ":santa_tone4:",
     "category": "people",
@@ -23930,11 +26797,12 @@
       "nice",
       "sleigh",
       "holiday"
-    ]
+    ],
+    "moji": "🎅🏾"
   },
   "santa_tone5": {
     "unicode": "1F385-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "father christmas tone 5",
     "shortname": ":santa_tone5:",
     "category": "people",
@@ -23956,7 +26824,8 @@
       "nice",
       "sleigh",
       "holiday"
-    ]
+    ],
+    "moji": "🎅🏿"
   },
   "satellite": {
     "unicode": "1F4E1",
@@ -23967,7 +26836,8 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "communication"
+      "communication",
+      "object"
     ],
     "moji": "📡"
   },
@@ -23976,21 +26846,23 @@
     "unicode_alternates": [],
     "name": "satellite",
     "shortname": ":satellite_orbital:",
-    "category": "objects_symbols",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "communication",
       "orbital",
-      "space"
-    ]
+      "space",
+      "object"
+    ],
+    "moji": "🛰"
   },
   "saxophone": {
     "unicode": "1F3B7",
     "unicode_alternates": [],
     "name": "saxophone",
     "shortname": ":saxophone:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -23998,15 +26870,14 @@
       "music",
       "saxophone",
       "sax",
-      "music",
-      "instrument",
-      "woodwind"
+      "woodwind",
+      "instruments"
     ],
     "moji": "🎷"
   },
   "scales": {
     "unicode": "2696",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "scales",
     "shortname": ":scales:",
     "category": "objects",
@@ -24020,14 +26891,15 @@
       "tool",
       "weight",
       "zodiac"
-    ]
+    ],
+    "moji": "⚖"
   },
   "school": {
     "unicode": "1F3EB",
     "unicode_alternates": [],
     "name": "school",
     "shortname": ":school:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24039,7 +26911,8 @@
       "high",
       "college",
       "teach",
-      "education"
+      "education",
+      "places"
     ],
     "moji": "🏫"
   },
@@ -24048,7 +26921,7 @@
     "unicode_alternates": [],
     "name": "school satchel",
     "shortname": ":school_satchel:",
-    "category": "objects",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24058,14 +26931,16 @@
       "school",
       "satchel",
       "backpack",
-      "bag",
       "packing",
       "pack",
       "hike",
-      "education",
       "adventure",
       "travel",
-      "sightsee"
+      "sightsee",
+      "fashion",
+      "office",
+      "vacation",
+      "accessories"
     ],
     "moji": "🎒"
   },
@@ -24081,19 +26956,39 @@
     "aliases_ascii": [],
     "keywords": [
       "cut",
-      "stationery"
+      "stationery",
+      "object",
+      "tool",
+      "weapon",
+      "office"
     ],
     "moji": "✂"
   },
+  "scooter": {
+    "unicode": "1F6F4",
+    "unicode_alternates": [],
+    "name": "scooter",
+    "shortname": ":scooter:",
+    "category": "travel",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🛴"
+  },
   "scorpion": {
     "unicode": "1F982",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "scorpion",
     "shortname": ":scorpion:",
     "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "insects",
+      "reptile",
+      "animal"
+    ],
+    "moji": "🦂"
   },
   "scorpius": {
     "unicode": "264F",
@@ -24102,7 +26997,7 @@
     ],
     "name": "scorpius",
     "shortname": ":scorpius:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24115,9 +27010,8 @@
       "stars",
       "zodiac",
       "sign",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♏"
   },
@@ -24126,7 +27020,7 @@
     "unicode_alternates": [],
     "name": "face screaming in fear",
     "shortname": ":scream:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24135,7 +27029,12 @@
       "scream",
       "painting",
       "artist",
-      "alien"
+      "alien",
+      "smiley",
+      "surprised",
+      "wow",
+      "emotion",
+      "omg"
     ],
     "moji": "😱"
   },
@@ -24144,7 +27043,7 @@
     "unicode_alternates": [],
     "name": "weary cat face",
     "shortname": ":scream_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24161,7 +27060,8 @@
       "exhausted",
       "scream",
       "painting",
-      "artist"
+      "artist",
+      "cat"
     ],
     "moji": "🙀"
   },
@@ -24174,7 +27074,9 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "documents"
+      "documents",
+      "object",
+      "office"
     ],
     "moji": "📜"
   },
@@ -24183,14 +27085,31 @@
     "unicode_alternates": [],
     "name": "seat",
     "shortname": ":seat:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "sit"
+      "sit",
+      "transportation",
+      "object",
+      "travel",
+      "vacation"
     ],
     "moji": "💺"
   },
+  "second_place": {
+    "unicode": "1F948",
+    "unicode_alternates": [],
+    "name": "second place medal",
+    "shortname": ":second_place:",
+    "category": "activity",
+    "aliases": [
+      ":second_place_medal:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥈"
+  },
   "secret": {
     "unicode": "3299",
     "unicode_alternates": [
@@ -24198,11 +27117,13 @@
     ],
     "name": "circled ideograph secret",
     "shortname": ":secret:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "privacy"
+      "privacy",
+      "japan",
+      "symbol"
     ],
     "moji": "㊙"
   },
@@ -24211,14 +27132,13 @@
     "unicode_alternates": [],
     "name": "see-no-evil monkey",
     "shortname": ":see_no_evil:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "animal",
       "monkey",
       "nature",
-      "monkey",
       "see",
       "eyes",
       "vision",
@@ -24241,34 +27161,118 @@
       "nature",
       "plant",
       "seedling",
-      "plant",
       "new",
       "start",
-      "grow"
+      "grow",
+      "leaf"
     ],
     "moji": "🌱"
   },
+  "selfie": {
+    "unicode": "1F933",
+    "unicode_alternates": [],
+    "name": "selfie",
+    "shortname": ":selfie:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳"
+  },
+  "selfie_tone1": {
+    "unicode": "1F933-1F3FB",
+    "unicode_alternates": [],
+    "name": "selfie tone 1",
+    "shortname": ":selfie_tone1:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳🏻"
+  },
+  "selfie_tone2": {
+    "unicode": "1F933-1F3FC",
+    "unicode_alternates": [],
+    "name": "selfie tone 2",
+    "shortname": ":selfie_tone2:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳🏼"
+  },
+  "selfie_tone3": {
+    "unicode": "1F933-1F3FD",
+    "unicode_alternates": [],
+    "name": "selfie tone 3",
+    "shortname": ":selfie_tone3:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳🏽"
+  },
+  "selfie_tone4": {
+    "unicode": "1F933-1F3FE",
+    "unicode_alternates": [],
+    "name": "selfie tone 4",
+    "shortname": ":selfie_tone4:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳🏾"
+  },
+  "selfie_tone5": {
+    "unicode": "1F933-1F3FF",
+    "unicode_alternates": [],
+    "name": "selfie tone 5",
+    "shortname": ":selfie_tone5:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤳🏿"
+  },
   "seven": {
     "moji": "7️⃣",
     "unicode": "0037-20E3",
     "unicode_alternates": [
       "0037-FE0F-20E3"
     ],
-    "name": "digit seven",
+    "name": "keycap digit seven",
     "shortname": ":seven:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "7",
       "blue-square",
       "numbers",
-      "prime"
+      "prime",
+      "number",
+      "math",
+      "symbol"
     ]
   },
+  "shallow_pan_of_food": {
+    "unicode": "1F958",
+    "unicode_alternates": [],
+    "name": "shallow pan of food",
+    "shortname": ":shallow_pan_of_food:",
+    "category": "food",
+    "aliases": [
+      ":paella:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [
+      "pan of food"
+    ],
+    "moji": "🥘"
+  },
   "shamrock": {
     "unicode": "2618",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "shamrock",
     "shortname": ":shamrock:",
     "category": "nature",
@@ -24276,15 +27280,29 @@
     "aliases_ascii": [],
     "keywords": [
       "nature",
-      "plant"
-    ]
+      "plant",
+      "luck",
+      "leaf"
+    ],
+    "moji": "☘"
+  },
+  "shark": {
+    "unicode": "1F988",
+    "unicode_alternates": [],
+    "name": "shark",
+    "shortname": ":shark:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦈"
   },
   "shaved_ice": {
     "unicode": "1F367",
     "unicode_alternates": [],
     "name": "shaved ice",
     "shortname": ":shaved_ice:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24295,7 +27313,8 @@
       "dessert",
       "treat",
       "syrup",
-      "flavoring"
+      "flavoring",
+      "food"
     ],
     "moji": "🍧"
   },
@@ -24334,7 +27353,6 @@
       "sea",
       "shell",
       "spiral",
-      "beach",
       "sand",
       "crab",
       "nautilus"
@@ -24346,7 +27364,7 @@
     "unicode_alternates": [],
     "name": "shield",
     "shortname": ":shield:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24354,12 +27372,13 @@
       "route",
       "sign",
       "highway",
-      "interstate"
-    ]
+      "object"
+    ],
+    "moji": "🛡"
   },
   "shinto_shrine": {
     "unicode": "26E9",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "shinto shrine",
     "shortname": ":shinto_shrine:",
     "category": "travel",
@@ -24367,15 +27386,20 @@
     "aliases_ascii": [],
     "keywords": [
       "religion",
-      "symbol"
-    ]
+      "symbol",
+      "places",
+      "building",
+      "travel",
+      "vacation"
+    ],
+    "moji": "⛩"
   },
   "ship": {
     "unicode": "1F6A2",
     "unicode_alternates": [],
     "name": "ship",
     "shortname": ":ship:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24383,7 +27407,9 @@
       "transportation",
       "ferry",
       "ship",
-      "boat"
+      "boat",
+      "travel",
+      "vacation"
     ],
     "moji": "🚢"
   },
@@ -24392,7 +27418,7 @@
     "unicode_alternates": [],
     "name": "t-shirt",
     "shortname": ":shirt:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24406,7 +27432,7 @@
     "unicode_alternates": [],
     "name": "shopping bags",
     "shortname": ":shopping_bags:",
-    "category": "travel_places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24414,8 +27440,25 @@
       "mall",
       "buy",
       "store",
-      "shop"
-    ]
+      "shop",
+      "object",
+      "birthday",
+      "parties"
+    ],
+    "moji": "🛍"
+  },
+  "shopping_cart": {
+    "unicode": "1F6D2",
+    "unicode_alternates": [],
+    "name": "shopping trolley",
+    "shortname": ":shopping_cart:",
+    "category": "objects",
+    "aliases": [
+      ":shopping_trolley:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🛒"
   },
   "shower": {
     "unicode": "1F6BF",
@@ -24433,22 +27476,100 @@
       "shower",
       "soap",
       "water",
-      "clean",
       "shampoo",
-      "lather"
+      "lather",
+      "object"
     ],
     "moji": "🚿"
   },
+  "shrimp": {
+    "unicode": "1F990",
+    "unicode_alternates": [],
+    "name": "shrimp",
+    "shortname": ":shrimp:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦐"
+  },
+  "shrug": {
+    "unicode": "1F937",
+    "unicode_alternates": [],
+    "name": "shrug",
+    "shortname": ":shrug:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷"
+  },
+  "shrug_tone1": {
+    "unicode": "1F937-1F3FB",
+    "unicode_alternates": [],
+    "name": "shrug tone 1",
+    "shortname": ":shrug_tone1:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷🏻"
+  },
+  "shrug_tone2": {
+    "unicode": "1F937-1F3FC",
+    "unicode_alternates": [],
+    "name": "shrug tone 2",
+    "shortname": ":shrug_tone2:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷🏼"
+  },
+  "shrug_tone3": {
+    "unicode": "1F937-1F3FD",
+    "unicode_alternates": [],
+    "name": "shrug tone 3",
+    "shortname": ":shrug_tone3:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷🏽"
+  },
+  "shrug_tone4": {
+    "unicode": "1F937-1F3FE",
+    "unicode_alternates": [],
+    "name": "shrug tone 4",
+    "shortname": ":shrug_tone4:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷🏾"
+  },
+  "shrug_tone5": {
+    "unicode": "1F937-1F3FF",
+    "unicode_alternates": [],
+    "name": "shrug tone 5",
+    "shortname": ":shrug_tone5:",
+    "category": "people",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤷🏿"
+  },
   "signal_strength": {
     "unicode": "1F4F6",
     "unicode_alternates": [],
     "name": "antenna with bars",
     "shortname": ":signal_strength:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "📶"
   },
@@ -24458,15 +27579,18 @@
     "unicode_alternates": [
       "0036-FE0F-20E3"
     ],
-    "name": "digit six",
+    "name": "keycap digit six",
     "shortname": ":six:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "6",
       "blue-square",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "six_pointed_star": {
@@ -24474,11 +27598,15 @@
     "unicode_alternates": [],
     "name": "six pointed star with middle dot",
     "shortname": ":six_pointed_star:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "purple-square"
+      "purple-square",
+      "religion",
+      "jew",
+      "star",
+      "symbol"
     ],
     "moji": "🔯"
   },
@@ -24487,7 +27615,7 @@
     "unicode_alternates": [],
     "name": "ski and ski boot",
     "shortname": ":ski:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24499,18 +27627,19 @@
       "cross-country",
       "poles",
       "snow",
-      "winter",
       "mountain",
       "alpine",
       "powder",
       "slalom",
-      "freestyle"
+      "freestyle",
+      "sport",
+      "skiing"
     ],
     "moji": "🎿"
   },
   "skier": {
     "unicode": "26F7",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "skier",
     "shortname": ":skier:",
     "category": "activity",
@@ -24521,15 +27650,20 @@
       "ski",
       "snow",
       "sport",
-      "travel"
-    ]
+      "travel",
+      "hat",
+      "vacation",
+      "cold",
+      "skiing"
+    ],
+    "moji": "⛷"
   },
   "skull": {
     "unicode": "1F480",
     "unicode_alternates": [],
     "name": "skull",
     "shortname": ":skull:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":skeleton:"
     ],
@@ -24537,13 +27671,15 @@
     "keywords": [
       "dead",
       "skeleton",
-      "dying"
+      "dying",
+      "halloween",
+      "skull"
     ],
     "moji": "💀"
   },
   "skull_crossbones": {
     "unicode": "2620",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "skull and crossbones",
     "shortname": ":skull_crossbones:",
     "category": "objects",
@@ -24556,15 +27692,19 @@
       "death",
       "face",
       "monster",
-      "person"
-    ]
+      "person",
+      "symbol",
+      "dead",
+      "skull"
+    ],
+    "moji": "☠"
   },
   "sleeping": {
     "unicode": "1F634",
     "unicode_alternates": [],
     "name": "sleeping face",
     "shortname": ":sleeping:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24572,9 +27712,11 @@
       "sleepy",
       "tired",
       "sleep",
-      "sleepy",
       "sleeping",
-      "snore"
+      "snore",
+      "smiley",
+      "emotion",
+      "goodnight"
     ],
     "moji": "😴"
   },
@@ -24583,21 +27725,23 @@
     "unicode_alternates": [],
     "name": "sleeping accommodation",
     "shortname": ":sleeping_accommodation:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "hotel",
       "motel",
-      "rest"
-    ]
+      "rest",
+      "tired"
+    ],
+    "moji": "🛌"
   },
   "sleepy": {
     "unicode": "1F62A",
     "unicode_alternates": [],
     "name": "sleepy face",
     "shortname": ":sleepy:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24605,8 +27749,10 @@
       "rest",
       "tired",
       "sleepy",
-      "tired",
-      "exhausted"
+      "exhausted",
+      "smiley",
+      "sick",
+      "emotion"
     ],
     "moji": "😪"
   },
@@ -24624,8 +27770,12 @@
       "slight",
       "frown",
       "unhappy",
-      "disappointed"
-    ]
+      "disappointed",
+      "sad",
+      "smiley",
+      "emotion"
+    ],
+    "moji": "🙁"
   },
   "slight_smile": {
     "unicode": "1F642",
@@ -24640,15 +27790,17 @@
     "keywords": [
       "slight",
       "smile",
-      "happy"
-    ]
+      "happy",
+      "smiley"
+    ],
+    "moji": "🙂"
   },
   "slot_machine": {
     "unicode": "1F3B0",
     "unicode_alternates": [],
     "name": "slot machine",
     "shortname": ":slot_machine:",
-    "category": "places",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24657,10 +27809,11 @@
       "vegas",
       "slot",
       "machine",
-      "gamble",
       "one-armed bandit",
       "slots",
-      "luck"
+      "luck",
+      "game",
+      "boys night"
     ],
     "moji": "🎰"
   },
@@ -24669,11 +27822,13 @@
     "unicode_alternates": [],
     "name": "small blue diamond",
     "shortname": ":small_blue_diamond:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol"
     ],
     "moji": "🔹"
   },
@@ -24682,11 +27837,13 @@
     "unicode_alternates": [],
     "name": "small orange diamond",
     "shortname": ":small_orange_diamond:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol"
     ],
     "moji": "🔸"
   },
@@ -24695,11 +27852,14 @@
     "unicode_alternates": [],
     "name": "up-pointing red triangle",
     "shortname": ":small_red_triangle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "triangle"
     ],
     "moji": "🔺"
   },
@@ -24708,11 +27868,14 @@
     "unicode_alternates": [],
     "name": "down-pointing red triangle",
     "shortname": ":small_red_triangle_down:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "triangle"
     ],
     "moji": "🔻"
   },
@@ -24721,7 +27884,7 @@
     "unicode_alternates": [],
     "name": "smiling face with open mouth and smiling eyes",
     "shortname": ":smile:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":)",
@@ -24739,7 +27902,8 @@
       "laugh",
       "smile",
       "smiley",
-      "smiling"
+      "smiling",
+      "emotion"
     ],
     "moji": "😄"
   },
@@ -24748,7 +27912,7 @@
     "unicode_alternates": [],
     "name": "grinning cat face with smiling eyes",
     "shortname": ":smile_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24757,7 +27921,8 @@
       "cat",
       "smile",
       "grin",
-      "grinning"
+      "grinning",
+      "happy"
     ],
     "moji": "😸"
   },
@@ -24766,7 +27931,7 @@
     "unicode_alternates": [],
     "name": "smiling face with open mouth",
     "shortname": ":smiley:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":D",
@@ -24780,7 +27945,9 @@
       "joy",
       "smiling",
       "smile",
-      "smiley"
+      "smiley",
+      "emotion",
+      "good"
     ],
     "moji": "😃"
   },
@@ -24789,7 +27956,7 @@
     "unicode_alternates": [],
     "name": "smiling cat face with open mouth",
     "shortname": ":smiley_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24798,8 +27965,7 @@
       "happy",
       "smile",
       "smiley",
-      "cat",
-      "happy"
+      "cat"
     ],
     "moji": "😺"
   },
@@ -24808,16 +27974,19 @@
     "unicode_alternates": [],
     "name": "smiling face with horns",
     "shortname": ":smiling_imp:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "devil",
       "horns",
-      "horns",
-      "devil",
       "impish",
-      "trouble"
+      "trouble",
+      "silly",
+      "smiley",
+      "angry",
+      "monster",
+      "boys night"
     ],
     "moji": "😈"
   },
@@ -24826,7 +27995,7 @@
     "unicode_alternates": [],
     "name": "smirking face",
     "shortname": ":smirk:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24836,10 +28005,12 @@
       "smug",
       "smirking",
       "smirk",
-      "smug",
-      "smile",
       "half-smile",
-      "conceited"
+      "conceited",
+      "silly",
+      "smiley",
+      "sexy",
+      "sarcastic"
     ],
     "moji": "😏"
   },
@@ -24848,7 +28019,7 @@
     "unicode_alternates": [],
     "name": "cat face with wry smile",
     "shortname": ":smirk_cat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24858,7 +28029,8 @@
       "smirking",
       "wry",
       "confident",
-      "confidence"
+      "confidence",
+      "cat"
     ],
     "moji": "😼"
   },
@@ -24875,13 +28047,14 @@
       "kills",
       "tobacco",
       "smoking",
-      "cigarette",
       "smoke",
       "cancer",
       "lungs",
       "inhale",
       "tar",
-      "nicotine"
+      "nicotine",
+      "symbol",
+      "drugs"
     ],
     "moji": "🚬"
   },
@@ -24898,10 +28071,10 @@
       "shell",
       "slow",
       "snail",
-      "slow",
       "escargot",
       "french",
-      "appetizer"
+      "appetizer",
+      "insects"
     ],
     "moji": "🐌"
   },
@@ -24915,16 +28088,32 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "evil"
+      "evil",
+      "wildlife",
+      "reptile",
+      "creationism"
     ],
     "moji": "🐍"
   },
+  "sneezing_face": {
+    "unicode": "1F927",
+    "unicode_alternates": [],
+    "name": "sneezing face",
+    "shortname": ":sneezing_face:",
+    "category": "people",
+    "aliases": [
+      ":sneeze:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤧"
+  },
   "snowboarder": {
     "unicode": "1F3C2",
     "unicode_alternates": [],
     "name": "snowboarder",
     "shortname": ":snowboarder:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -24932,13 +28121,16 @@
       "winter",
       "snow",
       "boarding",
-      "sports",
       "freestyle",
       "halfpipe",
       "board",
       "mountain",
       "alpine",
-      "winter"
+      "hat",
+      "vacation",
+      "cold",
+      "sport",
+      "snowboarding"
     ],
     "moji": "🏂"
   },
@@ -24965,13 +28157,13 @@
       "droplet",
       "ice",
       "crystal",
-      "cold",
       "chilly",
-      "winter",
       "unique",
       "special",
       "below zero",
-      "elsa"
+      "elsa",
+      "sky",
+      "holidays"
     ],
     "moji": "❄"
   },
@@ -24991,13 +28183,15 @@
       "season",
       "weather",
       "winter",
-      "xmas"
+      "xmas",
+      "holidays",
+      "snow"
     ],
     "moji": "⛄"
   },
   "snowman2": {
     "unicode": "2603",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "snowman",
     "shortname": ":snowman2:",
     "category": "nature",
@@ -25007,15 +28201,19 @@
       "cold",
       "nature",
       "snow",
-      "weather"
-    ]
+      "weather",
+      "winter",
+      "holidays",
+      "christmas"
+    ],
+    "moji": "☃"
   },
   "sob": {
     "unicode": "1F62D",
     "unicode_alternates": [],
     "name": "loudly crying face",
     "shortname": ":sob:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25024,14 +28222,14 @@
       "sad",
       "tears",
       "upset",
-      "cry",
       "sob",
-      "tears",
-      "sad",
       "melancholy",
       "morn",
       "somber",
-      "hurt"
+      "hurt",
+      "smiley",
+      "emotion",
+      "heartbreak"
     ],
     "moji": "😭"
   },
@@ -25042,7 +28240,7 @@
     ],
     "name": "soccer ball",
     "shortname": ":soccer:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25051,7 +28249,10 @@
       "football",
       "sports",
       "european",
-      "football"
+      "game",
+      "ball",
+      "sport",
+      "soccer"
     ],
     "moji": "⚽"
   },
@@ -25060,12 +28261,13 @@
     "unicode_alternates": [],
     "name": "soon with rightwards arrow above",
     "shortname": ":soon:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arrow",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🔜"
   },
@@ -25074,14 +28276,15 @@
     "unicode_alternates": [],
     "name": "squared sos",
     "shortname": ":sos:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "emergency",
       "help",
       "red-square",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🆘"
   },
@@ -25090,12 +28293,14 @@
     "unicode_alternates": [],
     "name": "speaker with one sound wave",
     "shortname": ":sound:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "speaker",
-      "volume"
+      "volume",
+      "alarm",
+      "symbol"
     ],
     "moji": "🔉"
   },
@@ -25104,12 +28309,14 @@
     "unicode_alternates": [],
     "name": "alien monster",
     "shortname": ":space_invader:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "arcade",
-      "game"
+      "game",
+      "monster",
+      "alien"
     ],
     "moji": "👾"
   },
@@ -25120,12 +28327,14 @@
     ],
     "name": "black spade suit",
     "shortname": ":spades:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cards",
-      "poker"
+      "poker",
+      "symbol",
+      "game"
     ],
     "moji": "♠"
   },
@@ -25134,7 +28343,7 @@
     "unicode_alternates": [],
     "name": "spaghetti",
     "shortname": ":spaghetti:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25145,7 +28354,7 @@
       "noodles",
       "tomato",
       "sauce",
-      "italian"
+      "pasta"
     ],
     "moji": "🍝"
   },
@@ -25156,12 +28365,13 @@
     ],
     "name": "sparkle",
     "shortname": ":sparkle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "green-square",
-      "stars"
+      "stars",
+      "symbol"
     ],
     "moji": "❇"
   },
@@ -25170,13 +28380,14 @@
     "unicode_alternates": [],
     "name": "firework sparkler",
     "shortname": ":sparkler:",
-    "category": "objects",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "night",
       "shine",
-      "stars"
+      "stars",
+      "parties"
     ],
     "moji": "🎇"
   },
@@ -25185,14 +28396,16 @@
     "unicode_alternates": [],
     "name": "sparkles",
     "shortname": ":sparkles:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "cool",
       "shine",
       "shiny",
-      "stars"
+      "stars",
+      "star",
+      "girls night"
     ],
     "moji": "✨"
   },
@@ -25201,14 +28414,16 @@
     "unicode_alternates": [],
     "name": "sparkling heart",
     "shortname": ":sparkling_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "affection",
       "like",
       "love",
-      "valentines"
+      "valentines",
+      "symbol",
+      "girls night"
     ],
     "moji": "💖"
   },
@@ -25217,13 +28432,12 @@
     "unicode_alternates": [],
     "name": "speak-no-evil monkey",
     "shortname": ":speak_no_evil:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "animal",
       "monkey",
-      "monkey",
       "mouth",
       "talk",
       "say",
@@ -25240,36 +28454,41 @@
     "unicode_alternates": [],
     "name": "speaker",
     "shortname": ":speaker:",
-    "category": "objects",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "sound",
       "listen",
       "hear",
-      "noise"
-    ]
+      "noise",
+      "alarm",
+      "symbol"
+    ],
+    "moji": "🔈"
   },
   "speaking_head": {
     "unicode": "1F5E3",
     "unicode_alternates": [],
     "name": "speaking head in silhouette",
     "shortname": ":speaking_head:",
-    "category": "objects_symbols",
+    "category": "people",
     "aliases": [
       ":speaking_head_in_silhouette:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "talk"
-    ]
+      "talk",
+      "people"
+    ],
+    "moji": "🗣"
   },
   "speech_balloon": {
     "unicode": "1F4AC",
     "unicode_alternates": [],
     "name": "speech balloon",
     "shortname": ":speech_balloon:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25281,96 +28500,18 @@
       "conversation",
       "communication",
       "comic",
-      "dialogue"
+      "dialogue",
+      "symbol",
+      "free speech"
     ],
     "moji": "💬"
   },
-  "speech_left": {
-    "unicode": "1F5E8",
-    "unicode_alternates": [],
-    "name": "left speech bubble",
-    "shortname": ":speech_left:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":left_speech_bubble:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "words",
-      "talk",
-      "conversation",
-      "communication",
-      "comic",
-      "dialogue"
-    ]
-  },
-  "speech_right": {
-    "unicode": "1F5E9",
-    "unicode_alternates": [],
-    "name": "right speech bubble",
-    "shortname": ":speech_right:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":right_speech_bubble:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "words",
-      "talk",
-      "conversation",
-      "communication",
-      "comic",
-      "dialogue"
-    ]
-  },
-  "speech_three": {
-    "unicode": "1F5EB",
-    "unicode_alternates": [],
-    "name": "three speech bubbles",
-    "shortname": ":speech_three:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":three_speech_bubbles:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "words",
-      "talk",
-      "conversation",
-      "communication",
-      "comic",
-      "dialogue"
-    ]
-  },
-  "speech_two": {
-    "unicode": "1F5EA",
-    "unicode_alternates": [],
-    "name": "two speech bubbles",
-    "shortname": ":speech_two:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":two_speech_bubbles:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "words",
-      "talk",
-      "conversation",
-      "communication",
-      "comic",
-      "dialogue"
-    ]
-  },
   "speedboat": {
     "unicode": "1F6A4",
     "unicode_alternates": [],
     "name": "speedboat",
     "shortname": ":speedboat:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25381,7 +28522,10 @@
       "speed",
       "ski",
       "power",
-      "boat"
+      "boat",
+      "travel",
+      "vacation",
+      "tropical"
     ],
     "moji": "🚤"
   },
@@ -25395,8 +28539,12 @@
     "aliases_ascii": [],
     "keywords": [
       "arachnid",
-      "eight-legged"
-    ]
+      "eight-legged",
+      "insects",
+      "halloween",
+      "animal"
+    ],
+    "moji": "🕷"
   },
   "spider_web": {
     "unicode": "1F578",
@@ -25407,8 +28555,21 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "cobweb"
-    ]
+      "cobweb",
+      "halloween"
+    ],
+    "moji": "🕸"
+  },
+  "spoon": {
+    "unicode": "1F944",
+    "unicode_alternates": [],
+    "name": "spoon",
+    "shortname": ":spoon:",
+    "category": "food",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥄"
   },
   "spy": {
     "unicode": "1F575",
@@ -25423,12 +28584,19 @@
     "keywords": [
       "pi",
       "undercover",
-      "investigator"
-    ]
+      "investigator",
+      "people",
+      "hat",
+      "men",
+      "glasses",
+      "diversity",
+      "job"
+    ],
+    "moji": "🕵"
   },
   "spy_tone1": {
     "unicode": "1F575-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sleuth or spy tone 1",
     "shortname": ":spy_tone1:",
     "category": "people",
@@ -25441,11 +28609,12 @@
       "undercover",
       "investigator",
       "person"
-    ]
+    ],
+    "moji": "🕵🏻"
   },
   "spy_tone2": {
     "unicode": "1F575-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sleuth or spy tone 2",
     "shortname": ":spy_tone2:",
     "category": "people",
@@ -25458,11 +28627,12 @@
       "undercover",
       "investigator",
       "person"
-    ]
+    ],
+    "moji": "🕵🏼"
   },
   "spy_tone3": {
     "unicode": "1F575-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sleuth or spy tone 3",
     "shortname": ":spy_tone3:",
     "category": "people",
@@ -25475,11 +28645,12 @@
       "undercover",
       "investigator",
       "person"
-    ]
+    ],
+    "moji": "🕵🏽"
   },
   "spy_tone4": {
     "unicode": "1F575-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sleuth or spy tone 4",
     "shortname": ":spy_tone4:",
     "category": "people",
@@ -25492,11 +28663,12 @@
       "undercover",
       "investigator",
       "person"
-    ]
+    ],
+    "moji": "🕵🏾"
   },
   "spy_tone5": {
     "unicode": "1F575-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "sleuth or spy tone 5",
     "shortname": ":spy_tone5:",
     "category": "people",
@@ -25509,14 +28681,26 @@
       "undercover",
       "investigator",
       "person"
-    ]
+    ],
+    "moji": "🕵🏿"
+  },
+  "squid": {
+    "unicode": "1F991",
+    "unicode_alternates": [],
+    "name": "squid",
+    "shortname": ":squid:",
+    "category": "nature",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🦑"
   },
   "stadium": {
     "unicode": "1F3DF",
     "unicode_alternates": [],
     "name": "stadium",
     "shortname": ":stadium:",
-    "category": "travel_places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25524,8 +28708,14 @@
       "event",
       "concert",
       "convention",
-      "game"
-    ]
+      "game",
+      "places",
+      "building",
+      "travel",
+      "vacation",
+      "boys night"
+    ],
+    "moji": "🏟"
   },
   "star": {
     "unicode": "2B50",
@@ -25539,7 +28729,10 @@
     "aliases_ascii": [],
     "keywords": [
       "night",
-      "yellow"
+      "yellow",
+      "space",
+      "sky",
+      "star"
     ],
     "moji": "⭐"
   },
@@ -25548,7 +28741,7 @@
     "unicode_alternates": [],
     "name": "glowing star",
     "shortname": ":star2:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25559,13 +28752,15 @@
       "star",
       "five",
       "points",
-      "classic"
+      "classic",
+      "space",
+      "sky"
     ],
     "moji": "🌟"
   },
   "star_and_crescent": {
     "unicode": "262A",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "star and crescent",
     "shortname": ":star_and_crescent:",
     "category": "symbols",
@@ -25576,11 +28771,12 @@
       "muslim",
       "religion",
       "symbol"
-    ]
+    ],
+    "moji": "☪"
   },
   "star_of_david": {
     "unicode": "2721",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "star of david",
     "shortname": ":star_of_david:",
     "category": "symbols",
@@ -25590,15 +28786,17 @@
       "jew",
       "jewish",
       "religion",
-      "symbol"
-    ]
+      "symbol",
+      "star"
+    ],
+    "moji": "✡"
   },
   "stars": {
     "unicode": "1F320",
     "unicode_alternates": [],
     "name": "shooting star",
     "shortname": ":stars:",
-    "category": "nature",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25608,9 +28806,9 @@
       "shoot",
       "star",
       "sky",
-      "night",
       "comet",
-      "meteoroid"
+      "meteoroid",
+      "space"
     ],
     "moji": "🌠"
   },
@@ -25619,7 +28817,7 @@
     "unicode_alternates": [],
     "name": "station",
     "shortname": ":station:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25628,7 +28826,8 @@
       "vehicle",
       "station",
       "train",
-      "subway"
+      "subway",
+      "travel"
     ],
     "moji": "🚉"
   },
@@ -25637,12 +28836,18 @@
     "unicode_alternates": [],
     "name": "statue of liberty",
     "shortname": ":statue_of_liberty:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "american",
-      "newyork"
+      "newyork",
+      "places",
+      "america",
+      "travel",
+      "vacation",
+      "statue of liberty",
+      "free speech"
     ],
     "moji": "🗽"
   },
@@ -25651,7 +28856,7 @@
     "unicode_alternates": [],
     "name": "steam locomotive",
     "shortname": ":steam_locomotive:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25660,35 +28865,17 @@
       "vehicle",
       "locomotive",
       "steam",
-      "train",
-      "engine"
+      "engine",
+      "travel"
     ],
     "moji": "🚂"
   },
-  "stereo": {
-    "unicode": "1F4FE",
-    "unicode_alternates": [],
-    "name": "portable stereo",
-    "shortname": ":stereo:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":portable_stereo:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "music",
-      "program",
-      "boom",
-      "box"
-    ]
-  },
   "stew": {
     "unicode": "1F372",
     "unicode_alternates": [],
     "name": "pot of food",
     "shortname": ":stew:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25697,30 +28884,16 @@
       "stew",
       "hearty",
       "soup",
-      "thick",
-      "hot",
-      "pot"
-    ],
-    "moji": "🍲"
-  },
-  "stock_chart": {
-    "unicode": "1F5E0",
-    "unicode_alternates": [],
-    "name": "stock chart",
-    "shortname": ":stock_chart:",
-    "category": "objects_symbols",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": [
-      "graph",
-      "presentation",
-      "stats",
-      "business"
-    ]
+      "thick",
+      "hot",
+      "pot",
+      "steam"
+    ],
+    "moji": "🍲"
   },
   "stop_button": {
     "unicode": "23F9",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "black square for stop",
     "shortname": ":stop_button:",
     "category": "symbols",
@@ -25728,12 +28901,14 @@
     "aliases_ascii": [],
     "keywords": [
       "sound",
-      "symbol"
-    ]
+      "symbol",
+      "square"
+    ],
+    "moji": "⏹"
   },
   "stopwatch": {
     "unicode": "23F1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "stopwatch",
     "shortname": ":stopwatch:",
     "category": "objects",
@@ -25742,8 +28917,10 @@
     "keywords": [
       "clock",
       "object",
-      "time"
-    ]
+      "time",
+      "electronics"
+    ],
+    "moji": "⏱"
   },
   "straight_ruler": {
     "unicode": "1F4CF",
@@ -25754,7 +28931,10 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "stationery"
+      "stationery",
+      "object",
+      "tool",
+      "office"
     ],
     "moji": "📏"
   },
@@ -25763,7 +28943,7 @@
     "unicode_alternates": [],
     "name": "strawberry",
     "shortname": ":strawberry:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25782,7 +28962,7 @@
     "unicode_alternates": [],
     "name": "face with stuck-out tongue",
     "shortname": ":stuck_out_tongue:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ":P",
@@ -25807,8 +28987,10 @@
       "prank",
       "tongue",
       "silly",
-      "playful",
-      "cheeky"
+      "cheeky",
+      "smiley",
+      "sex",
+      "emotion"
     ],
     "moji": "😛"
   },
@@ -25817,7 +28999,7 @@
     "unicode_alternates": [],
     "name": "face with stuck-out tongue and tightly-closed eyes",
     "shortname": ":stuck_out_tongue_closed_eyes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25828,8 +29010,10 @@
       "tongue",
       "kidding",
       "silly",
-      "playful",
-      "ecstatic"
+      "ecstatic",
+      "happy",
+      "smiley",
+      "emotion"
     ],
     "moji": "😝"
   },
@@ -25838,7 +29022,7 @@
     "unicode_alternates": [],
     "name": "face with stuck-out tongue and winking eye",
     "shortname": ":stuck_out_tongue_winking_eye:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ">:P",
@@ -25856,11 +29040,27 @@
       "winking",
       "kidding",
       "silly",
-      "playful",
-      "crazy"
+      "crazy",
+      "happy",
+      "smiley",
+      "emotion",
+      "parties"
     ],
     "moji": "😜"
   },
+  "stuffed_flatbread": {
+    "unicode": "1F959",
+    "unicode_alternates": [],
+    "name": "stuffed flatbread",
+    "shortname": ":stuffed_flatbread:",
+    "category": "food",
+    "aliases": [
+      ":stuffed_pita:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥙"
+  },
   "sun_with_face": {
     "unicode": "1F31E",
     "unicode_alternates": [],
@@ -25874,7 +29074,9 @@
       "sun",
       "anthropomorphic",
       "face",
-      "sky"
+      "sky",
+      "day",
+      "hump day"
     ],
     "moji": "🌞"
   },
@@ -25902,7 +29104,7 @@
     "unicode_alternates": [],
     "name": "smiling face with sunglasses",
     "shortname": ":sunglasses:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "B-)",
@@ -25920,8 +29122,11 @@
       "sun",
       "glasses",
       "sunny",
-      "cool",
-      "smooth"
+      "smooth",
+      "silly",
+      "smiley",
+      "emojione",
+      "boys night"
     ],
     "moji": "😎"
   },
@@ -25937,15 +29142,21 @@
     "aliases_ascii": [],
     "keywords": [
       "brightness",
-      "weather"
-    ]
+      "weather",
+      "sky",
+      "day",
+      "sun",
+      "hot",
+      "morning"
+    ],
+    "moji": "☀"
   },
   "sunrise": {
     "unicode": "1F305",
     "unicode_alternates": [],
     "name": "sunrise",
     "shortname": ":sunrise:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25955,9 +29166,13 @@
       "view",
       "sunrise",
       "sun",
-      "morning",
       "color",
-      "sky"
+      "sky",
+      "places",
+      "travel",
+      "tropical",
+      "day",
+      "hump day"
     ],
     "moji": "🌅"
   },
@@ -25966,7 +29181,7 @@
     "unicode_alternates": [],
     "name": "sunrise over mountains",
     "shortname": ":sunrise_over_mountains:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25979,7 +29194,11 @@
       "mountain",
       "rural",
       "color",
-      "sky"
+      "sky",
+      "places",
+      "travel",
+      "day",
+      "camp"
     ],
     "moji": "🌄"
   },
@@ -25988,7 +29207,7 @@
     "unicode_alternates": [],
     "name": "surfer",
     "shortname": ":surfer:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -25998,15 +29217,19 @@
       "surfer",
       "surf",
       "wave",
-      "ocean",
       "ride",
-      "swell"
+      "swell",
+      "men",
+      "vacation",
+      "tropical",
+      "sport",
+      "diversity"
     ],
     "moji": "🏄"
   },
   "surfer_tone1": {
     "unicode": "1F3C4-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "surfer tone 1",
     "shortname": ":surfer_tone1:",
     "category": "activity",
@@ -26018,14 +29241,14 @@
       "sport",
       "surf",
       "wave",
-      "ocean",
       "ride",
       "swell"
-    ]
+    ],
+    "moji": "🏄🏻"
   },
   "surfer_tone2": {
     "unicode": "1F3C4-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "surfer tone 2",
     "shortname": ":surfer_tone2:",
     "category": "activity",
@@ -26037,14 +29260,14 @@
       "sport",
       "surf",
       "wave",
-      "ocean",
       "ride",
       "swell"
-    ]
+    ],
+    "moji": "🏄🏼"
   },
   "surfer_tone3": {
     "unicode": "1F3C4-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "surfer tone 3",
     "shortname": ":surfer_tone3:",
     "category": "activity",
@@ -26056,14 +29279,14 @@
       "sport",
       "surf",
       "wave",
-      "ocean",
       "ride",
       "swell"
-    ]
+    ],
+    "moji": "🏄🏽"
   },
   "surfer_tone4": {
     "unicode": "1F3C4-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "surfer tone 4",
     "shortname": ":surfer_tone4:",
     "category": "activity",
@@ -26075,14 +29298,14 @@
       "sport",
       "surf",
       "wave",
-      "ocean",
       "ride",
       "swell"
-    ]
+    ],
+    "moji": "🏄🏾"
   },
   "surfer_tone5": {
     "unicode": "1F3C4-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "surfer tone 5",
     "shortname": ":surfer_tone5:",
     "category": "activity",
@@ -26094,17 +29317,17 @@
       "sport",
       "surf",
       "wave",
-      "ocean",
       "ride",
       "swell"
-    ]
+    ],
+    "moji": "🏄🏿"
   },
   "sushi": {
     "unicode": "1F363",
     "unicode_alternates": [],
     "name": "sushi",
     "shortname": ":sushi:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26114,7 +29337,7 @@
       "fish",
       "raw",
       "nigiri",
-      "japanese"
+      "japan"
     ],
     "moji": "🍣"
   },
@@ -26123,7 +29346,7 @@
     "unicode_alternates": [],
     "name": "suspension railway",
     "shortname": ":suspension_railway:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26133,7 +29356,7 @@
       "railway",
       "rail",
       "train",
-      "transportation"
+      "travel"
     ],
     "moji": "🚟"
   },
@@ -26142,7 +29365,7 @@
     "unicode_alternates": [],
     "name": "face with cold sweat",
     "shortname": ":sweat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "':(",
@@ -26158,7 +29381,11 @@
       "clammy",
       "diaphoresis",
       "face",
-      "hot"
+      "hot",
+      "sad",
+      "smiley",
+      "stressed",
+      "emotion"
     ],
     "moji": "😓"
   },
@@ -26167,11 +29394,14 @@
     "unicode_alternates": [],
     "name": "splashing sweat symbol",
     "shortname": ":sweat_drops:",
-    "category": "emoticons",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "water"
+      "water",
+      "rain",
+      "stressed",
+      "sweat"
     ],
     "moji": "💦"
   },
@@ -26180,7 +29410,7 @@
     "unicode_alternates": [],
     "name": "smiling face with open mouth and cold sweat",
     "shortname": ":sweat_smile:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       "':)",
@@ -26197,7 +29427,10 @@
       "smiling",
       "cold",
       "sweat",
-      "perspiration"
+      "perspiration",
+      "smiley",
+      "workout",
+      "emotion"
     ],
     "moji": "😅"
   },
@@ -26206,7 +29439,7 @@
     "unicode_alternates": [],
     "name": "roasted sweet potato",
     "shortname": ":sweet_potato:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26216,7 +29449,8 @@
       "potato",
       "potassium",
       "roasted",
-      "roast"
+      "roast",
+      "vegetables"
     ],
     "moji": "🍠"
   },
@@ -26225,7 +29459,7 @@
     "unicode_alternates": [],
     "name": "swimmer",
     "shortname": ":swimmer:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26238,13 +29472,16 @@
       "freestyle",
       "butterfly",
       "breaststroke",
-      "backstroke"
+      "backstroke",
+      "workout",
+      "sport",
+      "diversity"
     ],
     "moji": "🏊"
   },
   "swimmer_tone1": {
     "unicode": "1F3CA-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "swimmer tone 1",
     "shortname": ":swimmer_tone1:",
     "category": "activity",
@@ -26260,11 +29497,12 @@
       "butterfly",
       "breaststroke",
       "backstroke"
-    ]
+    ],
+    "moji": "🏊🏻"
   },
   "swimmer_tone2": {
     "unicode": "1F3CA-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "swimmer tone 2",
     "shortname": ":swimmer_tone2:",
     "category": "activity",
@@ -26280,11 +29518,12 @@
       "butterfly",
       "breaststroke",
       "backstroke"
-    ]
+    ],
+    "moji": "🏊🏼"
   },
   "swimmer_tone3": {
     "unicode": "1F3CA-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "swimmer tone 3",
     "shortname": ":swimmer_tone3:",
     "category": "activity",
@@ -26300,11 +29539,12 @@
       "butterfly",
       "breaststroke",
       "backstroke"
-    ]
+    ],
+    "moji": "🏊🏽"
   },
   "swimmer_tone4": {
     "unicode": "1F3CA-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "swimmer tone 4",
     "shortname": ":swimmer_tone4:",
     "category": "activity",
@@ -26320,11 +29560,12 @@
       "butterfly",
       "breaststroke",
       "backstroke"
-    ]
+    ],
+    "moji": "🏊🏾"
   },
   "swimmer_tone5": {
     "unicode": "1F3CA-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "swimmer tone 5",
     "shortname": ":swimmer_tone5:",
     "category": "activity",
@@ -26340,30 +29581,40 @@
       "butterfly",
       "breaststroke",
       "backstroke"
-    ]
+    ],
+    "moji": "🏊🏿"
   },
   "symbols": {
     "unicode": "1F523",
     "unicode_alternates": [],
     "name": "input symbol for symbols",
     "shortname": ":symbols:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "🔣"
   },
   "synagogue": {
     "unicode": "1F54D",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "synagogue",
     "shortname": ":synagogue:",
     "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "places",
+      "religion",
+      "building",
+      "travel",
+      "vacation",
+      "condolence"
+    ],
+    "moji": "🕍"
   },
   "syringe": {
     "unicode": "1F489",
@@ -26379,19 +29630,26 @@
       "health",
       "hospital",
       "medicine",
-      "needle"
+      "needle",
+      "object",
+      "weapon"
     ],
     "moji": "💉"
   },
   "taco": {
     "unicode": "1F32E",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "taco",
     "shortname": ":taco:",
-    "category": "foods",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "food",
+      "mexican",
+      "vagina"
+    ],
+    "moji": "🌮"
   },
   "tada": {
     "unicode": "1F389",
@@ -26404,14 +29662,21 @@
     "keywords": [
       "contulations",
       "party",
-      "party",
       "popper",
       "tada",
       "celebration",
       "victory",
       "announcement",
       "climax",
-      "congratulations"
+      "congratulations",
+      "object",
+      "birthday",
+      "holidays",
+      "cheers",
+      "good",
+      "girls night",
+      "boys night",
+      "parties"
     ],
     "moji": "🎉"
   },
@@ -26420,7 +29685,7 @@
     "unicode_alternates": [],
     "name": "tanabata tree",
     "shortname": ":tanabata_tree:",
-    "category": "objects",
+    "category": "nature",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26431,7 +29696,8 @@
       "festival",
       "star",
       "wish",
-      "holiday"
+      "holiday",
+      "trees"
     ],
     "moji": "🎋"
   },
@@ -26440,7 +29706,7 @@
     "unicode_alternates": [],
     "name": "tangerine",
     "shortname": ":tangerine:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26460,7 +29726,7 @@
     ],
     "name": "taurus",
     "shortname": ":taurus:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26473,9 +29739,8 @@
       "constellation",
       "stars",
       "zodiac",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♉"
   },
@@ -26484,7 +29749,7 @@
     "unicode_alternates": [],
     "name": "taxi",
     "shortname": ":taxi:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26497,7 +29762,8 @@
       "automobile",
       "city",
       "transport",
-      "service"
+      "service",
+      "travel"
     ],
     "moji": "🚕"
   },
@@ -26506,7 +29772,7 @@
     "unicode_alternates": [],
     "name": "teacup without handle",
     "shortname": ":tea:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26517,10 +29783,13 @@
       "green",
       "tea",
       "leaf",
-      "drink",
       "teacup",
       "hot",
-      "beverage"
+      "beverage",
+      "japan",
+      "caffeine",
+      "steam",
+      "morning"
     ],
     "moji": "🍵"
   },
@@ -26537,26 +29806,12 @@
     "keywords": [
       "communication",
       "dial",
-      "technology"
+      "technology",
+      "electronics",
+      "phone"
     ],
     "moji": "☎"
   },
-  "telephone_black": {
-    "unicode": "1F57F",
-    "unicode_alternates": [],
-    "name": "black touchtone telephone",
-    "shortname": ":telephone_black:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":black_touchtone_telephone:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "dial",
-      "technology"
-    ]
-  },
   "telephone_receiver": {
     "unicode": "1F4DE",
     "unicode_alternates": [],
@@ -26568,26 +29823,12 @@
     "keywords": [
       "communication",
       "dial",
-      "technology"
+      "technology",
+      "electronics",
+      "phone"
     ],
     "moji": "📞"
   },
-  "telephone_white": {
-    "unicode": "1F57E",
-    "unicode_alternates": [],
-    "name": "white touchtone telephone",
-    "shortname": ":telephone_white:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":white_touchtone_telephone:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "communication",
-      "dial",
-      "technology"
-    ]
-  },
   "telescope": {
     "unicode": "1F52D",
     "unicode_alternates": [],
@@ -26598,13 +29839,15 @@
     "aliases_ascii": [],
     "keywords": [
       "space",
-      "stars"
+      "stars",
+      "object",
+      "science"
     ],
     "moji": "🔭"
   },
   "ten": {
     "unicode": "1F51F",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "keycap ten",
     "shortname": ":ten:",
     "category": "symbols",
@@ -26615,15 +29858,18 @@
       "blue-square",
       "numbers",
       "symbol",
-      "word"
-    ]
+      "word",
+      "number",
+      "math"
+    ],
+    "moji": "🔟"
   },
   "tennis": {
     "unicode": "1F3BE",
     "unicode_alternates": [],
     "name": "tennis racquet and ball",
     "shortname": ":tennis:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -26637,7 +29883,8 @@
       "game",
       "net",
       "court",
-      "love"
+      "love",
+      "sport"
     ],
     "moji": "🎾"
   },
@@ -26648,13 +29895,16 @@
     ],
     "name": "tent",
     "shortname": ":tent:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "camp",
       "outdoors",
-      "photo"
+      "photo",
+      "places",
+      "travel",
+      "vacation"
     ],
     "moji": "⛺"
   },
@@ -26663,16 +29913,21 @@
     "unicode_alternates": [],
     "name": "thermometer",
     "shortname": ":thermometer:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "temperature"
-    ]
+      "temperature",
+      "object",
+      "science",
+      "health",
+      "hot"
+    ],
+    "moji": "🌡"
   },
   "thermometer_face": {
     "unicode": "1F912",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "face with thermometer",
     "shortname": ":thermometer_face:",
     "category": "people",
@@ -26680,11 +29935,17 @@
       ":face_with_thermometer:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "smiley",
+      "health",
+      "sick",
+      "emotion"
+    ],
+    "moji": "🤒"
   },
   "thinking": {
     "unicode": "1F914",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thinking face",
     "shortname": ":thinking:",
     "category": "people",
@@ -26692,66 +29953,47 @@
       ":thinking_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
-  },
-  "thought_balloon": {
-    "unicode": "1F4AD",
-    "unicode_alternates": [],
-    "name": "thought balloon",
-    "shortname": ":thought_balloon:",
-    "category": "emoticons",
-    "aliases": [],
-    "aliases_ascii": [],
     "keywords": [
-      "bubble",
-      "cloud",
-      "speech",
-      "thought",
-      "balloon",
-      "comic",
-      "think",
-      "day dream",
-      "wonder"
+      "smiley",
+      "thinking",
+      "boys night"
     ],
-    "moji": "💭"
+    "moji": "🤔"
   },
-  "thought_left": {
-    "unicode": "1F5EC",
+  "third_place": {
+    "unicode": "1F949",
     "unicode_alternates": [],
-    "name": "left thought bubble",
-    "shortname": ":thought_left:",
-    "category": "objects_symbols",
+    "name": "third place medal",
+    "shortname": ":third_place:",
+    "category": "activity",
     "aliases": [
-      ":left_thought_bubble:"
+      ":third_place_medal:"
     ],
     "aliases_ascii": [],
-    "keywords": [
-      "balloon",
-      "cloud",
-      "comic",
-      "think",
-      "day dream",
-      "wonder"
-    ]
+    "keywords": [],
+    "moji": "🥉"
   },
-  "thought_right": {
-    "unicode": "1F5ED",
-    "unicode_alternates": [],
-    "name": "right thought bubble",
-    "shortname": ":thought_right:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":right_thought_bubble:"
-    ],
+  "thought_balloon": {
+    "unicode": "1F4AD",
+    "unicode_alternates": [],
+    "name": "thought balloon",
+    "shortname": ":thought_balloon:",
+    "category": "symbols",
+    "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "balloon",
+      "bubble",
       "cloud",
+      "speech",
+      "thought",
+      "balloon",
       "comic",
       "think",
       "day dream",
-      "wonder"
-    ]
+      "wonder",
+      "symbol"
+    ],
+    "moji": "💭"
   },
   "three": {
     "moji": "3️⃣",
@@ -26759,50 +30001,19 @@
     "unicode_alternates": [
       "0033-FE0F-20E3"
     ],
-    "name": "digit three",
+    "name": "keycap digit three",
     "shortname": ":three:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "3",
       "blue-square",
       "numbers",
-      "prime"
-    ]
-  },
-  "thumbs_down_reverse": {
-    "unicode": "1F593",
-    "unicode_alternates": [],
-    "name": "reversed thumbs down sign",
-    "shortname": ":thumbs_down_reverse:",
-    "category": "people",
-    "aliases": [
-      ":reversed_thumbs_down_sign:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "hand",
-      "no",
-      "-1"
-    ]
-  },
-  "thumbs_up_reverse": {
-    "unicode": "1F592",
-    "unicode_alternates": [],
-    "name": "reversed thumbs up sign",
-    "shortname": ":thumbs_up_reverse:",
-    "category": "people",
-    "aliases": [
-      ":reversed_thumbs_up_sign:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "cool",
-      "hand",
-      "like",
-      "yes",
-      "+1"
+      "prime",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "thumbsdown": {
@@ -26810,20 +30021,23 @@
     "unicode_alternates": [],
     "name": "thumbs down sign",
     "shortname": ":thumbsdown:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":-1:"
     ],
     "aliases_ascii": [],
     "keywords": [
       "hand",
-      "no"
+      "no",
+      "body",
+      "hands",
+      "diversity"
     ],
     "moji": "👎"
   },
   "thumbsdown_tone1": {
     "unicode": "1F44E-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs down sign tone 1",
     "shortname": ":thumbsdown_tone1:",
     "category": "people",
@@ -26835,11 +30049,12 @@
       "hand",
       "no",
       "-1"
-    ]
+    ],
+    "moji": "👎🏻"
   },
   "thumbsdown_tone2": {
     "unicode": "1F44E-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs down sign tone 2",
     "shortname": ":thumbsdown_tone2:",
     "category": "people",
@@ -26851,11 +30066,12 @@
       "hand",
       "no",
       "-1"
-    ]
+    ],
+    "moji": "👎🏼"
   },
   "thumbsdown_tone3": {
     "unicode": "1F44E-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs down sign tone 3",
     "shortname": ":thumbsdown_tone3:",
     "category": "people",
@@ -26867,11 +30083,12 @@
       "hand",
       "no",
       "-1"
-    ]
+    ],
+    "moji": "👎🏽"
   },
   "thumbsdown_tone4": {
     "unicode": "1F44E-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs down sign tone 4",
     "shortname": ":thumbsdown_tone4:",
     "category": "people",
@@ -26883,11 +30100,12 @@
       "hand",
       "no",
       "-1"
-    ]
+    ],
+    "moji": "👎🏾"
   },
   "thumbsdown_tone5": {
     "unicode": "1F44E-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs down sign tone 5",
     "shortname": ":thumbsdown_tone5:",
     "category": "people",
@@ -26899,14 +30117,15 @@
       "hand",
       "no",
       "-1"
-    ]
+    ],
+    "moji": "👎🏿"
   },
   "thumbsup": {
     "unicode": "1F44D",
     "unicode_alternates": [],
     "name": "thumbs up sign",
     "shortname": ":thumbsup:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [
       ":+1:"
     ],
@@ -26915,13 +30134,22 @@
       "cool",
       "hand",
       "like",
-      "yes"
+      "yes",
+      "body",
+      "hands",
+      "hi",
+      "luck",
+      "thank you",
+      "diversity",
+      "perfect",
+      "good",
+      "beautiful"
     ],
     "moji": "👍"
   },
   "thumbsup_tone1": {
     "unicode": "1F44D-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs up sign tone 1",
     "shortname": ":thumbsup_tone1:",
     "category": "people",
@@ -26935,11 +30163,12 @@
       "like",
       "yes",
       "+1"
-    ]
+    ],
+    "moji": "👍🏻"
   },
   "thumbsup_tone2": {
     "unicode": "1F44D-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs up sign tone 2",
     "shortname": ":thumbsup_tone2:",
     "category": "people",
@@ -26953,11 +30182,12 @@
       "like",
       "yes",
       "+1"
-    ]
+    ],
+    "moji": "👍🏼"
   },
   "thumbsup_tone3": {
     "unicode": "1F44D-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs up sign tone 3",
     "shortname": ":thumbsup_tone3:",
     "category": "people",
@@ -26971,11 +30201,12 @@
       "like",
       "yes",
       "+1"
-    ]
+    ],
+    "moji": "👍🏽"
   },
   "thumbsup_tone4": {
     "unicode": "1F44D-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs up sign tone 4",
     "shortname": ":thumbsup_tone4:",
     "category": "people",
@@ -26989,11 +30220,12 @@
       "like",
       "yes",
       "+1"
-    ]
+    ],
+    "moji": "👍🏾"
   },
   "thumbsup_tone5": {
     "unicode": "1F44D-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thumbs up sign tone 5",
     "shortname": ":thumbsup_tone5:",
     "category": "people",
@@ -27007,11 +30239,12 @@
       "like",
       "yes",
       "+1"
-    ]
+    ],
+    "moji": "👍🏿"
   },
   "thunder_cloud_rain": {
     "unicode": "26C8",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "thunder cloud and rain",
     "shortname": ":thunder_cloud_rain:",
     "category": "nature",
@@ -27021,15 +30254,20 @@
     "aliases_ascii": [],
     "keywords": [
       "nature",
-      "weather"
-    ]
+      "weather",
+      "sky",
+      "cloud",
+      "cold",
+      "rain"
+    ],
+    "moji": "⛈"
   },
   "ticket": {
     "unicode": "1F3AB",
     "unicode_alternates": [],
     "name": "ticket",
     "shortname": ":ticket:",
-    "category": "places",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27042,7 +30280,10 @@
       "stub",
       "admission",
       "proof",
-      "purchase"
+      "purchase",
+      "theatre",
+      "movie",
+      "parties"
     ],
     "moji": "🎫"
   },
@@ -27064,8 +30305,12 @@
       "entertainment",
       "stub",
       "proof",
-      "purchase"
-    ]
+      "purchase",
+      "theatre",
+      "movie",
+      "parties"
+    ],
+    "moji": "🎟"
   },
   "tiger": {
     "unicode": "1F42F",
@@ -27076,7 +30321,10 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "animal"
+      "animal",
+      "wildlife",
+      "roar",
+      "cat"
     ],
     "moji": "🐯"
   },
@@ -27096,13 +30344,15 @@
       "striped",
       "tony",
       "tigger",
-      "hobs"
+      "hobs",
+      "wildlife",
+      "roar"
     ],
     "moji": "🐅"
   },
   "timer": {
     "unicode": "23F2",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "timer clock",
     "shortname": ":timer:",
     "category": "objects",
@@ -27113,14 +30363,15 @@
     "keywords": [
       "object",
       "time"
-    ]
+    ],
+    "moji": "⏲"
   },
   "tired_face": {
     "unicode": "1F62B",
     "unicode_alternates": [],
     "name": "tired face",
     "shortname": ":tired_face:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27131,13 +30382,18 @@
       "whine",
       "exhausted",
       "sleepy",
-      "tired"
+      "tired",
+      "sad",
+      "smiley",
+      "emotion"
     ],
     "moji": "😫"
   },
   "tm": {
     "unicode": "2122",
-    "unicode_alternates": "2122-fe0f",
+    "unicode_alternates": [
+      "2122-FE0F"
+    ],
     "name": "trade mark sign",
     "shortname": ":tm:",
     "category": "symbols",
@@ -27149,7 +30405,8 @@
       "symbol",
       "tm",
       "word"
-    ]
+    ],
+    "moji": "™"
   },
   "toilet": {
     "unicode": "1F6BD",
@@ -27168,7 +30425,8 @@
       "porcelain",
       "waste",
       "flush",
-      "plumbing"
+      "plumbing",
+      "object"
     ],
     "moji": "🚽"
   },
@@ -27177,12 +30435,16 @@
     "unicode_alternates": [],
     "name": "tokyo tower",
     "shortname": ":tokyo_tower:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "japan",
-      "photo"
+      "photo",
+      "places",
+      "travel",
+      "vacation",
+      "eiffel tower"
     ],
     "moji": "🗼"
   },
@@ -27191,7 +30453,7 @@
     "unicode_alternates": [],
     "name": "tomato",
     "shortname": ":tomato:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27200,80 +30462,83 @@
       "nature",
       "vegetable",
       "tomato",
-      "fruit",
       "sauce",
-      "italian"
+      "italian",
+      "vegetables"
     ],
     "moji": "🍅"
   },
   "tone1": {
     "unicode": "1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "emoji modifier Fitzpatrick type-1-2",
     "shortname": ":tone1:",
     "category": "modifier",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [],
+    "moji": "🏻"
   },
   "tone2": {
     "unicode": "1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "emoji modifier Fitzpatrick type-3",
     "shortname": ":tone2:",
     "category": "modifier",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [],
+    "moji": "🏼"
   },
   "tone3": {
     "unicode": "1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "emoji modifier Fitzpatrick type-4",
     "shortname": ":tone3:",
     "category": "modifier",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [],
+    "moji": "🏽"
   },
   "tone4": {
     "unicode": "1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "emoji modifier Fitzpatrick type-5",
     "shortname": ":tone4:",
     "category": "modifier",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [],
+    "moji": "🏾"
   },
   "tone5": {
     "unicode": "1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "emoji modifier Fitzpatrick type-6",
     "shortname": ":tone5:",
     "category": "modifier",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [],
+    "moji": "🏿"
   },
   "tongue": {
     "unicode": "1F445",
     "unicode_alternates": [],
     "name": "tongue",
     "shortname": ":tongue:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "mouth",
       "playful",
       "tongue",
-      "mouth",
       "taste",
       "buds",
       "food",
       "silly",
-      "playful",
       "tease",
       "kiss",
       "french kiss",
@@ -27281,7 +30546,10 @@
       "tasty",
       "playfulness",
       "silliness",
-      "intimacy"
+      "intimacy",
+      "body",
+      "sexy",
+      "lip"
     ],
     "moji": "👅"
   },
@@ -27290,26 +30558,31 @@
     "unicode_alternates": [],
     "name": "hammer and wrench",
     "shortname": ":tools:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [
       ":hammer_and_wrench:"
     ],
     "aliases_ascii": [],
     "keywords": [
-      "tools"
-    ]
+      "tools",
+      "object",
+      "tool"
+    ],
+    "moji": "🛠"
   },
   "top": {
     "unicode": "1F51D",
     "unicode_alternates": [],
     "name": "top with upwards arrow above",
     "shortname": ":top:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "words"
+      "words",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔝"
   },
@@ -27318,7 +30591,7 @@
     "unicode_alternates": [],
     "name": "top hat",
     "shortname": ":tophat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27337,14 +30610,15 @@
       "topper",
       "london",
       "period piece",
-      "magic",
-      "magician"
+      "magician",
+      "fashion",
+      "accessories"
     ],
     "moji": "🎩"
   },
   "track_next": {
     "unicode": "23ED",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "black right-pointing double triangle with vertical bar",
     "shortname": ":track_next:",
     "category": "symbols",
@@ -27358,11 +30632,12 @@
       "next track",
       "sound",
       "symbol"
-    ]
+    ],
+    "moji": "⏭"
   },
   "track_previous": {
     "unicode": "23EE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "black left-pointing double triangle with vertical bar",
     "shortname": ":track_previous:",
     "category": "symbols",
@@ -27376,28 +30651,34 @@
       "previous track",
       "sound",
       "symbol"
-    ]
+    ],
+    "moji": "⏮"
   },
   "trackball": {
     "unicode": "1F5B2",
     "unicode_alternates": [],
     "name": "trackball",
     "shortname": ":trackball:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "input",
       "device",
-      "gadget"
-    ]
+      "gadget",
+      "electronics",
+      "work",
+      "game",
+      "office"
+    ],
+    "moji": "🖲"
   },
   "tractor": {
     "unicode": "1F69C",
     "unicode_alternates": [],
     "name": "tractor",
     "shortname": ":tractor:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27409,7 +30690,8 @@
       "farm",
       "construction",
       "machine",
-      "digger"
+      "digger",
+      "transportation"
     ],
     "moji": "🚜"
   },
@@ -27418,18 +30700,19 @@
     "unicode_alternates": [],
     "name": "horizontal traffic light",
     "shortname": ":traffic_light:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "traffic",
       "transportation",
-      "traffic",
       "light",
       "stop",
       "go",
       "yield",
-      "horizontal"
+      "horizontal",
+      "object",
+      "stop light"
     ],
     "moji": "🚥"
   },
@@ -27438,20 +30721,24 @@
     "unicode_alternates": [],
     "name": "Tram Car",
     "shortname": ":train:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "tram",
-      "rail"
-    ]
+      "rail",
+      "transportation",
+      "travel",
+      "train"
+    ],
+    "moji": "🚋"
   },
   "train2": {
     "unicode": "1F686",
     "unicode_alternates": [],
     "name": "train",
     "shortname": ":train2:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27459,66 +30746,35 @@
       "vehicle",
       "train",
       "locomotive",
-      "rail"
+      "rail",
+      "travel"
     ],
     "moji": "🚆"
   },
-  "train_diesel": {
-    "unicode": "1F6F2",
-    "unicode_alternates": [],
-    "name": "diesel locomotive",
-    "shortname": ":train_diesel:",
-    "category": "travel_places",
-    "aliases": [
-      ":diesel_locomotive:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "train",
-      "transportation",
-      "engine",
-      "rail"
-    ]
-  },
   "tram": {
     "unicode": "1F68A",
     "unicode_alternates": [],
     "name": "tram",
     "shortname": ":tram:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "transportation",
       "vehicle",
       "tram",
-      "transportation",
-      "transport"
+      "transport",
+      "travel",
+      "train"
     ],
     "moji": "🚊"
   },
-  "triangle_round": {
-    "unicode": "1F6C6",
-    "unicode_alternates": [],
-    "name": "triangle with rounded corners",
-    "shortname": ":triangle_round:",
-    "category": "objects_symbols",
-    "aliases": [
-      ":triangle_with_rounded_corners:"
-    ],
-    "aliases_ascii": [],
-    "keywords": [
-      "caution",
-      "warning",
-      "alert"
-    ]
-  },
   "triangular_flag_on_post": {
     "unicode": "1F6A9",
     "unicode_alternates": [],
     "name": "triangular flag on post",
     "shortname": ":triangular_flag_on_post:",
-    "category": "places",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27527,7 +30783,8 @@
       "flag",
       "golf",
       "post",
-      "flagpole"
+      "flagpole",
+      "object"
     ],
     "moji": "🚩"
   },
@@ -27543,7 +30800,10 @@
       "architect",
       "math",
       "sketch",
-      "stationery"
+      "stationery",
+      "object",
+      "tool",
+      "office"
     ],
     "moji": "📐"
   },
@@ -27552,12 +30812,14 @@
     "unicode_alternates": [],
     "name": "trident emblem",
     "shortname": ":trident:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "spear",
-      "weapon"
+      "weapon",
+      "object",
+      "symbol"
     ],
     "moji": "🔱"
   },
@@ -27566,7 +30828,7 @@
     "unicode_alternates": [],
     "name": "face with look of triumph",
     "shortname": ":triumph:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27575,7 +30837,11 @@
       "phew",
       "triumph",
       "steam",
-      "breath"
+      "breath",
+      "mad",
+      "smiley",
+      "angry",
+      "emotion"
     ],
     "moji": "😤"
   },
@@ -27584,7 +30850,7 @@
     "unicode_alternates": [],
     "name": "trolleybus",
     "shortname": ":trolleybus:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27595,7 +30861,7 @@
       "bus",
       "city",
       "transport",
-      "transportation"
+      "travel"
     ],
     "moji": "🚎"
   },
@@ -27604,7 +30870,7 @@
     "unicode_alternates": [],
     "name": "trophy",
     "shortname": ":trophy:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27617,11 +30883,13 @@
       "trophy",
       "first",
       "show",
-      "place",
-      "win",
       "reward",
       "achievement",
-      "medal"
+      "medal",
+      "object",
+      "game",
+      "perfect",
+      "parties"
     ],
     "moji": "🏆"
   },
@@ -27630,7 +30898,7 @@
     "unicode_alternates": [],
     "name": "tropical drink",
     "shortname": ":tropical_drink:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27642,7 +30910,9 @@
       "coconut",
       "pina",
       "fruit",
-      "umbrella"
+      "umbrella",
+      "cocktail",
+      "alcohol"
     ],
     "moji": "🍹"
   },
@@ -27656,7 +30926,8 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "swim"
+      "swim",
+      "wildlife"
     ],
     "moji": "🐠"
   },
@@ -27665,7 +30936,7 @@
     "unicode_alternates": [],
     "name": "delivery truck",
     "shortname": ":truck:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27682,16 +30953,15 @@
     "unicode_alternates": [],
     "name": "trumpet",
     "shortname": ":trumpet:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "brass",
       "music",
       "trumpet",
-      "brass",
-      "music",
-      "instrument"
+      "instrument",
+      "instruments"
     ],
     "moji": "🎺"
   },
@@ -27711,34 +30981,40 @@
       "flower",
       "bulb",
       "spring",
-      "easter"
+      "easter",
+      "vagina",
+      "girls night"
     ],
     "moji": "🌷"
   },
-  "turkey": {
-    "unicode": "1F983",
-    "unicode_alternates": "",
-    "name": "turkey",
-    "shortname": ":turkey:",
-    "category": "nature",
-    "aliases": [],
-    "aliases_ascii": [],
-    "keywords": []
-  },
-  "turned_ok_hand": {
-    "unicode": "1F58F",
+  "tumbler_glass": {
+    "unicode": "1F943",
     "unicode_alternates": [],
-    "name": "turned ok hand sign",
-    "shortname": ":turned_ok_hand:",
-    "category": "people",
+    "name": "tumbler glass",
+    "shortname": ":tumbler_glass:",
+    "category": "food",
     "aliases": [
-      ":turned_ok_hand_sign:"
+      ":whisky:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [
+      "booze"
     ],
+    "moji": "🥃"
+  },
+  "turkey": {
+    "unicode": "1F983",
+    "unicode_alternates": [],
+    "name": "turkey",
+    "shortname": ":turkey:",
+    "category": "nature",
+    "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "perfect",
-      "okay"
-    ]
+      "wildlife",
+      "animal"
+    ],
+    "moji": "🦃"
   },
   "turtle": {
     "unicode": "1F422",
@@ -27756,15 +31032,15 @@
       "tortoise",
       "chelonian",
       "reptile",
-      "slow",
       "snap",
-      "steady"
+      "steady",
+      "wildlife"
     ],
     "moji": "🐢"
   },
   "tv": {
     "unicode": "1F4FA",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "television",
     "shortname": ":tv:",
     "category": "objects",
@@ -27778,19 +31054,23 @@
       "tv",
       "entertainment",
       "object",
-      "video"
-    ]
+      "video",
+      "electronics"
+    ],
+    "moji": "📺"
   },
   "twisted_rightwards_arrows": {
     "unicode": "1F500",
     "unicode_alternates": [],
     "name": "twisted rightwards arrows",
     "shortname": ":twisted_rightwards_arrows:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "arrow",
+      "symbol"
     ],
     "moji": "🔀"
   },
@@ -27800,16 +31080,19 @@
     "unicode_alternates": [
       "0032-FE0F-20E3"
     ],
-    "name": "digit two",
+    "name": "keycap digit two",
     "shortname": ":two:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "2",
       "blue-square",
       "numbers",
-      "prime"
+      "prime",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "two_hearts": {
@@ -27817,7 +31100,7 @@
     "unicode_alternates": [],
     "name": "two hearts",
     "shortname": ":two_hearts:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27828,8 +31111,8 @@
       "heart",
       "hearts",
       "two",
-      "love",
-      "emotion"
+      "emotion",
+      "symbol"
     ],
     "moji": "💕"
   },
@@ -27838,7 +31121,7 @@
     "unicode_alternates": [],
     "name": "two men holding hands",
     "shortname": ":two_men_holding_hands:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27850,11 +31133,13 @@
       "men",
       "gay",
       "homosexual",
-      "friends",
       "hands",
       "holding",
       "team",
-      "unity"
+      "unity",
+      "people",
+      "sex",
+      "lgbt"
     ],
     "moji": "👬"
   },
@@ -27863,7 +31148,7 @@
     "unicode_alternates": [],
     "name": "two women holding hands",
     "shortname": ":two_women_holding_hands:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27875,14 +31160,17 @@
       "women",
       "hands",
       "girlfriends",
-      "friends",
       "sisters",
       "mother",
       "daughter",
       "gay",
       "homosexual",
-      "couple",
-      "unity"
+      "unity",
+      "people",
+      "sex",
+      "lgbt",
+      "lesbian",
+      "girls night"
     ],
     "moji": "👭"
   },
@@ -27891,7 +31179,7 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-5272",
     "shortname": ":u5272:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27899,7 +31187,8 @@
       "cut",
       "divide",
       "kanji",
-      "pink"
+      "pink",
+      "symbol"
     ],
     "moji": "🈹"
   },
@@ -27908,14 +31197,16 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-5408",
     "shortname": ":u5408:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "japanese",
       "join",
-      "kanji"
+      "kanji",
+      "japan",
+      "symbol"
     ],
     "moji": "🈴"
   },
@@ -27924,12 +31215,13 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-55b6",
     "shortname": ":u55b6:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "japanese",
-      "opening hours"
+      "opening hours",
+      "symbol"
     ],
     "moji": "🈺"
   },
@@ -27940,14 +31232,15 @@
     ],
     "name": "squared cjk unified ideograph-6307",
     "shortname": ":u6307:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "green-square",
       "kanji",
-      "point"
+      "point",
+      "symbol"
     ],
     "moji": "🈯"
   },
@@ -27956,7 +31249,7 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-6708",
     "shortname": ":u6708:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27964,7 +31257,8 @@
       "japanese",
       "kanji",
       "moon",
-      "orange-square"
+      "orange-square",
+      "symbol"
     ],
     "moji": "🈷"
   },
@@ -27973,14 +31267,15 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-6709",
     "shortname": ":u6709:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "have",
       "kanji",
-      "orange-square"
+      "orange-square",
+      "symbol"
     ],
     "moji": "🈶"
   },
@@ -27989,7 +31284,7 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-6e80",
     "shortname": ":u6e80:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -27997,7 +31292,9 @@
       "full",
       "japanese",
       "kanji",
-      "red-square"
+      "red-square",
+      "japan",
+      "symbol"
     ],
     "moji": "🈵"
   },
@@ -28008,7 +31305,7 @@
     ],
     "name": "squared cjk unified ideograph-7121",
     "shortname": ":u7121:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28017,7 +31314,8 @@
       "kanji",
       "no",
       "nothing",
-      "orange-square"
+      "orange-square",
+      "symbol"
     ],
     "moji": "🈚"
   },
@@ -28026,13 +31324,14 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-7533",
     "shortname": ":u7533:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "japanese",
-      "kanji"
+      "kanji",
+      "symbol"
     ],
     "moji": "🈸"
   },
@@ -28041,7 +31340,7 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-7981",
     "shortname": ":u7981:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28050,7 +31349,9 @@
       "japanese",
       "kanji",
       "limit",
-      "restricted"
+      "restricted",
+      "japan",
+      "symbol"
     ],
     "moji": "🈲"
   },
@@ -28059,14 +31360,15 @@
     "unicode_alternates": [],
     "name": "squared cjk unified ideograph-7a7a",
     "shortname": ":u7a7a:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "chinese",
       "empty",
       "japanese",
-      "kanji"
+      "kanji",
+      "symbol"
     ],
     "moji": "🈳"
   },
@@ -28082,13 +31384,15 @@
     "aliases_ascii": [],
     "keywords": [
       "rain",
-      "weather"
+      "weather",
+      "sky",
+      "cold"
     ],
     "moji": "☔"
   },
   "umbrella2": {
     "unicode": "2602",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "umbrella",
     "shortname": ":umbrella2:",
     "category": "nature",
@@ -28098,15 +31402,19 @@
       "clothing",
       "nature",
       "rain",
-      "weather"
-    ]
+      "weather",
+      "object",
+      "sky",
+      "cold"
+    ],
+    "moji": "☂"
   },
   "unamused": {
     "unicode": "1F612",
     "unicode_alternates": [],
     "name": "unamused face",
     "shortname": ":unamused:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28120,7 +31428,12 @@
       "depressed",
       "unhappy",
       "disapprove",
-      "lame"
+      "lame",
+      "sad",
+      "mad",
+      "smiley",
+      "tired",
+      "emotion"
     ],
     "moji": "😒"
   },
@@ -28129,20 +31442,21 @@
     "unicode_alternates": [],
     "name": "no one under eighteen symbol",
     "shortname": ":underage:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "18",
       "drink",
       "night",
-      "pub"
+      "pub",
+      "symbol"
     ],
     "moji": "🔞"
   },
   "unicorn": {
     "unicode": "1F984",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "unicorn face",
     "shortname": ":unicorn:",
     "category": "nature",
@@ -28150,7 +31464,10 @@
       ":unicorn_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "animal"
+    ],
+    "moji": "🦄"
   },
   "unlock": {
     "unicode": "1F513",
@@ -28162,7 +31479,9 @@
     "aliases_ascii": [],
     "keywords": [
       "privacy",
-      "security"
+      "security",
+      "object",
+      "lock"
     ],
     "moji": "🔓"
   },
@@ -28171,17 +31490,18 @@
     "unicode_alternates": [],
     "name": "squared up with exclamation mark",
     "shortname": ":up:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "blue-square"
+      "blue-square",
+      "symbol"
     ],
     "moji": "🆙"
   },
   "upside_down": {
     "unicode": "1F643",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "upside-down face",
     "shortname": ":upside_down:",
     "category": "people",
@@ -28189,11 +31509,16 @@
       ":upside_down_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "silly",
+      "smiley",
+      "sarcastic"
+    ],
+    "moji": "🙃"
   },
   "urn": {
     "unicode": "26B1",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "funeral urn",
     "shortname": ":urn:",
     "category": "objects",
@@ -28203,8 +31528,11 @@
     "aliases_ascii": [],
     "keywords": [
       "death",
-      "object"
-    ]
+      "object",
+      "dead",
+      "rip"
+    ],
+    "moji": "⚱"
   },
   "v": {
     "unicode": "270C",
@@ -28213,7 +31541,7 @@
     ],
     "name": "victory hand",
     "shortname": ":v:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28222,13 +31550,19 @@
       "ohyeah",
       "peace",
       "two",
-      "victory"
+      "victory",
+      "body",
+      "hands",
+      "hi",
+      "thank you",
+      "diversity",
+      "girls night"
     ],
     "moji": "✌"
   },
   "v_tone1": {
     "unicode": "270C-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "victory hand tone 1",
     "shortname": ":v_tone1:",
     "category": "people",
@@ -28240,11 +31574,12 @@
       "peace",
       "two",
       "v"
-    ]
+    ],
+    "moji": "✌🏻"
   },
   "v_tone2": {
     "unicode": "270C-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "victory hand tone 2",
     "shortname": ":v_tone2:",
     "category": "people",
@@ -28256,11 +31591,12 @@
       "peace",
       "two",
       "v"
-    ]
+    ],
+    "moji": "✌🏼"
   },
   "v_tone3": {
     "unicode": "270C-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "victory hand tone 3",
     "shortname": ":v_tone3:",
     "category": "people",
@@ -28272,11 +31608,12 @@
       "peace",
       "two",
       "v"
-    ]
+    ],
+    "moji": "✌🏽"
   },
   "v_tone4": {
     "unicode": "270C-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "victory hand tone 4",
     "shortname": ":v_tone4:",
     "category": "people",
@@ -28288,11 +31625,12 @@
       "peace",
       "two",
       "v"
-    ]
+    ],
+    "moji": "✌🏾"
   },
   "v_tone5": {
     "unicode": "270C-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "victory hand tone 5",
     "shortname": ":v_tone5:",
     "category": "people",
@@ -28304,14 +31642,15 @@
       "peace",
       "two",
       "v"
-    ]
+    ],
+    "moji": "✌🏿"
   },
   "vertical_traffic_light": {
     "unicode": "1F6A6",
     "unicode_alternates": [],
     "name": "vertical traffic light",
     "shortname": ":vertical_traffic_light:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28321,7 +31660,9 @@
       "stop",
       "go",
       "yield",
-      "vertical"
+      "vertical",
+      "object",
+      "stop light"
     ],
     "moji": "🚦"
   },
@@ -28336,7 +31677,8 @@
     "keywords": [
       "oldschool",
       "record",
-      "video"
+      "video",
+      "electronics"
     ],
     "moji": "📼"
   },
@@ -28345,12 +31687,13 @@
     "unicode_alternates": [],
     "name": "vibration mode",
     "shortname": ":vibration_mode:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "orange-square",
-      "phone"
+      "phone",
+      "symbol"
     ],
     "moji": "📳"
   },
@@ -28364,7 +31707,10 @@
     "aliases_ascii": [],
     "keywords": [
       "film",
-      "record"
+      "record",
+      "electronics",
+      "camera",
+      "movie"
     ],
     "moji": "📹"
   },
@@ -28373,7 +31719,7 @@
     "unicode_alternates": [],
     "name": "video game",
     "shortname": ":video_game:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28383,11 +31729,11 @@
       "play",
       "video",
       "game",
-      "console",
-      "controller",
       "nintendo",
       "xbox",
-      "playstation"
+      "playstation",
+      "electronics",
+      "boys night"
     ],
     "moji": "🎮"
   },
@@ -28396,7 +31742,7 @@
     "unicode_alternates": [],
     "name": "violin",
     "shortname": ":violin:",
-    "category": "objects",
+    "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28404,8 +31750,8 @@
       "music",
       "violin",
       "fiddle",
-      "music",
-      "instrument"
+      "instruments",
+      "sarcastic"
     ],
     "moji": "🎻"
   },
@@ -28416,7 +31762,7 @@
     ],
     "name": "virgo",
     "shortname": ":virgo:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28428,9 +31774,8 @@
       "constellation",
       "stars",
       "zodiac",
-      "sign",
-      "zodiac",
-      "horoscope"
+      "horoscope",
+      "symbol"
     ],
     "moji": "♍"
   },
@@ -28439,7 +31784,7 @@
     "unicode_alternates": [],
     "name": "volcano",
     "shortname": ":volcano:",
-    "category": "nature",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28449,31 +31794,40 @@
       "lava",
       "magma",
       "hot",
-      "explode"
+      "explode",
+      "places",
+      "tropical"
     ],
     "moji": "🌋"
   },
   "volleyball": {
     "unicode": "1F3D0",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "volleyball",
     "shortname": ":volleyball:",
     "category": "activity",
     "aliases": [],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "game",
+      "ball",
+      "sport",
+      "volleyball"
+    ],
+    "moji": "🏐"
   },
   "vs": {
     "unicode": "1F19A",
     "unicode_alternates": [],
     "name": "squared vs",
     "shortname": ":vs:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "orange-square",
-      "words"
+      "words",
+      "symbol"
     ],
     "moji": "🆚"
   },
@@ -28493,12 +31847,17 @@
       "leonard",
       "nimoy",
       "star trek",
-      "live long"
-    ]
+      "live long",
+      "body",
+      "hands",
+      "hi",
+      "diversity"
+    ],
+    "moji": "🖖"
   },
   "vulcan_tone1": {
     "unicode": "1F596-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with part between middle and ring fingers tone 1",
     "shortname": ":vulcan_tone1:",
     "category": "people",
@@ -28513,11 +31872,12 @@
       "nimoy",
       "star trek",
       "live long"
-    ]
+    ],
+    "moji": "🖖🏻"
   },
   "vulcan_tone2": {
     "unicode": "1F596-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with part between middle and ring fingers tone 2",
     "shortname": ":vulcan_tone2:",
     "category": "people",
@@ -28532,11 +31892,12 @@
       "nimoy",
       "star trek",
       "live long"
-    ]
+    ],
+    "moji": "🖖🏼"
   },
   "vulcan_tone3": {
     "unicode": "1F596-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with part between middle and ring fingers tone 3",
     "shortname": ":vulcan_tone3:",
     "category": "people",
@@ -28551,11 +31912,12 @@
       "nimoy",
       "star trek",
       "live long"
-    ]
+    ],
+    "moji": "🖖🏽"
   },
   "vulcan_tone4": {
     "unicode": "1F596-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with part between middle and ring fingers tone 4",
     "shortname": ":vulcan_tone4:",
     "category": "people",
@@ -28570,11 +31932,12 @@
       "nimoy",
       "star trek",
       "live long"
-    ]
+    ],
+    "moji": "🖖🏾"
   },
   "vulcan_tone5": {
     "unicode": "1F596-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "raised hand with part between middle and ring fingers tone 5",
     "shortname": ":vulcan_tone5:",
     "category": "people",
@@ -28589,14 +31952,15 @@
       "nimoy",
       "star trek",
       "live long"
-    ]
+    ],
+    "moji": "🖖🏿"
   },
   "walking": {
     "unicode": "1F6B6",
     "unicode_alternates": [],
     "name": "pedestrian",
     "shortname": ":walking:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28607,13 +31971,16 @@
       "stroll",
       "stride",
       "foot",
-      "feet"
+      "feet",
+      "people",
+      "men",
+      "diversity"
     ],
     "moji": "🚶"
   },
   "walking_tone1": {
     "unicode": "1F6B6-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pedestrian tone 1",
     "shortname": ":walking_tone1:",
     "category": "people",
@@ -28626,11 +31993,12 @@
       "stride",
       "hiking",
       "hike"
-    ]
+    ],
+    "moji": "🚶🏻"
   },
   "walking_tone2": {
     "unicode": "1F6B6-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pedestrian tone 2",
     "shortname": ":walking_tone2:",
     "category": "people",
@@ -28643,11 +32011,12 @@
       "stride",
       "hiking",
       "hike"
-    ]
+    ],
+    "moji": "🚶🏼"
   },
   "walking_tone3": {
     "unicode": "1F6B6-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pedestrian tone 3",
     "shortname": ":walking_tone3:",
     "category": "people",
@@ -28660,11 +32029,12 @@
       "stride",
       "hiking",
       "hike"
-    ]
+    ],
+    "moji": "🚶🏽"
   },
   "walking_tone4": {
     "unicode": "1F6B6-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pedestrian tone 4",
     "shortname": ":walking_tone4:",
     "category": "people",
@@ -28677,11 +32047,12 @@
       "stride",
       "hiking",
       "hike"
-    ]
+    ],
+    "moji": "🚶🏾"
   },
   "walking_tone5": {
     "unicode": "1F6B6-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "pedestrian tone 5",
     "shortname": ":walking_tone5:",
     "category": "people",
@@ -28694,7 +32065,8 @@
       "stride",
       "hiking",
       "hike"
-    ]
+    ],
+    "moji": "🚶🏿"
   },
   "waning_crescent_moon": {
     "unicode": "1F318",
@@ -28712,7 +32084,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌘"
   },
@@ -28732,7 +32105,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌖"
   },
@@ -28743,12 +32117,14 @@
     ],
     "name": "warning sign",
     "shortname": ":warning:",
-    "category": "places",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "exclamation",
-      "wip"
+      "wip",
+      "symbol",
+      "punctuation"
     ],
     "moji": "⚠"
   },
@@ -28757,14 +32133,17 @@
     "unicode_alternates": [],
     "name": "wastebasket",
     "shortname": ":wastebasket:",
-    "category": "objects_symbols",
+    "category": "objects",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "trash",
       "garbage",
-      "dispose"
-    ]
+      "dispose",
+      "object",
+      "work"
+    ],
+    "moji": "🗑"
   },
   "watch": {
     "unicode": "231A",
@@ -28778,7 +32157,8 @@
     "aliases_ascii": [],
     "keywords": [
       "accessories",
-      "time"
+      "time",
+      "electronics"
     ],
     "moji": "⌚"
   },
@@ -28800,16 +32180,83 @@
       "asia",
       "bovine",
       "milk",
-      "dairy"
+      "dairy",
+      "wildlife"
     ],
     "moji": "🐃"
   },
+  "water_polo": {
+    "unicode": "1F93D",
+    "unicode_alternates": [],
+    "name": "water polo",
+    "shortname": ":water_polo:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽"
+  },
+  "water_polo_tone1": {
+    "unicode": "1F93D-1F3FB",
+    "unicode_alternates": [],
+    "name": "water polo tone 1",
+    "shortname": ":water_polo_tone1:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽🏻"
+  },
+  "water_polo_tone2": {
+    "unicode": "1F93D-1F3FC",
+    "unicode_alternates": [],
+    "name": "water polo tone 2",
+    "shortname": ":water_polo_tone2:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽🏼"
+  },
+  "water_polo_tone3": {
+    "unicode": "1F93D-1F3FD",
+    "unicode_alternates": [],
+    "name": "water polo tone 3",
+    "shortname": ":water_polo_tone3:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽🏽"
+  },
+  "water_polo_tone4": {
+    "unicode": "1F93D-1F3FE",
+    "unicode_alternates": [],
+    "name": "water polo tone 4",
+    "shortname": ":water_polo_tone4:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽🏾"
+  },
+  "water_polo_tone5": {
+    "unicode": "1F93D-1F3FF",
+    "unicode_alternates": [],
+    "name": "water polo tone 5",
+    "shortname": ":water_polo_tone5:",
+    "category": "activity",
+    "aliases": [],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤽🏿"
+  },
   "watermelon": {
     "unicode": "1F349",
     "unicode_alternates": [],
     "name": "watermelon",
     "shortname": ":watermelon:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28818,7 +32265,6 @@
       "melon",
       "watermelon",
       "summer",
-      "fruit",
       "large"
     ],
     "moji": "🍉"
@@ -28828,7 +32274,7 @@
     "unicode_alternates": [],
     "name": "waving hand sign",
     "shortname": ":wave:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28836,13 +32282,16 @@
       "gesture",
       "goodbye",
       "hands",
-      "solong"
+      "solong",
+      "body",
+      "hi",
+      "diversity"
     ],
     "moji": "👋"
   },
   "wave_tone1": {
     "unicode": "1F44B-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "waving hand sign tone 1",
     "shortname": ":wave_tone1:",
     "category": "people",
@@ -28855,11 +32304,12 @@
       "solong",
       "hi",
       "wave"
-    ]
+    ],
+    "moji": "👋🏻"
   },
   "wave_tone2": {
     "unicode": "1F44B-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "waving hand sign tone 2",
     "shortname": ":wave_tone2:",
     "category": "people",
@@ -28872,11 +32322,12 @@
       "solong",
       "hi",
       "wave"
-    ]
+    ],
+    "moji": "👋🏼"
   },
   "wave_tone3": {
     "unicode": "1F44B-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "waving hand sign tone 3",
     "shortname": ":wave_tone3:",
     "category": "people",
@@ -28889,11 +32340,12 @@
       "solong",
       "hi",
       "wave"
-    ]
+    ],
+    "moji": "👋🏽"
   },
   "wave_tone4": {
     "unicode": "1F44B-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "waving hand sign tone 4",
     "shortname": ":wave_tone4:",
     "category": "people",
@@ -28906,11 +32358,12 @@
       "solong",
       "hi",
       "wave"
-    ]
+    ],
+    "moji": "👋🏾"
   },
   "wave_tone5": {
     "unicode": "1F44B-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "waving hand sign tone 5",
     "shortname": ":wave_tone5:",
     "category": "people",
@@ -28923,19 +32376,21 @@
       "solong",
       "hi",
       "wave"
-    ]
+    ],
+    "moji": "👋🏿"
   },
   "wavy_dash": {
     "unicode": "3030",
     "unicode_alternates": [],
     "name": "wavy dash",
     "shortname": ":wavy_dash:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "draw",
-      "line"
+      "line",
+      "symbol"
     ],
     "moji": "〰"
   },
@@ -28954,7 +32409,8 @@
       "sky",
       "night",
       "cheese",
-      "phase"
+      "phase",
+      "space"
     ],
     "moji": "🌒"
   },
@@ -28967,7 +32423,10 @@
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "nature"
+      "nature",
+      "space",
+      "sky",
+      "moon"
     ],
     "moji": "🌔"
   },
@@ -28976,7 +32435,7 @@
     "unicode_alternates": [],
     "name": "water closet",
     "shortname": ":wc:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -28985,13 +32444,13 @@
       "toilet",
       "water",
       "closet",
-      "toilet",
       "bathroom",
       "throne",
       "porcelain",
       "waste",
       "flush",
-      "plumbing"
+      "plumbing",
+      "symbol"
     ],
     "moji": "🚾"
   },
@@ -29000,7 +32459,7 @@
     "unicode_alternates": [],
     "name": "weary face",
     "shortname": ":weary:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29010,13 +32469,14 @@
       "sleepy",
       "tired",
       "weary",
-      "sleepy",
-      "tired",
       "tiredness",
       "study",
       "finals",
       "school",
-      "exhausted"
+      "exhausted",
+      "smiley",
+      "stressed",
+      "emotion"
     ],
     "moji": "😩"
   },
@@ -29025,7 +32485,7 @@
     "unicode_alternates": [],
     "name": "wedding",
     "shortname": ":wedding:",
-    "category": "places",
+    "category": "travel",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29035,7 +32495,11 @@
       "groom",
       "like",
       "love",
-      "marriage"
+      "marriage",
+      "places",
+      "wedding",
+      "building",
+      "parties"
     ],
     "moji": "💒"
   },
@@ -29051,7 +32515,10 @@
       "animal",
       "nature",
       "ocean",
-      "sea"
+      "sea",
+      "wildlife",
+      "tropical",
+      "whales"
     ],
     "moji": "🐳"
   },
@@ -29073,13 +32540,16 @@
       "bloated",
       "fat",
       "large",
-      "massive"
+      "massive",
+      "wildlife",
+      "tropical",
+      "whales"
     ],
     "moji": "🐋"
   },
   "wheel_of_dharma": {
     "unicode": "2638",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "wheel of dharma",
     "shortname": ":wheel_of_dharma:",
     "category": "symbols",
@@ -29089,7 +32559,8 @@
       "buddhist",
       "religion",
       "symbol"
-    ]
+    ],
+    "moji": "☸"
   },
   "wheelchair": {
     "unicode": "267F",
@@ -29098,12 +32569,13 @@
     ],
     "name": "wheelchair symbol",
     "shortname": ":wheelchair:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
-      "disabled"
+      "disabled",
+      "symbol"
     ],
     "moji": "♿"
   },
@@ -29112,13 +32584,14 @@
     "unicode_alternates": [],
     "name": "white heavy check mark",
     "shortname": ":white_check_mark:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "agree",
       "green-square",
-      "ok"
+      "ok",
+      "symbol"
     ],
     "moji": "✅"
   },
@@ -29129,11 +32602,14 @@
     ],
     "name": "medium white circle",
     "shortname": ":white_circle:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "circle"
     ],
     "moji": "⚪"
   },
@@ -29142,7 +32618,7 @@
     "unicode_alternates": [],
     "name": "white flower",
     "shortname": ":white_flower:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29158,7 +32634,8 @@
       "homework",
       "student",
       "assignment",
-      "praise"
+      "praise",
+      "symbol"
     ],
     "moji": "💮"
   },
@@ -29169,11 +32646,14 @@
     ],
     "name": "white large square",
     "shortname": ":white_large_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "⬜"
   },
@@ -29184,11 +32664,14 @@
     ],
     "name": "white medium small square",
     "shortname": ":white_medium_small_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "◽"
   },
@@ -29199,11 +32682,14 @@
     ],
     "name": "white medium square",
     "shortname": ":white_medium_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "◻"
   },
@@ -29214,11 +32700,14 @@
     ],
     "name": "white small square",
     "shortname": ":white_small_square:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "▫"
   },
@@ -29227,17 +32716,20 @@
     "unicode_alternates": [],
     "name": "white square button",
     "shortname": ":white_square_button:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
-      "shape"
+      "shape",
+      "shapes",
+      "symbol",
+      "square"
     ],
     "moji": "🔳"
   },
   "white_sun_cloud": {
     "unicode": "1F325",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white sun behind cloud",
     "shortname": ":white_sun_cloud:",
     "category": "nature",
@@ -29247,12 +32739,17 @@
     "aliases_ascii": [],
     "keywords": [
       "nature",
-      "weather"
-    ]
+      "weather",
+      "sky",
+      "cloud",
+      "cold",
+      "sun"
+    ],
+    "moji": "🌥"
   },
   "white_sun_rain_cloud": {
     "unicode": "1F326",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white sun behind cloud with rain",
     "shortname": ":white_sun_rain_cloud:",
     "category": "nature",
@@ -29262,12 +32759,18 @@
     "aliases_ascii": [],
     "keywords": [
       "nature",
-      "weather"
-    ]
+      "weather",
+      "sky",
+      "cloud",
+      "cold",
+      "rain",
+      "sun"
+    ],
+    "moji": "🌦"
   },
   "white_sun_small_cloud": {
     "unicode": "1F324",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "white sun with small cloud",
     "shortname": ":white_sun_small_cloud:",
     "category": "nature",
@@ -29277,8 +32780,25 @@
     "aliases_ascii": [],
     "keywords": [
       "nature",
-      "weather"
-    ]
+      "weather",
+      "sky",
+      "cloud",
+      "sun"
+    ],
+    "moji": "🌤"
+  },
+  "wilted_rose": {
+    "unicode": "1F940",
+    "unicode_alternates": [],
+    "name": "wilted flower",
+    "shortname": ":wilted_rose:",
+    "category": "nature",
+    "aliases": [
+      ":wilted_flower:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🥀"
   },
   "wind_blowing_face": {
     "unicode": "1F32C",
@@ -29290,8 +32810,11 @@
     "aliases_ascii": [],
     "keywords": [
       "mother",
-      "nature"
-    ]
+      "nature",
+      "weather",
+      "cold"
+    ],
+    "moji": "🌬"
   },
   "wind_chime": {
     "unicode": "1F390",
@@ -29314,7 +32837,9 @@
       "soothing",
       "protective",
       "spiritual",
-      "sound"
+      "sound",
+      "object",
+      "japan"
     ],
     "moji": "🎐"
   },
@@ -29323,7 +32848,7 @@
     "unicode_alternates": [],
     "name": "wine glass",
     "shortname": ":wine_glass:",
-    "category": "objects",
+    "category": "food",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29338,7 +32863,10 @@
       "grapes",
       "tasting",
       "wine",
-      "winery"
+      "winery",
+      "italian",
+      "girls night",
+      "parties"
     ],
     "moji": "🍷"
   },
@@ -29347,7 +32875,7 @@
     "unicode_alternates": [],
     "name": "winking face",
     "shortname": ":wink:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [
       ";)",
@@ -29367,7 +32895,10 @@
       "wink",
       "winking",
       "friendly",
-      "joke"
+      "joke",
+      "silly",
+      "smiley",
+      "emotion"
     ],
     "moji": "😉"
   },
@@ -29381,7 +32912,9 @@
     "aliases_ascii": [],
     "keywords": [
       "animal",
-      "nature"
+      "nature",
+      "wildlife",
+      "roar"
     ],
     "moji": "🐺"
   },
@@ -29390,18 +32923,25 @@
     "unicode_alternates": [],
     "name": "woman",
     "shortname": ":woman:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "female",
-      "girls"
+      "girls",
+      "people",
+      "women",
+      "sex",
+      "diversity",
+      "feminist",
+      "selfie",
+      "girls night"
     ],
     "moji": "👩"
   },
   "woman_tone1": {
     "unicode": "1F469-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "woman tone 1",
     "shortname": ":woman_tone1:",
     "category": "people",
@@ -29411,11 +32951,12 @@
       "female",
       "girl",
       "lady"
-    ]
+    ],
+    "moji": "👩🏻"
   },
   "woman_tone2": {
     "unicode": "1F469-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "woman tone 2",
     "shortname": ":woman_tone2:",
     "category": "people",
@@ -29425,11 +32966,12 @@
       "female",
       "girl",
       "lady"
-    ]
+    ],
+    "moji": "👩🏼"
   },
   "woman_tone3": {
     "unicode": "1F469-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "woman tone 3",
     "shortname": ":woman_tone3:",
     "category": "people",
@@ -29439,11 +32981,12 @@
       "female",
       "girl",
       "lady"
-    ]
+    ],
+    "moji": "👩🏽"
   },
   "woman_tone4": {
     "unicode": "1F469-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "woman tone 4",
     "shortname": ":woman_tone4:",
     "category": "people",
@@ -29453,11 +32996,12 @@
       "female",
       "girl",
       "lady"
-    ]
+    ],
+    "moji": "👩🏾"
   },
   "woman_tone5": {
     "unicode": "1F469-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "woman tone 5",
     "shortname": ":woman_tone5:",
     "category": "people",
@@ -29467,14 +33011,15 @@
       "female",
       "girl",
       "lady"
-    ]
+    ],
+    "moji": "👩🏿"
   },
   "womans_clothes": {
     "unicode": "1F45A",
     "unicode_alternates": [],
     "name": "womans clothes",
     "shortname": ":womans_clothes:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29490,7 +33035,8 @@
       "shopping",
       "shop",
       "dressing",
-      "dressed"
+      "dressed",
+      "women"
     ],
     "moji": "👚"
   },
@@ -29499,13 +33045,14 @@
     "unicode_alternates": [],
     "name": "womans hat",
     "shortname": ":womans_hat:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "accessories",
       "fashion",
-      "female"
+      "female",
+      "women"
     ],
     "moji": "👒"
   },
@@ -29514,7 +33061,7 @@
     "unicode_alternates": [],
     "name": "womens symbol",
     "shortname": ":womens:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29525,7 +33072,8 @@
       "sign",
       "girl",
       "female",
-      "avatar"
+      "avatar",
+      "symbol"
     ],
     "moji": "🚺"
   },
@@ -29534,7 +33082,7 @@
     "unicode_alternates": [],
     "name": "worried face",
     "shortname": ":worried:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29544,8 +33092,10 @@
       "worried",
       "anxious",
       "distressed",
-      "nervous",
-      "tense"
+      "tense",
+      "sad",
+      "smiley",
+      "emotion"
     ],
     "moji": "😟"
   },
@@ -29560,30 +33110,111 @@
     "keywords": [
       "diy",
       "ikea",
-      "tools"
+      "tools",
+      "object",
+      "tool"
     ],
     "moji": "🔧"
   },
-  "writing_hand": {
-    "unicode": "1F58E",
+  "wrestlers": {
+    "unicode": "1F93C",
     "unicode_alternates": [],
-    "name": "left writing hand",
-    "shortname": ":writing_hand:",
-    "category": "people",
+    "name": "wrestlers",
+    "shortname": ":wrestlers:",
+    "category": "activity",
+    "aliases": [
+      ":wrestling:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼"
+  },
+  "wrestlers_tone1": {
+    "unicode": "1F93C-1F3FB",
+    "unicode_alternates": [],
+    "name": "wrestlers tone 1",
+    "shortname": ":wrestlers_tone1:",
+    "category": "activity",
+    "aliases": [
+      ":wrestling_tone1:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼🏻"
+  },
+  "wrestlers_tone2": {
+    "unicode": "1F93C-1F3FC",
+    "unicode_alternates": [],
+    "name": "wrestlers tone 2",
+    "shortname": ":wrestlers_tone2:",
+    "category": "activity",
+    "aliases": [
+      ":wrestling_tone2:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼🏼"
+  },
+  "wrestlers_tone3": {
+    "unicode": "1F93C-1F3FD",
+    "unicode_alternates": [],
+    "name": "wrestlers tone 3",
+    "shortname": ":wrestlers_tone3:",
+    "category": "activity",
+    "aliases": [
+      ":wrestling_tone3:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼🏽"
+  },
+  "wrestlers_tone4": {
+    "unicode": "1F93C-1F3FE",
+    "unicode_alternates": [],
+    "name": "wrestlers tone 4",
+    "shortname": ":wrestlers_tone4:",
+    "category": "activity",
+    "aliases": [
+      ":wrestling_tone4:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼🏾"
+  },
+  "wrestlers_tone5": {
+    "unicode": "1F93C-1F3FF",
+    "unicode_alternates": [],
+    "name": "wrestlers tone 5",
+    "shortname": ":wrestlers_tone5:",
+    "category": "activity",
     "aliases": [
-      ":left_writing_hand:"
+      ":wrestling_tone5:"
+    ],
+    "aliases_ascii": [],
+    "keywords": [],
+    "moji": "🤼🏿"
+  },
+  "writing_hand": {
+    "unicode": "270D",
+    "unicode_alternates": [
+      "270D-FE0F"
     ],
+    "name": "writing hand",
+    "shortname": ":writing_hand:",
+    "category": "people",
+    "aliases": [],
     "aliases_ascii": [],
     "keywords": [
+      "body",
+      "hands",
       "write",
-      "sign",
-      "signature",
-      "draw"
-    ]
+      "diversity"
+    ],
+    "moji": "✍"
   },
   "writing_hand_tone1": {
     "unicode": "270D-1F3FB",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "writing hand tone 1",
     "shortname": ":writing_hand_tone1:",
     "category": "people",
@@ -29594,11 +33225,12 @@
       "sign",
       "signature",
       "draw"
-    ]
+    ],
+    "moji": "✍🏻"
   },
   "writing_hand_tone2": {
     "unicode": "270D-1F3FC",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "writing hand tone 2",
     "shortname": ":writing_hand_tone2:",
     "category": "people",
@@ -29609,11 +33241,12 @@
       "sign",
       "signature",
       "draw"
-    ]
+    ],
+    "moji": "✍🏼"
   },
   "writing_hand_tone3": {
     "unicode": "270D-1F3FD",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "writing hand tone 3",
     "shortname": ":writing_hand_tone3:",
     "category": "people",
@@ -29624,11 +33257,12 @@
       "sign",
       "signature",
       "draw"
-    ]
+    ],
+    "moji": "✍🏽"
   },
   "writing_hand_tone4": {
     "unicode": "270D-1F3FE",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "writing hand tone 4",
     "shortname": ":writing_hand_tone4:",
     "category": "people",
@@ -29639,11 +33273,12 @@
       "sign",
       "signature",
       "draw"
-    ]
+    ],
+    "moji": "✍🏾"
   },
   "writing_hand_tone5": {
     "unicode": "270D-1F3FF",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "writing hand tone 5",
     "shortname": ":writing_hand_tone5:",
     "category": "people",
@@ -29654,20 +33289,23 @@
       "sign",
       "signature",
       "draw"
-    ]
+    ],
+    "moji": "✍🏿"
   },
   "x": {
     "unicode": "274C",
     "unicode_alternates": [],
     "name": "cross mark",
     "shortname": ":x:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "delete",
       "no",
-      "remove"
+      "remove",
+      "symbol",
+      "sol"
     ],
     "moji": "❌"
   },
@@ -29676,7 +33314,7 @@
     "unicode_alternates": [],
     "name": "yellow heart",
     "shortname": ":yellow_heart:",
-    "category": "emoticons",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29687,7 +33325,6 @@
       "yellow",
       "gold",
       "heart",
-      "love",
       "friendship",
       "happy",
       "happiness",
@@ -29696,7 +33333,8 @@
       "respectful",
       "honest",
       "caring",
-      "selfless"
+      "selfless",
+      "symbol"
     ],
     "moji": "💛"
   },
@@ -29715,10 +33353,7 @@
       "money",
       "yen",
       "japan",
-      "japanese",
       "banknote",
-      "money",
-      "currency",
       "paper",
       "cash",
       "bill"
@@ -29727,7 +33362,7 @@
   },
   "yin_yang": {
     "unicode": "262F",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "yin yang",
     "shortname": ":yin_yang:",
     "category": "symbols",
@@ -29739,14 +33374,15 @@
       "symbol",
       "tao",
       "taoist"
-    ]
+    ],
+    "moji": "☯"
   },
   "yum": {
     "unicode": "1F60B",
     "unicode_alternates": [],
     "name": "face savouring delicious food",
     "shortname": ":yum:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
@@ -29762,7 +33398,12 @@
       "yummy",
       "yum",
       "tasty",
-      "savory"
+      "savory",
+      "silly",
+      "smiley",
+      "emotion",
+      "sarcastic",
+      "good"
     ],
     "moji": "😋"
   },
@@ -29779,7 +33420,9 @@
     "keywords": [
       "lightning bolt",
       "thunder",
-      "weather"
+      "weather",
+      "sky",
+      "diarrhea"
     ],
     "moji": "⚡"
   },
@@ -29789,20 +33432,23 @@
     "unicode_alternates": [
       "0030-FE0F-20E3"
     ],
-    "name": "digit zero",
+    "name": "keycap digit zero",
     "shortname": ":zero:",
-    "category": "other",
+    "category": "symbols",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "blue-square",
       "null",
-      "numbers"
+      "numbers",
+      "number",
+      "math",
+      "symbol"
     ]
   },
   "zipper_mouth": {
     "unicode": "1F910",
-    "unicode_alternates": "",
+    "unicode_alternates": [],
     "name": "zipper-mouth face",
     "shortname": ":zipper_mouth:",
     "category": "people",
@@ -29810,19 +33456,24 @@
       ":zipper_mouth_face:"
     ],
     "aliases_ascii": [],
-    "keywords": []
+    "keywords": [
+      "mad",
+      "smiley"
+    ],
+    "moji": "🤐"
   },
   "zzz": {
     "unicode": "1F4A4",
     "unicode_alternates": [],
     "name": "sleeping symbol",
     "shortname": ":zzz:",
-    "category": "emoticons",
+    "category": "people",
     "aliases": [],
     "aliases_ascii": [],
     "keywords": [
       "sleepy",
-      "tired"
+      "tired",
+      "goodnight"
     ],
     "moji": "💤"
   }
diff --git a/generator_templates/active_record/migration/create_table_migration.rb b/generator_templates/active_record/migration/create_table_migration.rb
index 27acc75dcc433d9980e92871bdc035af80044c3a..aad8626a7206346bbafac4a5a3bd7cfbc0d9fdfd 100644
--- a/generator_templates/active_record/migration/create_table_migration.rb
+++ b/generator_templates/active_record/migration/create_table_migration.rb
@@ -4,6 +4,14 @@
 class <%= migration_class_name %> < ActiveRecord::Migration
   include Gitlab::Database::MigrationHelpers
 
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
   # When using the methods "add_concurrent_index" or "add_column_with_default"
   # you must disable the use of transactions as these methods can not run in an
   # existing transaction. When using "add_concurrent_index" make sure that this
diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb
index 06bdea113670ab16b0370f222bd02ab5fc42b3d1..825bc8bdf6154c9aad05ba7571a21c247d84f0ba 100644
--- a/generator_templates/active_record/migration/migration.rb
+++ b/generator_templates/active_record/migration/migration.rb
@@ -4,6 +4,14 @@
 class <%= migration_class_name %> < ActiveRecord::Migration
   include Gitlab::Database::MigrationHelpers
 
+  # Set this constant to true if this migration requires downtime.
+  DOWNTIME = false
+
+  # When a migration requires downtime you **must** uncomment the following
+  # constant and define a short and easy to understand explanation as to why the
+  # migration requires downtime.
+  # DOWNTIME_REASON = ''
+
   # When using the methods "add_concurrent_index" or "add_column_with_default"
   # you must disable the use of transactions as these methods can not run in an
   # existing transaction. When using "add_concurrent_index" make sure that this
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 3d7d67510a83ac12c19114400671e8b5f2c70e26..bd16806892b6acb50e5cf5aa922259a6b0c16f91 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -7,6 +7,10 @@ module API
       rack_response({ 'message' => '404 Not found' }.to_json, 404)
     end
 
+    rescue_from Grape::Exceptions::ValidationErrors do |e|
+      error!({ messages: e.full_messages }, 400)
+    end
+
     rescue_from :all do |exception|
       # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
       # why is this not wrapped in something reusable?
@@ -32,6 +36,7 @@ module API
     mount ::API::CommitStatuses
     mount ::API::Commits
     mount ::API::DeployKeys
+    mount ::API::Environments
     mount ::API::Files
     mount ::API::GroupMembers
     mount ::API::Groups
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index d467eb9d4742c31a7a07c4783a860d3cc18b85b3..a77afe634f626d96737d9fbba5be9ede41400fbe 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -15,7 +15,8 @@ module API
       #   GET /projects/:id/repository/branches
       get ":id/repository/branches" do
         branches = user_project.repository.branches.sort_by(&:name)
-        present branches, with: Entities::RepoObject, project: user_project
+
+        present branches, with: Entities::RepoBranch, project: user_project
       end
 
       # Get a single branch
@@ -28,14 +29,21 @@ module API
       get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
         @branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
         not_found!("Branch") unless @branch
-        present @branch, with: Entities::RepoObject, project: user_project
+
+        present @branch, with: Entities::RepoBranch, project: user_project
       end
 
       # Protect a single branch
       #
+      # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}`
+      # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility),
+      # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`.
+      #
       # Parameters:
       #   id (required) - The ID of a project
       #   branch (required) - The name of the branch
+      #   developers_can_push (optional) - Flag if developers can push to that branch
+      #   developers_can_merge (optional) - Flag if developers can merge to that branch
       # Example Request:
       #   PUT /projects/:id/repository/branches/:branch/protect
       put ':id/repository/branches/:branch/protect',
@@ -43,11 +51,41 @@ module API
         authorize_admin_project
 
         @branch = user_project.repository.find_branch(params[:branch])
-        not_found!("Branch") unless @branch
+        not_found!('Branch') unless @branch
         protected_branch = user_project.protected_branches.find_by(name: @branch.name)
-        user_project.protected_branches.create(name: @branch.name) unless protected_branch
 
-        present @branch, with: Entities::RepoObject, project: user_project
+        developers_can_merge = to_boolean(params[:developers_can_merge])
+        developers_can_push = to_boolean(params[:developers_can_push])
+
+        protected_branch_params = {
+          name: @branch.name
+        }
+
+        unless developers_can_merge.nil?
+          protected_branch_params.merge!({
+            merge_access_level_attributes: {
+              access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+            }
+          })
+        end
+
+        unless developers_can_push.nil?
+          protected_branch_params.merge!({
+            push_access_level_attributes: {
+              access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+            }
+          })
+        end
+
+        if protected_branch
+          service = ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch_params)
+          service.execute(protected_branch)
+        else
+          service = ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params)
+          service.execute
+        end
+
+        present @branch, with: Entities::RepoBranch, project: user_project
       end
 
       # Unprotect a single branch
@@ -66,7 +104,7 @@ module API
         protected_branch = user_project.protected_branches.find_by(name: @branch.name)
         protected_branch.destroy if protected_branch
 
-        present @branch, with: Entities::RepoObject, project: user_project
+        present @branch, with: Entities::RepoBranch, project: user_project
       end
 
       # Create branch
@@ -84,7 +122,7 @@ module API
 
         if result[:status] == :success
           present result[:branch],
-                  with: Entities::RepoObject,
+                  with: Entities::RepoBranch,
                   project: user_project
         else
           render_api_error!(result[:message], 400)
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index d36047acd1f65c03a11d39a67c1ded42f20ebd2d..be5a3484ec8a8143f8fef3a64fb90a312567863c 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -52,8 +52,7 @@ module API
       get ':id/builds/:build_id' do
         authorize_read_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
 
         present build, with: Entities::Build,
                        user_can_download_artifacts: can?(current_user, :read_build, user_project)
@@ -69,18 +68,27 @@ module API
       get ':id/builds/:build_id/artifacts' do
         authorize_read_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
 
-        artifacts_file = build.artifacts_file
+        present_artifacts!(build.artifacts_file)
+      end
 
-        unless artifacts_file.file_storage?
-          return redirect_to build.artifacts_file.url
-        end
+      # Download the artifacts file from ref_name and job
+      #
+      # Parameters:
+      #   id (required) - The ID of a project
+      #   ref_name (required) - The ref from repository
+      #   job (required) - The name for the build
+      # Example Request:
+      #   GET /projects/:id/builds/artifacts/:ref_name/download?job=name
+      get ':id/builds/artifacts/:ref_name/download',
+        requirements: { ref_name: /.+/ } do
+        authorize_read_builds!
 
-        return not_found! unless artifacts_file.exists?
+        builds = user_project.latest_successful_builds_for(params[:ref_name])
+        latest_build = builds.find_by!(name: params[:job])
 
-        present_file!(artifacts_file.path, artifacts_file.filename)
+        present_artifacts!(latest_build.artifacts_file)
       end
 
       # Get a trace of a specific build of a project
@@ -97,8 +105,7 @@ module API
       get ':id/builds/:build_id/trace' do
         authorize_read_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
 
         header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
         content_type 'text/plain'
@@ -118,8 +125,7 @@ module API
       post ':id/builds/:build_id/cancel' do
         authorize_update_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
 
         build.cancel
 
@@ -137,8 +143,7 @@ module API
       post ':id/builds/:build_id/retry' do
         authorize_update_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
         return forbidden!('Build is not retryable') unless build.retryable?
 
         build = Ci::Build.retry(build, current_user)
@@ -157,8 +162,7 @@ module API
       post ':id/builds/:build_id/erase' do
         authorize_update_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build
+        build = get_build!(params[:build_id])
         return forbidden!('Build is not erasable!') unless build.erasable?
 
         build.erase(erased_by: current_user)
@@ -176,8 +180,8 @@ module API
       post ':id/builds/:build_id/artifacts/keep' do
         authorize_update_builds!
 
-        build = get_build(params[:build_id])
-        return not_found!(build) unless build && build.artifacts?
+        build = get_build!(params[:build_id])
+        return not_found!(build) unless build.artifacts?
 
         build.keep_artifacts!
 
@@ -192,6 +196,20 @@ module API
         user_project.builds.find_by(id: id.to_i)
       end
 
+      def get_build!(id)
+        get_build(id) || not_found!
+      end
+
+      def present_artifacts!(artifacts_file)
+        if !artifacts_file.file_storage?
+          redirect_to(build.artifacts_file.url)
+        elsif artifacts_file.exists?
+          present_file!(artifacts_file.path, artifacts_file.filename)
+        else
+          not_found!
+        end
+      end
+
       def filter_builds(builds, scope)
         return builds if scope.nil? || scope.empty?
 
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index acb4812b5cf5ab22d65e5da017c2ed08e912cf30..4df6ca8333ecc10cf05166dd9263e05b8075f4b8 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -24,7 +24,7 @@ module API
 
         pipelines = user_project.pipelines.where(sha: params[:sha])
         statuses = ::CommitStatus.where(pipeline: pipelines)
-        statuses = statuses.latest unless parse_boolean(params[:all])
+        statuses = statuses.latest unless to_boolean(params[:all])
         statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
         statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
         statuses = statuses.where(name: params[:name]) if params[:name].present?
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 06eb7756841fe4ef5540bfef81480cb6fd2ddda5..5c570b5e5ca375e1718a721576444116d44fd017 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -2,74 +2,87 @@ module API
   # Projects API
   class DeployKeys < Grape::API
     before { authenticate! }
-    before { authorize_admin_project }
+
+    get "deploy_keys" do
+      authenticated_as_admin!
+
+      keys = DeployKey.all
+      present keys, with: Entities::SSHKey
+    end
 
     resource :projects do
-      # Get a specific project's keys
-      #
-      # Example Request:
-      #   GET /projects/:id/keys
-      get ":id/keys" do
-        present user_project.deploy_keys, with: Entities::SSHKey
-      end
+      before { authorize_admin_project }
 
-      # Get single key owned by currently authenticated user
+      # Routing "projects/:id/keys/..." is DEPRECATED and WILL BE REMOVED in version 9.0
+      # Use "projects/:id/deploy_keys/..." instead.
       #
-      # Example Request:
-      #   GET /projects/:id/keys/:id
-      get ":id/keys/:key_id" do
-        key = user_project.deploy_keys.find params[:key_id]
-        present key, with: Entities::SSHKey
-      end
+      %w(keys deploy_keys).each do |path|
+        # Get a specific project's deploy keys
+        #
+        # Example Request:
+        #   GET /projects/:id/deploy_keys
+        get ":id/#{path}" do
+          present user_project.deploy_keys, with: Entities::SSHKey
+        end
 
-      # Add new ssh key to currently authenticated user
-      # If deploy key already exists - it will be joined to project
-      # but only if original one was is accessible by same user
-      #
-      # Parameters:
-      #   key (required) - New SSH Key
-      #   title (required) - New SSH Key's title
-      # Example Request:
-      #   POST /projects/:id/keys
-      post ":id/keys" do
-        attrs = attributes_for_keys [:title, :key]
+        # Get single deploy key owned by currently authenticated user
+        #
+        # Example Request:
+        #   GET /projects/:id/deploy_keys/:key_id
+        get ":id/#{path}/:key_id" do
+          key = user_project.deploy_keys.find params[:key_id]
+          present key, with: Entities::SSHKey
+        end
 
-        if attrs[:key].present?
-          attrs[:key].strip!
+        # Add new deploy key to currently authenticated user
+        # If deploy key already exists - it will be joined to project
+        # but only if original one was accessible by same user
+        #
+        # Parameters:
+        #   key (required) - New deploy Key
+        #   title (required) - New deploy Key's title
+        # Example Request:
+        #   POST /projects/:id/deploy_keys
+        post ":id/#{path}" do
+          attrs = attributes_for_keys [:title, :key]
 
-          # check if key already exist in project
-          key = user_project.deploy_keys.find_by(key: attrs[:key])
-          if key
-            present key, with: Entities::SSHKey
-            return
+          if attrs[:key].present?
+            attrs[:key].strip!
+
+            # check if key already exist in project
+            key = user_project.deploy_keys.find_by(key: attrs[:key])
+            if key
+              present key, with: Entities::SSHKey
+              next
+            end
+
+            # Check for available deploy keys in other projects
+            key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
+            if key
+              user_project.deploy_keys << key
+              present key, with: Entities::SSHKey
+              next
+            end
           end
 
-          # Check for available deploy keys in other projects
-          key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
-          if key
-            user_project.deploy_keys << key
+          key = DeployKey.new attrs
+
+          if key.valid? && user_project.deploy_keys << key
             present key, with: Entities::SSHKey
-            return
+          else
+            render_validation_error!(key)
           end
         end
 
-        key = DeployKey.new attrs
-
-        if key.valid? && user_project.deploy_keys << key
-          present key, with: Entities::SSHKey
-        else
-          render_validation_error!(key)
+        # Delete existing deploy key of currently authenticated user
+        #
+        # Example Request:
+        #   DELETE /projects/:id/deploy_keys/:key_id
+        delete ":id/#{path}/:key_id" do
+          key = user_project.deploy_keys.find params[:key_id]
+          key.destroy
         end
       end
-
-      # Delete existed ssh key of currently authenticated user
-      #
-      # Example Request:
-      #   DELETE /projects/:id/keys/:id
-      delete ":id/keys/:key_id" do
-        key = user_project.deploy_keys.find params[:key_id]
-        key.destroy
-      end
     end
   end
 end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 3c79a00eb8c89093ef1c952b5d00df3319939e55..3e21b7a0b8a617b8af6a851c8f175ff2aa0aceee 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -114,21 +114,25 @@ module API
       end
     end
 
-    class RepoObject < Grape::Entity
+    class RepoBranch < Grape::Entity
       expose :name
 
-      expose :commit do |repo_obj, options|
-        if repo_obj.respond_to?(:commit)
-          repo_obj.commit
-        elsif options[:project]
-          options[:project].repository.commit(repo_obj.target)
-        end
+      expose :commit do |repo_branch, options|
+        options[:project].repository.commit(repo_branch.target)
       end
 
-      expose :protected do |repo, options|
-        if options[:project]
-          options[:project].protected_branch? repo.name
-        end
+      expose :protected do |repo_branch, options|
+        options[:project].protected_branch? repo_branch.name
+      end
+
+      expose :developers_can_push do |repo_branch, options|
+        project = options[:project]
+        project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_level.access_level == Gitlab::Access::DEVELOPER }
+      end
+
+      expose :developers_can_merge do |repo_branch, options|
+        project = options[:project]
+        project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_level.access_level == Gitlab::Access::DEVELOPER }
       end
     end
 
@@ -147,8 +151,13 @@ module API
       expose :safe_message, as: :message
     end
 
+    class RepoCommitStats < Grape::Entity
+      expose :additions, :deletions, :total
+    end
+
     class RepoCommitDetail < RepoCommit
       expose :parent_ids, :committed_date, :authored_date
+      expose :stats, using: Entities::RepoCommitStats
       expose :status
     end
 
@@ -412,7 +421,9 @@ module API
       expose :default_project_visibility
       expose :default_snippet_visibility
       expose :default_group_visibility
-      expose :restricted_signup_domains
+      expose :domain_whitelist
+      expose :domain_blacklist_enabled
+      expose :domain_blacklist
       expose :user_oauth_applications
       expose :after_sign_out_path
       expose :container_registry_token_expire_delay
@@ -425,27 +436,14 @@ module API
     end
 
     class RepoTag < Grape::Entity
-      expose :name
-      expose :message do |repo_obj, _options|
-        if repo_obj.respond_to?(:message)
-          repo_obj.message
-        else
-          nil
-        end
-      end
+      expose :name, :message
 
-      expose :commit do |repo_obj, options|
-        if repo_obj.respond_to?(:commit)
-          repo_obj.commit
-        elsif options[:project]
-          options[:project].repository.commit(repo_obj.target)
-        end
+      expose :commit do |repo_tag, options|
+        options[:project].repository.commit(repo_tag.target)
       end
 
-      expose :release, using: Entities::Release do |repo_obj, options|
-        if options[:project]
-          options[:project].releases.find_by(tag: repo_obj.name)
-        end
+      expose :release, using: Entities::Release do |repo_tag, options|
+        options[:project].releases.find_by(tag: repo_tag.name)
       end
     end
 
@@ -498,6 +496,10 @@ module API
       expose :key, :value
     end
 
+    class Environment < Grape::Entity
+      expose :id, :name, :external_url
+    end
+
     class RepoLicense < Grape::Entity
       expose :key, :name, :nickname
       expose :featured, as: :popular
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..819f80d836590786e95c3e0d36496c52a1bad700
--- /dev/null
+++ b/lib/api/environments.rb
@@ -0,0 +1,83 @@
+module API
+  # Environments RESTfull API endpoints
+  class Environments < Grape::API
+    before { authenticate! }
+
+    params do
+      requires :id, type: String, desc: 'The project ID'
+    end
+    resource :projects do
+      desc 'Get all environments of the project' do
+        detail 'This feature was introduced in GitLab 8.11.'
+        success Entities::Environment
+      end
+      params do
+        optional :page,     type: Integer, desc: 'Page number of the current request'
+        optional :per_page, type: Integer, desc: 'Number of items per page'
+      end
+      get ':id/environments' do
+        authorize! :read_environment, user_project
+
+        present paginate(user_project.environments), with: Entities::Environment
+      end
+
+      desc 'Creates a new environment' do
+        detail 'This feature was introduced in GitLab 8.11.'
+        success Entities::Environment
+      end
+      params do
+        requires :name,           type: String,   desc: 'The name of the environment to be created'
+        optional :external_url,   type: String,   desc: 'URL on which this deployment is viewable'
+      end
+      post ':id/environments' do
+        authorize! :create_environment, user_project
+
+        create_params = declared(params, include_parent_namespaces: false).to_h
+        environment = user_project.environments.create(create_params)
+
+        if environment.persisted?
+          present environment, with: Entities::Environment
+        else
+          render_validation_error!(environment)
+        end
+      end
+
+      desc 'Updates an existing environment' do
+        detail 'This feature was introduced in GitLab 8.11.'
+        success Entities::Environment
+      end
+      params do
+        requires :environment_id, type: Integer,  desc: 'The environment ID'
+        optional :name,           type: String,   desc: 'The new environment name'
+        optional :external_url,   type: String,   desc: 'The new URL on which this deployment is viewable'
+      end
+      put ':id/environments/:environment_id' do
+        authorize! :update_environment, user_project
+
+        environment = user_project.environments.find(params[:environment_id])
+        
+        update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h
+        if environment.update(update_params)
+          present environment, with: Entities::Environment
+        else
+          render_validation_error!(environment)
+        end
+      end
+
+      desc 'Deletes an existing environment' do
+        detail 'This feature was introduced in GitLab 8.11.'
+        success Entities::Environment
+      end
+      params do
+        requires :environment_id, type: Integer,  desc: 'The environment ID'
+      end
+      delete ':id/environments/:environment_id' do
+        authorize! :update_environment, user_project
+
+        environment = user_project.environments.find(params[:environment_id])
+
+        present environment.destroy, with: Entities::Environment
+      end
+    end
+  end
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 73557cf7db634a6e31d26428f75b3d06c80c347d..130509cdad6ca3123d0d5d6c0c093e8b24de18e3 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -5,8 +5,11 @@ module API
     SUDO_HEADER = "HTTP_SUDO"
     SUDO_PARAM = :sudo
 
-    def parse_boolean(value)
-      [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
+    def to_boolean(value)
+      return true if value =~ /^(true|t|yes|y|1|on)$/i
+      return false if value =~ /^(false|f|no|n|0|off)$/i
+
+      nil
     end
 
     def find_user_by_private_token
@@ -290,7 +293,7 @@ module API
     def filter_projects(projects)
       # If the archived parameter is passed, limit results accordingly
       if params[:archived].present?
-        projects = projects.where(archived: parse_boolean(params[:archived]))
+        projects = projects.where(archived: to_boolean(params[:archived]))
       end
 
       if params[:search].present?
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c588103e517eb05894ba94b7d0d9292786004a09..c4d3134da6c3e6c1031809ee3a1feb1689e86f36 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -21,17 +21,6 @@ module API
       def filter_issues_milestone(issues, milestone)
         issues.includes(:milestone).where('milestones.title' => milestone)
       end
-
-      def create_spam_log(project, current_user, attrs)
-        params = attrs.merge({
-          source_ip: client_ip(env),
-          user_agent: user_agent(env),
-          noteable_type: 'Issue',
-          via_api: true
-        })
-
-        ::CreateSpamLogService.new(project, current_user, params).execute
-      end
     end
 
     resource :issues do
@@ -168,15 +157,13 @@ module API
         end
 
         project = user_project
-        text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
 
-        if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
-          create_spam_log(project, current_user, attrs)
+        issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
+
+        if issue.spam?
           render_api_error!({ error: 'Spam detected' }, 400)
         end
 
-        issue = ::Issues::CreateService.new(project, current_user, attrs).execute
-
         if issue.valid?
           # Find or create labels and attach to issue. Labels are valid because
           # we already checked its name, so there can't be an error here
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4fcdf8968c91c51ffe676f234260d2c4fb9cb810..2b685621da9a0c4443e30962ae59a98b490f395e 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -242,7 +242,7 @@ module API
             should_remove_source_branch: params[:should_remove_source_branch]
           }
 
-          if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active?
+          if to_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active?
             ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
               execute(merge_request)
           else
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 6d2a6f3946c45644a95ac073fdd355104517b1ff..8fed7db8803992a2c5169cd24592d0113e87d5ea 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -8,7 +8,7 @@ module API
         def map_public_to_visibility_level(attrs)
           publik = attrs.delete(:public)
           if publik.present? && !attrs[:visibility_level].present?
-            publik = parse_boolean(publik)
+            publik = to_boolean(publik)
             # Since setting the public attribute to private could mean either
             # private or internal, use the more conservative option, private.
             attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 2a6bfa98ca4c666c002dcf12fd2fecfbd4252f58..26c24c3baffcf67b71630651c5f80cfefd9bfe2d 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -75,7 +75,7 @@ module API
         todos = find_todos
         todos.each(&:done)
 
-        present paginate(Kaminari.paginate_array(todos)), with: Entities::Todo, current_user: current_user
+        todos.length
       end
     end
   end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index b9773f98d752a7c8ea4a077c387f8b3776a1bd57..1f5917b81275367420a109aa7cd29fad214d9a67 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -54,10 +54,10 @@ module Backup
         # Move repos dir to 'repositories.old' dir
         bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
         FileUtils.mv(path, bk_repos_path)
+        # This is expected from gitlab:check
+        FileUtils.mkdir_p(path, mode: 2770)
       end
 
-      FileUtils.mkdir_p(repos_path)
-
       Project.find_each(batch_size: 1000) do |project|
         $progress.print " * #{project.path_with_namespace} ... "
 
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index ae7d31cf191ae63ed9937f39004f900ba6e46f92..2492b5213ac4230a86bcbabee83246e3ad6b4217 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -38,6 +38,11 @@ module Banzai
         end
       end
 
+      # Build a regexp that matches all valid :emoji: names.
+      def self.emoji_pattern
+        @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+      end
+
       private
 
       def emoji_url(name)
@@ -59,11 +64,6 @@ module Banzai
         ActionController::Base.helpers.url_to_image(image)
       end
 
-      # Build a regexp that matches all valid :emoji: names.
-      def self.emoji_pattern
-        @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
-      end
-
       def emoji_pattern
         self.class.emoji_pattern
       end
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
index 9b209533a895879b9de2ac75568c985697f48032..ff580ec68f8dfac6ef87723e6570a579aae039ad 100644
--- a/lib/banzai/filter/markdown_filter.rb
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -12,7 +12,12 @@ module Banzai
         html
       end
 
-      private
+      def self.renderer
+        @renderer ||= begin
+          renderer = Redcarpet::Render::HTML.new
+          Redcarpet::Markdown.new(renderer, redcarpet_options)
+        end
+      end
 
       def self.redcarpet_options
         # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@@ -28,12 +33,7 @@ module Banzai
         }.freeze
       end
 
-      def self.renderer
-        @renderer ||= begin
-          renderer = Redcarpet::Render::HTML.new
-          Redcarpet::Markdown.new(renderer, redcarpet_options)
-        end
-      end
+      private_class_method :redcarpet_options
     end
   end
 end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index c78da40460764bb4c3ccea18c8189bea840813d3..5b73fc8fceec1ba2835e5ad3afb27e354ce9dc8a 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -20,7 +20,7 @@ module Banzai
           process_link_attr el.attribute('href')
         end
 
-        doc.search('img').each do |el|
+        doc.css('img, video').each do |el|
           process_link_attr el.attribute('src')
         end
 
@@ -87,10 +87,13 @@ module Banzai
       def build_relative_path(path, request_path)
         return request_path if path.empty?
         return path unless request_path
+        return path[1..-1] if path.start_with?('/')
 
         parts = request_path.split('/')
         parts.pop if uri_type(request_path) != :tree
 
+        path.sub!(%r{^\./}, '')
+
         while path.start_with?('../')
           parts.pop
           path.sub!('../', '')
@@ -112,8 +115,7 @@ module Banzai
       end
 
       def current_commit
-        @current_commit ||= context[:commit] ||
-          ref ? repository.commit(ref) : repository.head_commit
+        @current_commit ||= context[:commit] || ref ? repository.commit(ref) : repository.head_commit
       end
 
       def relative_url_root
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd8b9a6f0cc642d6f2f30bd270acdc4c65e79c63
--- /dev/null
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -0,0 +1,59 @@
+module Banzai
+  module Filter
+
+    # Find every image that isn't already wrapped in an `a` tag, and that has
+    # a `src` attribute ending with a video extension, add a new video node and
+    # a "Download" link in the case the video cannot be played.
+    class VideoLinkFilter < HTML::Pipeline::Filter
+
+      def call
+        doc.xpath(query).each do |el|
+          el.replace(video_node(doc, el))
+        end
+
+        doc
+      end
+
+      private
+
+      def query
+        @query ||= begin
+          src_query = UploaderHelper::VIDEO_EXT.map do |ext|
+            "'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
+          end
+
+          "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
+        end
+      end
+
+      def video_node(doc, element)
+        container = doc.document.create_element(
+          'div',
+          class: 'video-container'
+        )
+
+        video = doc.document.create_element(
+          'video',
+          src: element['src'],
+          width: '400',
+          controls: true,
+          'data-setup' => '{}')
+
+        link = doc.document.create_element(
+          'a',
+          element['title'] || element['alt'],
+          href: element['src'],
+          target: '_blank',
+          title: "Download '#{element['title'] || element['alt']}'")
+        download_paragraph = doc.document.create_element('p')
+        download_paragraph.children = link
+
+        container.add_child(video)
+        container.add_child(download_paragraph)
+
+        container
+      end
+    end
+
+  end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b27ecf3c923a95ab116abdc75474ef8ca01e7ccc..8d94b199c6680194be3b8ff2b53e37f63de0d865 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
           Filter::SanitizationFilter,
 
           Filter::UploadLinkFilter,
+          Filter::VideoLinkFilter,
           Filter::ImageLinkFilter,
           Filter::EmojiFilter,
           Filter::TableOfContentsFilter,
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index bf366962aef318b2f9ee96756d0c5b504163c581..b26a41a1f3b7df73b976768e756ccaef226cccb3 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -2,11 +2,11 @@ module Banzai
   # Extract possible GFM references from an arbitrary String for further processing.
   class ReferenceExtractor
     def initialize
-      @texts = []
+      @texts_and_contexts = []
     end
 
     def analyze(text, context = {})
-      @texts << Renderer.render(text, context)
+      @texts_and_contexts << { text: text, context: context }
     end
 
     def references(type, project, current_user = nil)
@@ -21,9 +21,10 @@ module Banzai
     def html_documents
       # This ensures that we don't memoize anything until we have a number of
       # text blobs to parse.
-      return [] if @texts.empty?
+      return [] if @texts_and_contexts.empty?
 
-      @html_documents ||= @texts.map { |html| Nokogiri::HTML.fragment(html) }
+      @html_documents ||= Renderer.cache_collection_render(@texts_and_contexts)
+        .map { |html| Nokogiri::HTML.fragment(html) }
     end
   end
 end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index f306079d833dfc231c51061ed853adda6fa9f6f5..6c20dec5734d1fbab69654c199ad377cde90ecff 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -9,10 +9,11 @@ module Banzai
 
         issues = issues_for_nodes(nodes)
 
-        nodes.select do |node|
-          issue = issue_for_node(issues, node)
+        readable_issues = Ability.
+          issues_readable_by_user(issues.values, user).to_set
 
-          issue ? can?(user, :read_issue, issue) : false
+        nodes.select do |node|
+          readable_issues.include?(issue_for_node(issues, node))
         end
       end
 
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 910687a7b6a33c0db68cfcd4d796701e801d01b9..a4ae27eefd81012547a9bae0952ab74c71cf6721 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -1,5 +1,7 @@
 module Banzai
   module Renderer
+    extend self
+
     # Convert a Markdown String into an HTML-safe String of HTML
     #
     # Note that while the returned HTML will have been sanitized of dangerous
@@ -14,7 +16,7 @@ module Banzai
     # context  - Hash of context options passed to our HTML Pipeline
     #
     # Returns an HTML-safe String
-    def self.render(text, context = {})
+    def render(text, context = {})
       cache_key = context.delete(:cache_key)
       cache_key = full_cache_key(cache_key, context[:pipeline])
 
@@ -52,7 +54,7 @@ module Banzai
     #    texts_and_contexts
     #    => [{ text: '### Hello',
     #          context: { cache_key: [note, :note] } }]
-    def self.cache_collection_render(texts_and_contexts)
+    def cache_collection_render(texts_and_contexts)
       items_collection = texts_and_contexts.each_with_index do |item, index|
         context = item[:context]
         cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
@@ -81,7 +83,7 @@ module Banzai
       items_collection.map { |item| item[:rendered] }
     end
 
-    def self.render_result(text, context = {})
+    def render_result(text, context = {})
       text = Pipeline[:pre_process].to_html(text, context) if text
 
       Pipeline[context[:pipeline]].call(text, context)
@@ -100,7 +102,7 @@ module Banzai
     #            :user      - User object
     #
     # Returns an HTML-safe String
-    def self.post_process(html, context)
+    def post_process(html, context)
       context = Pipeline[context[:pipeline]].transform_context(context)
 
       pipeline = Pipeline[:post_process]
@@ -113,7 +115,7 @@ module Banzai
 
     private
 
-    def self.cacheless_render(text, context = {})
+    def cacheless_render(text, context = {})
       Gitlab::Metrics.measure(:banzai_cacheless_render) do
         result = render_result(text, context)
 
@@ -126,7 +128,7 @@ module Banzai
       end
     end
 
-    def self.full_cache_key(cache_key, pipeline_name)
+    def full_cache_key(cache_key, pipeline_name)
       return unless cache_key
       ["banzai", *cache_key, pipeline_name || :full]
     end
@@ -134,7 +136,7 @@ module Banzai
     # To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
     # Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key
     # method.
-    def self.full_cache_multi_key(cache_key, pipeline_name)
+    def full_cache_multi_key(cache_key, pipeline_name)
       return unless cache_key
       Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
     end
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 1d7126a432df01f854a8af361d2219564c1eda3a..3decc3b1a269e609064157e46adec6c1281a4fcf 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -1,5 +1,37 @@
 module Ci
   module Charts
+    module DailyInterval
+      def grouped_count(query)
+        query.
+          group("DATE(#{Ci::Build.table_name}.created_at)").
+          count(:created_at).
+          transform_keys { |date| date.strftime(@format) }
+      end
+
+      def interval_step
+        @interval_step ||= 1.day
+      end
+    end
+
+    module MonthlyInterval
+      def grouped_count(query)
+        if Gitlab::Database.postgresql?
+          query.
+            group("to_char(#{Ci::Build.table_name}.created_at, '01 Month YYYY')").
+            count(:created_at).
+            transform_keys(&:squish)
+        else
+          query.
+            group("DATE_FORMAT(#{Ci::Build.table_name}.created_at, '01 %M %Y')").
+            count(:created_at)
+        end
+      end
+
+      def interval_step
+        @interval_step ||= 1.month
+      end
+    end
+
     class Chart
       attr_reader :labels, :total, :success, :project, :build_times
 
@@ -13,47 +45,59 @@ module Ci
         collect
       end
 
-      def push(from, to, format)
-        @labels << from.strftime(format)
-        @total << project.builds.
-          where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from).
-          count(:all)
-        @success << project.builds.
-          where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from).
-          success.count(:all)
+      def collect
+        query = project.builds.
+          where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", @to, @from)
+
+        totals_count  = grouped_count(query)
+        success_count = grouped_count(query.success)
+
+        current = @from
+        while current < @to
+          label = current.strftime(@format)
+
+          @labels  << label
+          @total   << (totals_count[label] || 0)
+          @success << (success_count[label] || 0)
+
+          current += interval_step
+        end
       end
     end
 
     class YearChart < Chart
-      def collect
-        13.times do |i|
-          start_month = (Date.today.years_ago(1) + i.month).beginning_of_month
-          end_month = start_month.end_of_month
+      include MonthlyInterval
 
-          push(start_month, end_month, "%d %B %Y")
-        end
+      def initialize(*)
+        @to     = Date.today.end_of_month
+        @from   = @to.years_ago(1).beginning_of_month
+        @format = '%d %B %Y'
+
+        super
       end
     end
 
     class MonthChart < Chart
-      def collect
-        30.times do |i|
-          start_day = Date.today - 30.days + i.days
-          end_day = Date.today - 30.days + i.day + 1.day
+      include DailyInterval
 
-          push(start_day, end_day, "%d %B")
-        end
+      def initialize(*)
+        @to     = Date.today
+        @from   = @to - 30.days
+        @format = '%d %B'
+
+        super
       end
     end
 
     class WeekChart < Chart
-      def collect
-        7.times do |i|
-          start_day = Date.today - 7.days + i.days
-          end_day = Date.today - 7.days + i.day + 1.day
+      include DailyInterval
 
-          push(start_day, end_day, "%d %B")
-        end
+      def initialize(*)
+        @to     = Date.today
+        @from   = @to - 7.days
+        @format = '%d %B'
+
+        super
       end
     end
 
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index a48dc542b14c35e87bfa1809bb70ba872d78c2ea..a2e8bd22a525786bc0bac9aa8a62303131e4598b 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -4,21 +4,11 @@ module Ci
 
     include Gitlab::Ci::Config::Node::LegacyValidationHelpers
 
-    DEFAULT_STAGE = 'test'
-    ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
-    ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
-                        :allow_failure, :type, :stage, :when, :artifacts, :cache,
-                        :dependencies, :before_script, :after_script, :variables,
-                        :environment]
-    ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
-    ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in]
-
     attr_reader :path, :cache, :stages
 
     def initialize(config, path = nil)
       @ci_config = Gitlab::Ci::Config.new(config)
       @config = @ci_config.to_hash
-
       @path = path
 
       unless @ci_config.valid?
@@ -26,7 +16,6 @@ module Ci
       end
 
       initial_parsing
-      validate!
     rescue Gitlab::Ci::Config::Loader::FormatError => e
       raise ValidationError, e.message
     end
@@ -44,52 +33,25 @@ module Ci
     end
 
     def builds_for_ref(ref, tag = false, trigger_request = nil)
-      jobs_for_ref(ref, tag, trigger_request).map do |name, job|
-        build_job(name, job)
+      jobs_for_ref(ref, tag, trigger_request).map do |name, _|
+        build_attributes(name)
       end
     end
 
     def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
-      jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, job|
-        build_job(name, job)
+      jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
+        build_attributes(name)
       end
     end
 
     def builds
-      @jobs.map do |name, job|
-        build_job(name, job)
+      @jobs.map do |name, _|
+        build_attributes(name)
       end
     end
 
-    private
-
-    def initial_parsing
-      @before_script = @ci_config.before_script
-      @image = @ci_config.image
-      @after_script = @ci_config.after_script
-      @services = @ci_config.services
-      @variables = @ci_config.variables
-      @stages = @ci_config.stages
-      @cache = @ci_config.cache
-
-      @jobs = {}
-
-      @config.except!(*ALLOWED_YAML_KEYS)
-      @config.each { |name, param| add_job(name, param) }
-
-      raise ValidationError, "Please define at least one job" if @jobs.none?
-    end
-
-    def add_job(name, job)
-      return if name.to_s.start_with?('.')
-
-      raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
-
-      stage = job[:stage] || job[:type] || DEFAULT_STAGE
-      @jobs[name] = { stage: stage }.merge(job)
-    end
-
-    def build_job(name, job)
+    def build_attributes(name)
+      job = @jobs[name.to_sym] || {}
       {
         stage_idx: @stages.index(job[:stage]),
         stage: job[:stage],
@@ -100,7 +62,7 @@ module Ci
         #  - before script should be a concatenated command
         commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
         tag_list: job[:tags] || [],
-        name: name,
+        name: job[:name],
         allow_failure: job[:allow_failure] || false,
         when: job[:when] || 'on_success',
         environment: job[:environment],
@@ -116,172 +78,59 @@ module Ci
       }
     end
 
-    def yaml_variables(name)
-      variables = global_variables.merge(job_variables(name))
-      variables.map do |key, value|
-        { key: key, value: value, public: true }
-      end
-    end
-
-    def global_variables
-      @variables || {}
-    end
+    private
 
-    def job_variables(name)
-      job = @jobs[name.to_sym]
-      return {} unless job
+    def initial_parsing
+      ##
+      # Global config
+      #
+      @before_script = @ci_config.before_script
+      @image = @ci_config.image
+      @after_script = @ci_config.after_script
+      @services = @ci_config.services
+      @variables = @ci_config.variables
+      @stages = @ci_config.stages
+      @cache = @ci_config.cache
 
-      job[:variables] || {}
-    end
+      ##
+      # Jobs
+      #
+      @jobs = @ci_config.jobs
 
-    def validate!
       @jobs.each do |name, job|
-        validate_job!(name, job)
-      end
+        # logical validation for job
 
-      true
-    end
-
-    def validate_job!(name, job)
-      validate_job_name!(name)
-      validate_job_keys!(name, job)
-      validate_job_types!(name, job)
-      validate_job_script!(name, job)
-
-      validate_job_stage!(name, job) if job[:stage]
-      validate_job_variables!(name, job) if job[:variables]
-      validate_job_cache!(name, job) if job[:cache]
-      validate_job_artifacts!(name, job) if job[:artifacts]
-      validate_job_dependencies!(name, job) if job[:dependencies]
-    end
-
-    def validate_job_name!(name)
-      if name.blank? || !validate_string(name)
-        raise ValidationError, "job name should be non-empty string"
-      end
-    end
-
-    def validate_job_keys!(name, job)
-      job.keys.each do |key|
-        unless ALLOWED_JOB_KEYS.include? key
-          raise ValidationError, "#{name} job: unknown parameter #{key}"
-        end
+        validate_job_stage!(name, job)
+        validate_job_dependencies!(name, job)
       end
     end
 
-    def validate_job_types!(name, job)
-      if job[:image] && !validate_string(job[:image])
-        raise ValidationError, "#{name} job: image should be a string"
-      end
-
-      if job[:services] && !validate_array_of_strings(job[:services])
-        raise ValidationError, "#{name} job: services should be an array of strings"
-      end
-
-      if job[:tags] && !validate_array_of_strings(job[:tags])
-        raise ValidationError, "#{name} job: tags parameter should be an array of strings"
-      end
-
-      if job[:only] && !validate_array_of_strings_or_regexps(job[:only])
-        raise ValidationError, "#{name} job: only parameter should be an array of strings or regexps"
-      end
-
-      if job[:except] && !validate_array_of_strings_or_regexps(job[:except])
-        raise ValidationError, "#{name} job: except parameter should be an array of strings or regexps"
-      end
-
-      if job[:allow_failure] && !validate_boolean(job[:allow_failure])
-        raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
-      end
-
-      if job[:when] && !job[:when].in?(%w[on_success on_failure always])
-        raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
-      end
+    def yaml_variables(name)
+      variables = (@variables || {})
+        .merge(job_variables(name))
 
-      if job[:environment] && !validate_environment(job[:environment])
-        raise ValidationError, "#{name} job: environment parameter #{Gitlab::Regex.environment_name_regex_message}"
+      variables.map do |key, value|
+        { key: key, value: value, public: true }
       end
     end
 
-    def validate_job_script!(name, job)
-      if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
-        raise ValidationError, "#{name} job: script should be a string or an array of a strings"
-      end
-
-      if job[:before_script] && !validate_array_of_strings(job[:before_script])
-        raise ValidationError, "#{name} job: before_script should be an array of strings"
-      end
+    def job_variables(name)
+      job = @jobs[name.to_sym]
+      return {} unless job
 
-      if job[:after_script] && !validate_array_of_strings(job[:after_script])
-        raise ValidationError, "#{name} job: after_script should be an array of strings"
-      end
+      job[:variables] || {}
     end
 
     def validate_job_stage!(name, job)
+      return unless job[:stage]
+
       unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
         raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
       end
     end
 
-    def validate_job_variables!(name, job)
-      unless validate_variables(job[:variables])
-        raise ValidationError,
-          "#{name} job: variables should be a map of key-value strings"
-      end
-    end
-
-    def validate_job_cache!(name, job)
-      job[:cache].keys.each do |key|
-        unless ALLOWED_CACHE_KEYS.include? key
-          raise ValidationError, "#{name} job: cache unknown parameter #{key}"
-        end
-      end
-
-      if job[:cache][:key] && !validate_string(job[:cache][:key])
-        raise ValidationError, "#{name} job: cache:key parameter should be a string"
-      end
-
-      if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
-        raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
-      end
-
-      if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths])
-        raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings"
-      end
-    end
-
-    def validate_job_artifacts!(name, job)
-      job[:artifacts].keys.each do |key|
-        unless ALLOWED_ARTIFACTS_KEYS.include? key
-          raise ValidationError, "#{name} job: artifacts unknown parameter #{key}"
-        end
-      end
-
-      if job[:artifacts][:name] && !validate_string(job[:artifacts][:name])
-        raise ValidationError, "#{name} job: artifacts:name parameter should be a string"
-      end
-
-      if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
-        raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean"
-      end
-
-      if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
-        raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
-      end
-
-      if job[:artifacts][:when] && !job[:artifacts][:when].in?(%w[on_success on_failure always])
-        raise ValidationError, "#{name} job: artifacts:when parameter should be on_success, on_failure or always"
-      end
-
-      if job[:artifacts][:expire_in] && !validate_duration(job[:artifacts][:expire_in])
-        raise ValidationError, "#{name} job: artifacts:expire_in parameter should be a duration"
-      end
-    end
-
     def validate_job_dependencies!(name, job)
-      unless validate_array_of_strings(job[:dependencies])
-        raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
-      end
+      return unless job[:dependencies]
 
       stage_index = @stages.index(job[:stage])
 
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index de41ea415a67d17ab461dde421c7a6fc551d7649..a533bac26925ff589a89a4c312df8040769e28bd 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -7,6 +7,7 @@ module Gitlab
   module Access
     class AccessDeniedError < StandardError; end
 
+    NO_ACCESS = 0
     GUEST     = 10
     REPORTER  = 20
     DEVELOPER = 30
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
index 04676fdb74839e51813671459ae89915cd4177d8..207736b59db18fdd55c22908eb94a6416b2910f7 100644
--- a/lib/gitlab/akismet_helper.rb
+++ b/lib/gitlab/akismet_helper.rb
@@ -17,8 +17,8 @@ module Gitlab
       env['HTTP_USER_AGENT']
     end
 
-    def check_for_spam?(project, user)
-      akismet_enabled? && !project.team.member?(user)
+    def check_for_spam?(project)
+      akismet_enabled? && project.public?
     end
 
     def is_spam?(environment, user, text)
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
index c94bfc0e65ff751232d869c355f7e59289175d77..39b43ab5489a362ed9cfc3da7ba83209d653ce35 100644
--- a/lib/gitlab/award_emoji.rb
+++ b/lib/gitlab/award_emoji.rb
@@ -1,24 +1,14 @@
 module Gitlab
   class AwardEmoji
     CATEGORIES = {
-      other: "Other",
       objects: "Objects",
-      places: "Places",
-      travel_places: "Travel",
-      emoticons: "Emoticons",
-      objects_symbols: "Symbols",
+      travel: "Travel",
+      symbols: "Symbols",
       nature: "Nature",
-      celebration: "Celebration",
       people: "People",
       activity: "Activity",
       flags: "Flags",
-      food_drink: "Food"
-    }.with_indifferent_access
-
-    CATEGORY_ALIASES = {
-      symbols: "objects_symbols",
-      foods: "food_drink",
-      travel: "travel_places"
+      food: "Food"
     }.with_indifferent_access
 
     def self.normalize_emoji_name(name)
@@ -35,7 +25,7 @@ module Gitlab
           # Skip Fitzpatrick(tone) modifiers
           next if data["category"] == "modifier"
 
-          category = CATEGORY_ALIASES[data["category"]] || data["category"]
+          category = data["category"]
 
           @emoji_by_category[category] << data
         end
@@ -57,9 +47,9 @@ module Gitlab
     def self.aliases
       @aliases ||=
         begin
-         json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
-         JSON.parse(File.read(json_path))
-       end
+          json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
+          JSON.parse(File.read(json_path))
+        end
     end
 
     # Returns an Array of Emoji names and their asset URLs.
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 34e0143a82ee793bce189617e36fd9cce4ff4306..839a4fa30d5de358fc17b7f1ac53ec712a424f02 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -60,16 +60,18 @@ module Gitlab
     end
 
     # Fork repository to new namespace
-    # storage - project's storage path
+    # forked_from_storage - forked-from project's storage path
     # path - project path with namespace
+    # forked_to_storage - forked-to project's storage path
     # fork_namespace - namespace for forked project
     #
     # Ex.
-    #  fork_repository("/path/to/storage", "gitlab/gitlab-ci", "randx")
+    #  fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
     #
-    def fork_repository(storage, path, fork_namespace)
+    def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
       Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
-                                   storage, "#{path}.git", fork_namespace])
+                                   forked_from_storage, "#{path}.git", forked_to_storage,
+                                   fork_namespace])
     end
 
     # Remove repository from file system
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
index dfa83a0eab352ec78c2e07a4d5d380a713d5f747..5fe86553bd014486944dc6fa936457e490e93628 100644
--- a/lib/gitlab/checks/force_push.rb
+++ b/lib/gitlab/checks/force_push.rb
@@ -8,8 +8,8 @@ module Gitlab
         if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
           false
         else
-          missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
-          missed_refs.split("\n").size > 0
+          missed_ref, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list --max-count=1 #{oldrev} ^#{newrev}))
+          missed_ref.present?
         end
       end
     end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index e6cc1529760d9664f5031f07ba4625894527000e..ae82c0db3f1ce40f0bc4de001d4ce0e42e5e8d5c 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -8,7 +8,7 @@ module Gitlab
       # Temporary delegations that should be removed after refactoring
       #
       delegate :before_script, :image, :services, :after_script, :variables,
-               :stages, :cache, to: :@global
+               :stages, :cache, :jobs, to: :@global
 
       def initialize(config)
         @config = Loader.new(config).load!
diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..844bd2fe99861ea396ff8dd420178c0ca0e34466
--- /dev/null
+++ b/lib/gitlab/ci/config/node/artifacts.rb
@@ -0,0 +1,35 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a configuration of job artifacts.
+        #
+        class Artifacts < Entry
+          include Validatable
+          include Attributable
+
+          ALLOWED_KEYS = %i[name untracked paths when expire_in]
+
+          attributes ALLOWED_KEYS
+
+          validations do
+            validates :config, type: Hash
+            validates :config, allowed_keys: ALLOWED_KEYS
+
+            with_options allow_nil: true do
+              validates :name, type: String
+              validates :untracked, boolean: true
+              validates :paths, array_of_strings: true
+              validates :when,
+                inclusion: { in: %w[on_success on_failure always],
+                             message: 'should be on_success, on_failure ' \
+                                      'or always' }
+              validates :expire_in, duration: true
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/attributable.rb b/lib/gitlab/ci/config/node/attributable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..221b666f9f6d12d526652887cfa8429592dc66c5
--- /dev/null
+++ b/lib/gitlab/ci/config/node/attributable.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        module Attributable
+          extend ActiveSupport::Concern
+
+          class_methods do
+            def attributes(*attributes)
+              attributes.flatten.each do |attribute|
+                define_method(attribute) do
+                  return unless config.is_a?(Hash)
+
+                  config[attribute]
+                end
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb
index cdf8ba2e35d249b56156c5b00a2aad05d1f3a8dd..b4bda2841ac467ec9c3751d0ab5c82beaee3c294 100644
--- a/lib/gitlab/ci/config/node/cache.rb
+++ b/lib/gitlab/ci/config/node/cache.rb
@@ -8,6 +8,12 @@ module Gitlab
         class Cache < Entry
           include Configurable
 
+          ALLOWED_KEYS = %i[key untracked paths]
+
+          validations do
+            validates :config, allowed_keys: ALLOWED_KEYS
+          end
+
           node :key, Node::Key,
             description: 'Cache key used to define a cache affinity.'
 
@@ -16,10 +22,6 @@ module Gitlab
 
           node :paths, Node::Paths,
             description: 'Specify which paths should be cached across builds.'
-
-          validations do
-            validates :config, allowed_keys: true
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/commands.rb b/lib/gitlab/ci/config/node/commands.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d7657ae314b1776301cb3dc7ab7129da2341e431
--- /dev/null
+++ b/lib/gitlab/ci/config/node/commands.rb
@@ -0,0 +1,33 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a job script.
+        #
+        class Commands < Entry
+          include Validatable
+
+          validations do
+            include LegacyValidationHelpers
+
+            validate do
+              unless string_or_array_of_strings?(config)
+                errors.add(:config,
+                           'should be a string or an array of strings')
+              end
+            end
+
+            def string_or_array_of_strings?(field)
+              validate_string(field) || validate_array_of_strings(field)
+            end
+          end
+
+          def value
+            Array(@config)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 37936fc82421d21dd0818021c822aa48f3fcb1e2..2de82d40c9dad753c8e1f4c850ec3b0255d4ab39 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -25,10 +25,14 @@ module Gitlab
 
           private
 
-          def create_node(key, factory)
-            factory.with(value: @config[key], key: key, parent: self)
+          def compose!
+            self.class.nodes.each do |key, factory|
+              factory
+                .value(@config[key])
+                .with(key: key, parent: self)
 
-            factory.create!
+              @entries[key] = factory.create!
+            end
           end
 
           class_methods do
@@ -36,24 +40,25 @@ module Gitlab
               Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
             end
 
-            private
+            private # rubocop:disable Lint/UselessAccessModifier
 
-            def node(symbol, entry_class, metadata)
-              factory = Node::Factory.new(entry_class)
+            def node(key, node, metadata)
+              factory = Node::Factory.new(node)
                 .with(description: metadata[:description])
 
-              (@nodes ||= {}).merge!(symbol.to_sym => factory)
+              (@nodes ||= {}).merge!(key.to_sym => factory)
             end
 
             def helpers(*nodes)
               nodes.each do |symbol|
                 define_method("#{symbol}_defined?") do
-                  @nodes[symbol].try(:defined?)
+                  @entries[symbol].specified? if @entries[symbol]
                 end
 
                 define_method("#{symbol}_value") do
-                  raise Entry::InvalidError unless valid?
-                  @nodes[symbol].try(:value)
+                  return unless @entries[symbol] && @entries[symbol].valid?
+
+                  @entries[symbol].value
                 end
 
                 alias_method symbol.to_sym, "#{symbol}_value".to_sym
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 9e79e170a4fabf8cbc385763e71a0e9fc6133cdf..0c782c422b583d706b4ab33a9764bc2969ca1ef5 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -8,30 +8,31 @@ module Gitlab
         class Entry
           class InvalidError < StandardError; end
 
-          attr_reader :config
+          attr_reader :config, :metadata
           attr_accessor :key, :parent, :description
 
-          def initialize(config)
+          def initialize(config, **metadata)
             @config = config
-            @nodes = {}
+            @metadata = metadata
+            @entries = {}
+
             @validator = self.class.validator.new(self)
-            @validator.validate
+            @validator.validate(:new)
           end
 
           def process!
-            return if leaf?
             return unless valid?
 
             compose!
-            process_nodes!
+            descendants.each(&:process!)
           end
 
-          def nodes
-            @nodes.values
+          def leaf?
+            @entries.none?
           end
 
-          def leaf?
-            self.class.nodes.none?
+          def descendants
+            @entries.values
           end
 
           def ancestors
@@ -43,27 +44,30 @@ module Gitlab
           end
 
           def errors
-            @validator.messages + nodes.flat_map(&:errors)
+            @validator.messages + descendants.flat_map(&:errors)
           end
 
           def value
             if leaf?
               @config
             else
-              defined = @nodes.select { |_key, value| value.defined? }
-              Hash[defined.map { |key, node| [key, node.value] }]
+              meaningful = @entries.select do |_key, value|
+                value.specified? && value.relevant?
+              end
+
+              Hash[meaningful.map { |key, entry| [key, entry.value] }]
             end
           end
 
-          def defined?
+          def specified?
             true
           end
 
-          def self.default
+          def relevant?
+            true
           end
 
-          def self.nodes
-            {}
+          def self.default
           end
 
           def self.validator
@@ -73,17 +77,6 @@ module Gitlab
           private
 
           def compose!
-            self.class.nodes.each do |key, essence|
-              @nodes[key] = create_node(key, essence)
-            end
-          end
-
-          def process_nodes!
-            nodes.each(&:process!)
-          end
-
-          def create_node(key, essence)
-            raise NotImplementedError
           end
         end
       end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 5919a2832836593045328f9c9a84be5b9d95b3dc..707b052e6a8f15f8b15cbd63e0c18235ad9b7428 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -10,35 +10,60 @@ module Gitlab
 
           def initialize(node)
             @node = node
+            @metadata = {}
             @attributes = {}
           end
 
+          def value(value)
+            @value = value
+            self
+          end
+
+          def metadata(metadata)
+            @metadata.merge!(metadata)
+            self
+          end
+
           def with(attributes)
             @attributes.merge!(attributes)
             self
           end
 
           def create!
-            raise InvalidFactory unless @attributes.has_key?(:value)
+            raise InvalidFactory unless defined?(@value)
 
-            fabricate.tap do |entry|
-              entry.key = @attributes[:key]
-              entry.parent = @attributes[:parent]
-              entry.description = @attributes[:description]
+            ##
+            # We assume that unspecified entry is undefined.
+            # See issue #18775.
+            #
+            if @value.nil?
+              Node::Undefined.new(
+                fabricate_undefined
+              )
+            else
+              fabricate(@node, @value)
             end
           end
 
           private
 
-          def fabricate
+          def fabricate_undefined
             ##
-            # We assume that unspecified entry is undefined.
-            # See issue #18775.
+            # If node has a default value we fabricate concrete node
+            # with default value.
             #
-            if @attributes[:value].nil?
-              Node::Undefined.new(@node)
+            if @node.default.nil?
+              fabricate(Node::Null)
             else
-              @node.new(@attributes[:value])
+              fabricate(@node, @node.default)
+            end
+          end
+
+          def fabricate(node, value = nil)
+            node.new(value, @metadata).tap do |entry|
+              entry.key = @attributes[:key]
+              entry.parent = @attributes[:parent]
+              entry.description = @attributes[:description]
             end
           end
         end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index f92e1eccbcf4f0d2b32a3c03133d8f429acafab8..ccd539fb0037cb43d2d39f70e2f5dd357ae19334 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -34,10 +34,36 @@ module Gitlab
             description: 'Configure caching between build jobs.'
 
           helpers :before_script, :image, :services, :after_script,
-                  :variables, :stages, :types, :cache
+                  :variables, :stages, :types, :cache, :jobs
 
-          def stages
-            stages_defined? ? stages_value : types_value
+          private
+
+          def compose!
+            super
+
+            compose_jobs!
+            compose_deprecated_entries!
+          end
+
+          def compose_jobs!
+            factory = Node::Factory.new(Node::Jobs)
+              .value(@config.except(*self.class.nodes.keys))
+              .with(key: :jobs, parent: self,
+                    description: 'Jobs definition for this pipeline')
+
+            @entries[:jobs] = factory.create!
+          end
+
+          def compose_deprecated_entries!
+            ##
+            # Deprecated `:types` key workaround - if types are defined and
+            # stages are not defined we use types definition as stages.
+            #
+            if types_defined? && !stages_defined?
+              @entries[:stages] = @entries[:types]
+            end
+
+            @entries.delete(:types)
           end
         end
       end
diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden_job.rb
new file mode 100644
index 0000000000000000000000000000000000000000..073044b66f89c66ae593d1042b22214b6d901bfc
--- /dev/null
+++ b/lib/gitlab/ci/config/node/hidden_job.rb
@@ -0,0 +1,23 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a hidden CI/CD job.
+        #
+        class HiddenJob < Entry
+          include Validatable
+
+          validations do
+            validates :config, type: Hash
+            validates :config, presence: true
+          end
+
+          def relevant?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e84737acbb98e2f70d96a6895bb9a908949e9e1a
--- /dev/null
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -0,0 +1,123 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a concrete CI/CD job.
+        #
+        class Job < Entry
+          include Configurable
+          include Attributable
+
+          ALLOWED_KEYS = %i[tags script only except type image services allow_failure
+                            type stage when artifacts cache dependencies before_script
+                            after_script variables environment]
+
+          attributes :tags, :allow_failure, :when, :environment, :dependencies
+
+          validations do
+            validates :config, allowed_keys: ALLOWED_KEYS
+
+            validates :config, presence: true
+            validates :name, presence: true
+            validates :name, type: Symbol
+
+            with_options allow_nil: true do
+              validates :tags, array_of_strings: true
+              validates :allow_failure, boolean: true
+              validates :when,
+                inclusion: { in: %w[on_success on_failure always manual],
+                             message: 'should be on_success, on_failure, ' \
+                                      'always or manual' }
+              validates :environment,
+                type: {
+                  with: String,
+                  message: Gitlab::Regex.environment_name_regex_message }
+              validates :environment,
+                format: {
+                  with: Gitlab::Regex.environment_name_regex,
+                  message: Gitlab::Regex.environment_name_regex_message }
+
+              validates :dependencies, array_of_strings: true
+            end
+          end
+
+          node :before_script, Script,
+            description: 'Global before script overridden in this job.'
+
+          node :script, Commands,
+            description: 'Commands that will be executed in this job.'
+
+          node :stage, Stage,
+            description: 'Pipeline stage this job will be executed into.'
+
+          node :type, Stage,
+            description: 'Deprecated: stage this job will be executed into.'
+
+          node :after_script, Script,
+            description: 'Commands that will be executed when finishing job.'
+
+          node :cache, Cache,
+            description: 'Cache definition for this job.'
+
+          node :image, Image,
+            description: 'Image that will be used to execute this job.'
+
+          node :services, Services,
+            description: 'Services that will be used to execute this job.'
+
+          node :only, Trigger,
+            description: 'Refs policy this job will be executed for.'
+
+          node :except, Trigger,
+            description: 'Refs policy this job will be executed for.'
+
+          node :variables, Variables,
+            description: 'Environment variables available for this job.'
+
+          node :artifacts, Artifacts,
+            description: 'Artifacts configuration for this job.'
+
+          helpers :before_script, :script, :stage, :type, :after_script,
+                  :cache, :image, :services, :only, :except, :variables,
+                  :artifacts
+
+          def name
+            @metadata[:name]
+          end
+
+          def value
+            @config.merge(to_hash.compact)
+          end
+
+          private
+
+          def to_hash
+            { name: name,
+              before_script: before_script,
+              script: script,
+              image: image,
+              services: services,
+              stage: stage,
+              cache: cache,
+              only: only,
+              except: except,
+              variables: variables_defined? ? variables : nil,
+              artifacts: artifacts,
+              after_script: after_script }
+          end
+
+          def compose!
+            super
+
+            if type_defined? && !stage_defined?
+              @entries[:stage] = @entries[:type]
+            end
+
+            @entries.delete(:type)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51683c82ceb7565f742b5063fd2163ac5bc8c654
--- /dev/null
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -0,0 +1,48 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a set of jobs.
+        #
+        class Jobs < Entry
+          include Validatable
+
+          validations do
+            validates :config, type: Hash
+
+            validate do
+              unless has_visible_job?
+                errors.add(:config, 'should contain at least one visible job')
+              end
+            end
+
+            def has_visible_job?
+              config.any? { |name, _| !hidden?(name) }
+            end
+          end
+
+          def hidden?(name)
+            name.to_s.start_with?('.')
+          end
+
+          private
+
+          def compose!
+            @config.each do |name, config|
+              node = hidden?(name) ? Node::HiddenJob : Node::Job
+
+              factory = Node::Factory.new(node)
+                .value(config || {})
+                .metadata(name: name)
+                .with(key: name, parent: self,
+                      description: "#{name} job definition.")
+
+              @entries[name] = factory.create!
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
index 4d9a508796abff70c3731d0a2248998e7d5a83ab..0c291efe6a59c55ac89c0828cbd089cf0df0e521 100644
--- a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
+++ b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
@@ -41,10 +41,6 @@ module Gitlab
             false
           end
 
-          def validate_environment(value)
-            value.is_a?(String) && value =~ Gitlab::Regex.environment_name_regex
-          end
-
           def validate_boolean(value)
             value.in?([true, false])
           end
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
new file mode 100644
index 0000000000000000000000000000000000000000..88a5f53f13c1a046e8754acad7b4052f992b40cc
--- /dev/null
+++ b/lib/gitlab/ci/config/node/null.rb
@@ -0,0 +1,34 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # This class represents an undefined node.
+        #
+        # Implements the Null Object pattern.
+        #
+        class Null < Entry
+          def value
+            nil
+          end
+
+          def valid?
+            true
+          end
+
+          def errors
+            []
+          end
+
+          def specified?
+            false
+          end
+
+          def relevant?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cbc97641f5a8816aa24441f93400a6b162d1966b
--- /dev/null
+++ b/lib/gitlab/ci/config/node/stage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a stage for a job.
+        #
+        class Stage < Entry
+          include Validatable
+
+          validations do
+            validates :config, type: String
+          end
+
+          def self.default
+            'test'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/trigger.rb b/lib/gitlab/ci/config/node/trigger.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d8b31975088032c72a1e1b7661e4932b8e54c1bc
--- /dev/null
+++ b/lib/gitlab/ci/config/node/trigger.rb
@@ -0,0 +1,26 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # Entry that represents a trigger policy for the job.
+        #
+        class Trigger < Entry
+          include Validatable
+
+          validations do
+            include LegacyValidationHelpers
+
+            validate :array_of_strings_or_regexps
+
+            def array_of_strings_or_regexps
+              unless validate_array_of_strings_or_regexps(config)
+                errors.add(:config, 'should be an array of strings or regexps')
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
index 699605e1e3aef2ceffc324b5ba92463ca1a15135..45fef8c3ae55204c3d0a5a87b92de655e9d4cebe 100644
--- a/lib/gitlab/ci/config/node/undefined.rb
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -3,24 +3,13 @@ module Gitlab
     class Config
       module Node
         ##
-        # This class represents an undefined entry node.
+        # This class represents an unspecified entry node.
         #
-        # It takes original entry class as configuration and returns default
-        # value of original entry as self value.
+        # It decorates original entry adding method that indicates it is
+        # unspecified.
         #
-        #
-        class Undefined < Entry
-          include Validatable
-
-          validations do
-            validates :config, type: Class
-          end
-
-          def value
-            @config.default
-          end
-
-          def defined?
+        class Undefined < SimpleDelegator
+          def specified?
             false
           end
         end
diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb
index 758a6cf435672d50610df01516211ad45f73d198..43c7e102b50a47b66b9f5021f7b4275bbe3607f8 100644
--- a/lib/gitlab/ci/config/node/validator.rb
+++ b/lib/gitlab/ci/config/node/validator.rb
@@ -21,18 +21,19 @@ module Gitlab
             'Validator'
           end
 
-          def unknown_keys
-            return [] unless config.is_a?(Hash)
-
-            config.keys - @node.class.nodes.keys
-          end
-
           private
 
           def location
             predecessors = ancestors.map(&:key).compact
-            current = key || @node.class.name.demodulize.underscore
-            predecessors.append(current).join(':')
+            predecessors.append(key_name).join(':')
+          end
+
+          def key_name
+            if key.blank?
+              @node.class.name.demodulize.underscore.humanize
+            else
+              key
+            end
           end
         end
       end
diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb
index 7b2f57990b5c468fb99b0051b4888853a79666f8..e20908ad3cb2f0727933fdfec05593149e54ddc8 100644
--- a/lib/gitlab/ci/config/node/validators.rb
+++ b/lib/gitlab/ci/config/node/validators.rb
@@ -5,10 +5,11 @@ module Gitlab
         module Validators
           class AllowedKeysValidator < ActiveModel::EachValidator
             def validate_each(record, attribute, value)
-              if record.unknown_keys.any?
-                unknown_list = record.unknown_keys.join(', ')
-                record.errors.add(:config,
-                                  "contains unknown keys: #{unknown_list}")
+              unknown_keys = record.config.try(:keys).to_a - options[:in]
+
+              if unknown_keys.any?
+                record.errors.add(:config, 'contains unknown keys: ' +
+                                            unknown_keys.join(', '))
               end
             end
           end
@@ -33,6 +34,16 @@ module Gitlab
             end
           end
 
+          class DurationValidator < ActiveModel::EachValidator
+            include LegacyValidationHelpers
+
+            def validate_each(record, attribute, value)
+              unless validate_duration(value)
+                record.errors.add(attribute, 'should be a duration')
+              end
+            end
+          end
+
           class KeyValidator < ActiveModel::EachValidator
             include LegacyValidationHelpers
 
@@ -49,7 +60,8 @@ module Gitlab
               raise unless type.is_a?(Class)
 
               unless value.is_a?(type)
-                record.errors.add(attribute, "should be a #{type.name}")
+                message = options[:message] || "should be a #{type.name}"
+                record.errors.add(attribute, message)
               end
             end
           end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index ffc1814b29d631a7b640586a89445dd859e10aa9..735331df66cf4c72acfb0bc0b4c5a3e23c143a0a 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -39,7 +39,7 @@ module Gitlab
         session_expire_delay: Settings.gitlab['session_expire_delay'],
         default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
         default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
-        restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+        domain_whitelist: Settings.gitlab['domain_whitelist'],
         import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project],
         shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
         max_artifacts_size: Settings.artifacts['max_size'],
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 078609c86f15dec8bc74da36bf4c18a8fef50930..55b8f888d534cb631c53015ad50a17880f9e44d0 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -55,12 +55,12 @@ module Gitlab
       end
     end
 
-    private
-
     def self.connection
       ActiveRecord::Base.connection
     end
 
+    private_class_method :connection
+
     def self.database_version
       row = connection.execute("SELECT VERSION()").first
 
@@ -70,5 +70,7 @@ module Gitlab
         row.first
       end
     end
+
+    private_class_method :database_version
   end
 end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 28ad637fda455b646e3faab3ec207499e0ad0814..55708d42161043182b014f379c559e4295dc194b 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -19,24 +19,6 @@ module Gitlab
 
       attr_accessor :old_line, :new_line, :offset
 
-      def self.for_lines(lines)
-        changed_line_pairs = self.find_changed_line_pairs(lines)
-
-        inline_diffs = []
-
-        changed_line_pairs.each do |old_index, new_index|
-          old_line = lines[old_index]
-          new_line = lines[new_index]
-
-          old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
-
-          inline_diffs[old_index] = old_diffs
-          inline_diffs[new_index] = new_diffs
-        end
-
-        inline_diffs
-      end
-
       def initialize(old_line, new_line, offset: 0)
         @old_line = old_line[offset..-1]
         @new_line = new_line[offset..-1]
@@ -63,32 +45,54 @@ module Gitlab
         [old_diffs, new_diffs]
       end
 
-      private
+      class << self
+        def for_lines(lines)
+          changed_line_pairs = find_changed_line_pairs(lines)
 
-      # Finds pairs of old/new line pairs that represent the same line that changed
-      def self.find_changed_line_pairs(lines)
-        # Prefixes of all diff lines, indicating their types
-        # For example: `" - +  -+  ---+++ --+  -++"`
-        line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+          inline_diffs = []
 
-        changed_line_pairs = []
-        line_prefixes.scan(LINE_PAIRS_PATTERN) do
-          # For `"---+++"`, `begin_index == 0`, `end_index == 6`
-          begin_index, end_index = Regexp.last_match.offset(:del_ins)
+          changed_line_pairs.each do |old_index, new_index|
+            old_line = lines[old_index]
+            new_line = lines[new_index]
 
-          # For `"---+++"`, `changed_line_count == 3`
-          changed_line_count = (end_index - begin_index) / 2
+            old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
 
-          halfway_index = begin_index + changed_line_count
-          (begin_index...halfway_index).each do |i|
-            # For `"---+++"`, index 1 maps to 1 + 3 = 4
-            changed_line_pairs << [i, i + changed_line_count]
+            inline_diffs[old_index] = old_diffs
+            inline_diffs[new_index] = new_diffs
           end
+
+          inline_diffs
         end
 
-        changed_line_pairs
+        private
+
+        # Finds pairs of old/new line pairs that represent the same line that changed
+        def find_changed_line_pairs(lines)
+          # Prefixes of all diff lines, indicating their types
+          # For example: `" - +  -+  ---+++ --+  -++"`
+          line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+
+          changed_line_pairs = []
+          line_prefixes.scan(LINE_PAIRS_PATTERN) do
+            # For `"---+++"`, `begin_index == 0`, `end_index == 6`
+            begin_index, end_index = Regexp.last_match.offset(:del_ins)
+
+            # For `"---+++"`, `changed_line_count == 3`
+            changed_line_count = (end_index - begin_index) / 2
+
+            halfway_index = begin_index + changed_line_count
+            (begin_index...halfway_index).each do |i|
+              # For `"---+++"`, index 1 maps to 1 + 3 = 4
+              changed_line_pairs << [i, i + changed_line_count]
+            end
+          end
+
+          changed_line_pairs
+        end
       end
 
+      private
+
       def longest_common_prefix(a, b)
         max_length = [a.length, b.length].max
 
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
index b069afdd28c1a03eb3083c326ee32393f78aa2bc..481536a380bc9118d5103141dbb198b54131a636 100644
--- a/lib/gitlab/diff/parallel_diff.rb
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -8,72 +8,35 @@ module Gitlab
       end
 
       def parallelize
-
         i = 0
         free_right_index = nil
 
         lines = []
         highlighted_diff_lines = diff_file.highlighted_diff_lines
         highlighted_diff_lines.each do |line|
-          line_code = diff_file.line_code(line)
-          position = diff_file.position(line)
-
-          case line.type
-          when 'match', nil
+          if line.meta? || line.unchanged?
             # line in the right panel is the same as in the left one
             lines << {
-              left: {
-                type:       line.type,
-                number:     line.old_pos,
-                text:       line.text,
-                line_code:  line_code,
-                position:   position
-              },
-              right: {
-                type:       line.type,
-                number:     line.new_pos,
-                text:       line.text,
-                line_code:  line_code,
-                position:   position
-              }
+              left: line,
+              right: line
             }
 
             free_right_index = nil
             i += 1
-          when 'old'
+          elsif line.removed?
             lines << {
-              left: {
-                type:       line.type,
-                number:     line.old_pos,
-                text:       line.text,
-                line_code:  line_code,
-                position:   position
-              },
-              right: {
-                type:       nil,
-                number:     nil,
-                text:       "",
-                line_code:  line_code,
-                position:   position
-              }
+              left: line,
+              right: nil
             }
 
             # Once we come upon a new line it can be put on the right of this old line
             free_right_index ||= i
             i += 1
-          when 'new'
-            data = {
-              type:       line.type,
-              number:     line.new_pos,
-              text:       line.text,
-              line_code:  line_code,
-              position:   position
-            }
-
+          elsif line.added?
             if free_right_index
               # If an old line came before this without a line on the right, this
               # line can be put to the right of it.
-              lines[free_right_index][:right] = data
+              lines[free_right_index][:right] = line
 
               # If there are any other old lines on the left that don't yet have
               # a new counterpart on the right, update the free_right_index
@@ -81,14 +44,8 @@ module Gitlab
               free_right_index = next_free_right_index < i ? next_free_right_index : nil
             else
               lines << {
-                left: {
-                  type:       nil,
-                  number:     nil,
-                  text:       "",
-                  line_code:  line_code,
-                  position:   position
-                },
-                right: data
+                left: nil,
+                right: line
               }
 
               free_right_index = nil
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index 989fff8918eca130a56252d0647676b09f20649e..2fdcf8d7838d7e572066228abaa5cdd2d976d70a 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -73,8 +73,8 @@ module Gitlab
           diff_refs.complete?
       end
 
-      def to_json
-        JSON.generate(self.to_h)
+      def to_json(opts = nil)
+        JSON.generate(self.to_h, opts)
       end
 
       def type
diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab9537ed7d701752ff9c020da8a45448c016a65a
--- /dev/null
+++ b/lib/gitlab/downtime_check.rb
@@ -0,0 +1,71 @@
+module Gitlab
+  # Checks if a set of migrations requires downtime or not.
+  class DowntimeCheck
+    # The constant containing the boolean that indicates if downtime is needed
+    # or not.
+    DOWNTIME_CONST = :DOWNTIME
+
+    # The constant that specifies the reason for the migration requiring
+    # downtime.
+    DOWNTIME_REASON_CONST = :DOWNTIME_REASON
+
+    # Checks the given migration paths and returns an Array of
+    # `Gitlab::DowntimeCheck::Message` instances.
+    #
+    # migrations - The migration file paths to check.
+    def check(migrations)
+      migrations.map do |path|
+        require(path)
+
+        migration_class = class_for_migration_file(path)
+
+        unless migration_class.const_defined?(DOWNTIME_CONST)
+          raise "The migration in #{path} does not specify if it requires " \
+            "downtime or not"
+        end
+
+        if online?(migration_class)
+          Message.new(path)
+        else
+          reason = downtime_reason(migration_class)
+
+          unless reason
+            raise "The migration in #{path} requires downtime but no reason " \
+              "was given"
+          end
+
+          Message.new(path, true, reason)
+        end
+      end
+    end
+
+    # Checks the given migrations and prints the results to STDOUT/STDERR.
+    #
+    # migrations - The migration file paths to check.
+    def check_and_print(migrations)
+      check(migrations).each do |message|
+        puts message.to_s # rubocop: disable Rails/Output
+      end
+    end
+
+    # Returns the class for the given migration file path.
+    def class_for_migration_file(path)
+      File.basename(path, File.extname(path)).split('_', 2).last.camelize.
+        constantize
+    end
+
+    # Returns true if the given migration can be performed without downtime.
+    def online?(migration)
+      migration.const_get(DOWNTIME_CONST) == false
+    end
+
+    # Returns the downtime reason, or nil if none was defined.
+    def downtime_reason(migration)
+      if migration.const_defined?(DOWNTIME_REASON_CONST)
+        migration.const_get(DOWNTIME_REASON_CONST)
+      else
+        nil
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4446e921e0df989d2cd0b7a54dafd84388becfe9
--- /dev/null
+++ b/lib/gitlab/downtime_check/message.rb
@@ -0,0 +1,28 @@
+module Gitlab
+  class DowntimeCheck
+    class Message
+      attr_reader :path, :offline, :reason
+
+      OFFLINE = "\e[32moffline\e[0m"
+      ONLINE = "\e[31monline\e[0m"
+
+      # path - The file path of the migration.
+      # offline - When set to `true` the migration will require downtime.
+      # reason - The reason as to why the migration requires downtime.
+      def initialize(path, offline = false, reason = nil)
+        @path = path
+        @offline = offline
+        @reason = reason
+      end
+
+      def to_s
+        label = offline ? OFFLINE : ONLINE
+
+        message = "[#{label}]: #{path}"
+        message += ": #{reason}" if reason
+
+        message
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd3267e2a80ba54f31753d732b664b19e8838583
--- /dev/null
+++ b/lib/gitlab/email/handler.rb
@@ -0,0 +1,17 @@
+require 'gitlab/email/handler/create_note_handler'
+require 'gitlab/email/handler/create_issue_handler'
+
+module Gitlab
+  module Email
+    module Handler
+      HANDLERS = [CreateNoteHandler, CreateIssueHandler]
+
+      def self.for(mail, mail_key)
+        HANDLERS.find do |klass|
+          handler = klass.new(mail, mail_key)
+          break handler if handler.can_handle?
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7ed11cb6389e06cdbeccd81a4b0134534d5636e
--- /dev/null
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -0,0 +1,60 @@
+module Gitlab
+  module Email
+    module Handler
+      class BaseHandler
+        attr_reader :mail, :mail_key
+
+        def initialize(mail, mail_key)
+          @mail = mail
+          @mail_key = mail_key
+        end
+
+        def message
+          @message ||= process_message
+        end
+
+        def author
+          raise NotImplementedError
+        end
+
+        def project
+          raise NotImplementedError
+        end
+
+        private
+
+        def validate_permission!(permission)
+          raise UserNotFoundError unless author
+          raise UserBlockedError if author.blocked?
+          raise ProjectNotFound unless author.can?(:read_project, project)
+          raise UserNotAuthorizedError unless author.can?(permission, project)
+        end
+
+        def process_message
+          message = ReplyParser.new(mail).execute.strip
+          add_attachments(message)
+        end
+
+        def add_attachments(reply)
+          attachments = Email::AttachmentUploader.new(mail).execute(project)
+
+          reply + attachments.map do |link|
+            "\n\n#{link[:markdown]}"
+          end.join
+        end
+
+        def verify_record!(record:, invalid_exception:, record_name:)
+          return if record.persisted?
+
+          error_title = "The #{record_name} could not be created for the following reasons:"
+
+          msg = error_title + record.errors.full_messages.map do |error|
+            "\n\n- #{error}"
+          end.join
+
+          raise invalid_exception, msg
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4e6566af8abed30fb7658266c8ec2897802fa23b
--- /dev/null
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -0,0 +1,52 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+  module Email
+    module Handler
+      class CreateIssueHandler < BaseHandler
+        attr_reader :project_path, :authentication_token
+
+        def initialize(mail, mail_key)
+          super(mail, mail_key)
+          @project_path, @authentication_token =
+            mail_key && mail_key.split('+', 2)
+        end
+
+        def can_handle?
+          !authentication_token.nil?
+        end
+
+        def execute
+          raise ProjectNotFound unless project
+
+          validate_permission!(:create_issue)
+
+          verify_record!(
+            record: create_issue,
+            invalid_exception: InvalidIssueError,
+            record_name: 'issue')
+        end
+
+        def author
+          @author ||= User.find_by(authentication_token: authentication_token)
+        end
+
+        def project
+          @project ||= Project.find_with_namespace(project_path)
+        end
+
+        private
+
+        def create_issue
+          Issues::CreateService.new(
+            project,
+            author,
+            title:       mail.subject,
+            description: message
+          ).execute
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..06dae31cc27e7ce139c6a7476918df83c34141a8
--- /dev/null
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -0,0 +1,55 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+  module Email
+    module Handler
+      class CreateNoteHandler < BaseHandler
+        def can_handle?
+          mail_key =~ /\A\w+\z/
+        end
+
+        def execute
+          raise SentNotificationNotFoundError unless sent_notification
+          raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/
+
+          validate_permission!(:create_note)
+
+          raise NoteableNotFoundError unless sent_notification.noteable
+          raise EmptyEmailError if message.blank?
+
+          verify_record!(
+            record: create_note,
+            invalid_exception: InvalidNoteError,
+            record_name: 'comment')
+        end
+
+        def author
+          sent_notification.recipient
+        end
+
+        def project
+          sent_notification.project
+        end
+
+        def sent_notification
+          @sent_notification ||= SentNotification.for(mail_key)
+        end
+
+        private
+
+        def create_note
+          Notes::CreateService.new(
+            project,
+            author,
+            note:           message,
+            noteable_type:  sent_notification.noteable_type,
+            noteable_id:    sent_notification.noteable_id,
+            commit_id:      sent_notification.commit_id,
+            line_code:      sent_notification.line_code
+          ).execute
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 1c671a7487b26ebbc201b1cbb60a08cd53ade85f..9213cfb51e8354ed99768f80f65aec717738981f 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -1,18 +1,24 @@
+
+require 'gitlab/email/handler'
+
 # Inspired in great part by Discourse's Email::Receiver
 module Gitlab
   module Email
-    class Receiver
-      class ProcessingError < StandardError; end
-      class EmailUnparsableError < ProcessingError; end
-      class SentNotificationNotFoundError < ProcessingError; end
-      class EmptyEmailError < ProcessingError; end
-      class AutoGeneratedEmailError < ProcessingError; end
-      class UserNotFoundError < ProcessingError; end
-      class UserBlockedError < ProcessingError; end
-      class UserNotAuthorizedError < ProcessingError; end
-      class NoteableNotFoundError < ProcessingError; end
-      class InvalidNoteError < ProcessingError; end
+    class ProcessingError < StandardError; end
+    class EmailUnparsableError < ProcessingError; end
+    class SentNotificationNotFoundError < ProcessingError; end
+    class ProjectNotFound < ProcessingError; end
+    class EmptyEmailError < ProcessingError; end
+    class AutoGeneratedEmailError < ProcessingError; end
+    class UserNotFoundError < ProcessingError; end
+    class UserBlockedError < ProcessingError; end
+    class UserNotAuthorizedError < ProcessingError; end
+    class NoteableNotFoundError < ProcessingError; end
+    class InvalidNoteError < ProcessingError; end
+    class InvalidIssueError < ProcessingError; end
+    class UnknownIncomingEmail < ProcessingError; end
 
+    class Receiver
       def initialize(raw)
         @raw = raw
       end
@@ -20,91 +26,38 @@ module Gitlab
       def execute
         raise EmptyEmailError if @raw.blank?
 
-        raise SentNotificationNotFoundError unless sent_notification
-
-        raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/
-
-        author = sent_notification.recipient
-
-        raise UserNotFoundError unless author
-
-        raise UserBlockedError if author.blocked?
-
-        project = sent_notification.project
-
-        raise UserNotAuthorizedError unless project && author.can?(:create_note, project)
-
-        raise NoteableNotFoundError unless sent_notification.noteable
-
-        reply = ReplyParser.new(message).execute.strip
-
-        raise EmptyEmailError if reply.blank?
-
-        reply = add_attachments(reply)
-
-        note = create_note(reply)
+        mail = build_mail
+        mail_key = extract_mail_key(mail)
+        handler = Handler.for(mail, mail_key)
 
-        unless note.persisted?
-          msg = "The comment could not be created for the following reasons:"
-          note.errors.full_messages.each do |error|
-            msg << "\n\n- #{error}"
-          end
+        raise UnknownIncomingEmail unless handler
 
-          raise InvalidNoteError, msg
-        end
+        handler.execute
       end
 
-      private
-
-      def message
-        @message ||= Mail::Message.new(@raw)
-      rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
+      def build_mail
+        Mail::Message.new(@raw)
+      rescue Encoding::UndefinedConversionError,
+             Encoding::InvalidByteSequenceError => e
         raise EmailUnparsableError, e
       end
 
-      def reply_key
-        key_from_to_header || key_from_additional_headers
+      def extract_mail_key(mail)
+        key_from_to_header(mail) || key_from_additional_headers(mail)
       end
 
-      def key_from_to_header
-        key = nil
-        message.to.each do |address|
+      def key_from_to_header(mail)
+        mail.to.find do |address|
           key = Gitlab::IncomingEmail.key_from_address(address)
-          break if key
+          break key if key
         end
-
-        key
       end
 
-      def key_from_additional_headers
-        reply_key = nil
-
-        Array(message.references).each do |message_id|
-          reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
-          break if reply_key
+      def key_from_additional_headers(mail)
+        Array(mail.references).find do |mail_id|
+          key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
+          break key if key
         end
-
-        reply_key
-      end
-
-      def sent_notification
-        return nil unless reply_key
-
-        SentNotification.for(reply_key)
-      end
-
-      def add_attachments(reply)
-        attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
-
-        attachments.each do |link|
-          reply << "\n\n#{link[:markdown]}"
-        end
-
-        reply
-      end
-
-      def create_note(reply)
-        sent_notification.create_note(reply)
       end
     end
   end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 308f23bc9bcdc0e5c4f26d992437e789753bb868..8e8f39d9cb25435ee7a8bb9faafda59b8e3eeb79 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -110,6 +110,7 @@ module Gitlab
 
     def deploy_key_can_read_project?
       if deploy_key
+        return true if project.public?
         deploy_key.projects.include?(project)
       else
         false
diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb
index 5a806ff6e0df59e196f51b4ded5fd41963679168..09bb01be694ff69a2934e5b2d236f07b5f475721 100644
--- a/lib/gitlab/git_access_status.rb
+++ b/lib/gitlab/git_access_status.rb
@@ -8,8 +8,8 @@ module Gitlab
       @message = message
     end
 
-    def to_json
-      { status: @status, message: @message }.to_json
+    def to_json(opts = nil)
+      { status: @status, message: @message }.to_json(opts)
     end
   end
 end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index d4f12cb1df9716cc86c7b115049ec03f9a370ee6..c5a11148d33f7c5dae8fa1c8b5811b91a177b9b7 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -5,7 +5,7 @@ module Gitlab
       gon.default_avatar_url     = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
       gon.max_file_size          = current_application_settings.max_attachment_size
       gon.relative_url_root      = Gitlab.config.gitlab.relative_url_root
-      gon.shortcuts_path         = help_shortcuts_path
+      gon.shortcuts_path         = help_page_path('shortcuts')
       gon.user_color_scheme      = Gitlab::ColorSchemes.for_user(current_user).css_class
       gon.award_menu_url         = emojis_path
 
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index bab2ea73c4f10a63636a81dabd66cb3eecddeb0a..48b2c43ac21cee166310d10c870920054b5a5365 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,7 @@ module Gitlab
   module ImportExport
     extend self
 
-    VERSION = '0.1.1'
+    VERSION = '0.1.3'
     FILENAME_LIMIT = 50
 
     def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/avatar_restorer.rb b/lib/gitlab/import_export/avatar_restorer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..352539eb594ead70aec67accc7d348ba31776f15
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_restorer.rb
@@ -0,0 +1,31 @@
+module Gitlab
+  module ImportExport
+    class AvatarRestorer
+
+      def initialize(project:, shared:)
+        @project = project
+        @shared = shared
+      end
+
+      def restore
+        return true unless avatar_export_file
+
+        @project.avatar = File.open(avatar_export_file)
+        @project.save!
+      rescue => e
+        @shared.error(e)
+        false
+      end
+
+      private
+
+      def avatar_export_file
+        @avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
+      end
+
+      def avatar_export_path
+        File.join(@shared.export_path, 'avatar')
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..998c21e25869c3daa7529d722d19e421b9cdec83
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_saver.rb
@@ -0,0 +1,31 @@
+module Gitlab
+  module ImportExport
+    class AvatarSaver
+      include Gitlab::ImportExport::CommandLineUtil
+
+      def initialize(project:, shared:)
+        @project = project
+        @shared = shared
+      end
+
+      def save
+        return true unless @project.avatar.exists?
+
+        copy_files(avatar_path, avatar_export_path)
+      rescue => e
+        @shared.error(e)
+        false
+      end
+
+      private
+
+      def avatar_export_path
+        File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
+      end
+
+      def avatar_path
+        @project.avatar.path
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index 2249904145c7df427cff988c0a6159fa1781a5aa..e522a0fc8f69a6ba1b906bdf9124458bda24f6ba 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -17,6 +17,10 @@ module Gitlab
         execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path}))
       end
 
+      def git_restore_hooks
+        execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
+      end
+
       private
 
       def tar_with_options(archive:, dir:, options:)
@@ -36,6 +40,19 @@ module Gitlab
       def git_bin_path
         Gitlab.config.git.bin_path
       end
+
+      def copy_files(source, destination)
+        # if we are copying files, create the destination folder
+        destination_folder = File.file?(source) ? File.dirname(destination) : destination
+
+        FileUtils.mkdir_p(destination_folder)
+        FileUtils.copy_entry(source, destination)
+        true
+      end
+
+      def repository_storage_paths_args
+        Gitlab.config.repositories.storages.values
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 82d1e1805c5a2b585a2d17a5bbf082e21724f0e4..eca6e5b6d512dcfc358b4164b4f2e8f1a9c53a9c 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -3,6 +3,8 @@ module Gitlab
     class FileImporter
       include Gitlab::ImportExport::CommandLineUtil
 
+      MAX_RETRIES = 8
+
       def self.import(*args)
         new(*args).import
       end
@@ -14,7 +16,10 @@ module Gitlab
 
       def import
         FileUtils.mkdir_p(@shared.export_path)
-        decompress_archive
+
+        wait_for_archived_file do
+          decompress_archive
+        end
       rescue => e
         @shared.error(e)
         false
@@ -22,6 +27,17 @@ module Gitlab
 
       private
 
+      # Exponentially sleep until I/O finishes copying the file
+      def wait_for_archived_file
+        MAX_RETRIES.times do |retry_number|
+          break if File.exist?(@archive_file)
+
+          sleep(2**retry_number)
+        end
+
+        yield
+      end
+
       def decompress_archive
         result = untar_zxf(archive: @archive_file, dir: @shared.export_path)
 
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 05f4ad527ac356ca14130690202d9a626e9d3f45..1da51043611a4d547da37db26133362771e74bb6 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -3,11 +3,12 @@ project_tree:
   - issues:
     - :events
     - notes:
-        - :author
-        - :events
-  - :labels
-  - milestones:
-    - :events
+      - :author
+      - :events
+    - label_links:
+      - :label
+    - milestone:
+      - :events
   - snippets:
     - notes:
         :author
@@ -20,6 +21,10 @@ project_tree:
       - :events
     - :merge_request_diff
     - :events
+    - label_links:
+      - :label
+    - milestone:
+      - :events
   - pipelines:
     - notes:
       - :author
@@ -31,6 +36,9 @@ project_tree:
   - :services
   - :hooks
   - :protected_branches
+  - :labels
+  - milestones:
+    - :events
 
 # Only include the following attributes for the models specified.
 included_attributes:
@@ -53,7 +61,15 @@ included_attributes:
 excluded_attributes:
   snippets:
     - :expired_at
+  merge_request_diff:
+    - :st_diffs
+  issues:
+    - :milestone_id
+  merge_requests:
+    - :milestone_id
 
 methods:
   statuses:
-    - :type
\ No newline at end of file
+    - :type
+  merge_request_diff:
+    - :utf8_st_diffs
\ No newline at end of file
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index 6b69a653f12183b2d2ac7572b486f28a64606d8d..e9ee47fc090d5091bed7cfbccee80fa129c6006e 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -9,7 +9,7 @@ module Gitlab
       end
 
       def execute
-        if import_file && check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
+        if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
           project_tree.restored_project
         else
           raise Projects::ImportService::Error.new(@shared.errors.join(', '))
@@ -35,6 +35,10 @@ module Gitlab
                                                                         project: @project)
       end
 
+      def avatar_restorer
+        Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
+      end
+
       def repo_restorer
         Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
                                                shared: @shared,
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..008300bde4557bb3c4e9de762a0187606704b1c2
--- /dev/null
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -0,0 +1,110 @@
+module Gitlab
+  module ImportExport
+    # Generates a hash that conforms with http://apidock.com/rails/Hash/to_json
+    # and its peculiar options.
+    class JsonHashBuilder
+      def self.build(model_objects, attributes_finder)
+        new(model_objects, attributes_finder).build
+      end
+
+      def initialize(model_objects, attributes_finder)
+        @model_objects = model_objects
+        @attributes_finder = attributes_finder
+      end
+
+      def build
+        process_model_objects(@model_objects)
+      end
+
+      private
+
+      # Called when the model is actually a hash containing other relations (more models)
+      # Returns the config in the right format for calling +to_json+
+      #
+      # +model_object_hash+ - A model relationship such as:
+      #   {:merge_requests=>[:merge_request_diff, :notes]}
+      def process_model_objects(model_object_hash)
+        json_config_hash = {}
+        current_key = model_object_hash.keys.first
+
+        model_object_hash.values.flatten.each do |model_object|
+          @attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash }
+          handle_model_object(current_key, model_object, json_config_hash)
+        end
+
+        json_config_hash
+      end
+
+      # Creates or adds to an existing hash an individual model or list
+      #
+      # +current_key+ main model that will be a key in the hash
+      # +model_object+ model or list of models to include in the hash
+      # +json_config_hash+ the original hash containing the root model
+      def handle_model_object(current_key, model_object, json_config_hash)
+        model_or_sub_model = model_object.is_a?(Hash) ? process_model_objects(model_object) : model_object
+
+        if json_config_hash[current_key]
+          add_model_value(current_key, model_or_sub_model, json_config_hash)
+        else
+          create_model_value(current_key, model_or_sub_model, json_config_hash)
+        end
+      end
+
+      # Constructs a new hash that will hold the configuration for that particular object
+      # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+      #
+      # +current_key+ main model that will be a key in the hash
+      # +value+ existing model to be included in the hash
+      # +json_config_hash+ the original hash containing the root model
+      def create_model_value(current_key, value, json_config_hash)
+        parsed_hash = { include: value }
+        parse_hash(value, parsed_hash)
+
+        json_config_hash[current_key] = parsed_hash
+      end
+
+      # Calls attributes finder to parse the hash and add any attributes to it
+      #
+      # +value+ existing model to be included in the hash
+      # +parsed_hash+ the original hash
+      def parse_hash(value, parsed_hash)
+        @attributes_finder.parse(value) do |hash|
+          parsed_hash = { include: hash_or_merge(value, hash) }
+        end
+      end
+
+      # Adds new model configuration to an existing hash with key +current_key+
+      # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+      #
+      # +current_key+ main model that will be a key in the hash
+      # +value+ existing model to be included in the hash
+      # +json_config_hash+ the original hash containing the root model
+      def add_model_value(current_key, value, json_config_hash)
+        @attributes_finder.parse(value) { |hash| value = { value => hash } }
+
+        add_to_array(current_key, json_config_hash, value)
+      end
+
+      # Adds new model configuration to an existing hash with key +current_key+
+      # it creates a new array if it was previously a single value
+      #
+      # +current_key+ main model that will be a key in the hash
+      # +value+ existing model to be included in the hash
+      # +json_config_hash+ the original hash containing the root model
+      def add_to_array(current_key, json_config_hash, value)
+        old_values = json_config_hash[current_key][:include]
+
+        json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
+      end
+
+      # Construct a new hash or merge with an existing one a model configuration
+      # This is to fulfil +to_json+ requirements.
+      #
+      # +hash+ hash containing configuration generated mainly from +@attributes_finder+
+      # +value+ existing model to be included in the hash
+      def hash_or_merge(value, hash)
+        value.is_a?(Hash) ? value.merge(hash) : { value => hash }
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 051110c23cfddb9501675edf259cd27589ed9983..c7b3551b84c5cfdbf9cc081f7cd9bdff24389f72 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -47,7 +47,7 @@ module Gitlab
 
           relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
           relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s])
-          saved << restored_project.update_attribute(relation_key, relation_hash)
+          saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
         end
         saved.all?
       end
@@ -78,7 +78,7 @@ module Gitlab
         relation_key = relation.keys.first.to_s
         return if tree_hash[relation_key].blank?
 
-        tree_hash[relation_key].each do |relation_item|
+        [tree_hash[relation_key]].flatten.each do |relation_item|
           relation.values.flatten.each do |sub_relation|
             # We just use author to get the user ID, do not attempt to create an instance.
             next if sub_relation == :author
diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb
index 15f5dd31035ca9cd43efbddeb983f8faf030bd25..5021a1a14cebf8d546a7f83d312d68784a269d51 100644
--- a/lib/gitlab/import_export/reader.rb
+++ b/lib/gitlab/import_export/reader.rb
@@ -29,87 +29,12 @@ module Gitlab
       def build_hash(model_list)
         model_list.map do |model_objects|
           if model_objects.is_a?(Hash)
-            build_json_config_hash(model_objects)
+            Gitlab::ImportExport::JsonHashBuilder.build(model_objects, @attributes_finder)
           else
             @attributes_finder.find(model_objects)
           end
         end
       end
-
-      # Called when the model is actually a hash containing other relations (more models)
-      # Returns the config in the right format for calling +to_json+
-      # +model_object_hash+ - A model relationship such as:
-      #   {:merge_requests=>[:merge_request_diff, :notes]}
-      def build_json_config_hash(model_object_hash)
-        @json_config_hash = {}
-
-        model_object_hash.values.flatten.each do |model_object|
-          current_key = model_object_hash.keys.first
-
-          @attributes_finder.parse(current_key) { |hash| @json_config_hash[current_key] ||= hash }
-
-          handle_model_object(current_key, model_object)
-          process_sub_model(current_key, model_object) if model_object.is_a?(Hash)
-        end
-        @json_config_hash
-      end
-
-      # If the model is a hash, process the sub_models, which could also be hashes
-      # If there is a list, add to an existing array, otherwise use hash syntax
-      # +current_key+ main model that will be a key in the hash
-      # +model_object+ model or list of models to include in the hash
-      def process_sub_model(current_key, model_object)
-        sub_model_json = build_json_config_hash(model_object).dup
-        @json_config_hash.slice!(current_key)
-
-        if @json_config_hash[current_key] && @json_config_hash[current_key][:include]
-          @json_config_hash[current_key][:include] << sub_model_json
-        else
-          @json_config_hash[current_key] = { include: sub_model_json }
-        end
-      end
-
-      # Creates or adds to an existing hash an individual model or list
-      # +current_key+ main model that will be a key in the hash
-      # +model_object+ model or list of models to include in the hash
-      def handle_model_object(current_key, model_object)
-        if @json_config_hash[current_key]
-          add_model_value(current_key, model_object)
-        else
-          create_model_value(current_key, model_object)
-        end
-      end
-
-      # Constructs a new hash that will hold the configuration for that particular object
-      # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
-      # +current_key+ main model that will be a key in the hash
-      # +value+ existing model to be included in the hash
-      def create_model_value(current_key, value)
-        parsed_hash = { include: value }
-
-        @attributes_finder.parse(value) do |hash|
-          parsed_hash = { include: hash_or_merge(value, hash) }
-        end
-        @json_config_hash[current_key] = parsed_hash
-      end
-
-      # Adds new model configuration to an existing hash with key +current_key+
-      # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
-      # +current_key+ main model that will be a key in the hash
-      # +value+ existing model to be included in the hash
-      def add_model_value(current_key, value)
-        @attributes_finder.parse(value) { |hash| value = { value => hash } }
-        old_values = @json_config_hash[current_key][:include]
-        @json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
-      end
-
-      # Construct a new hash or merge with an existing one a model configuration
-      # This is to fulfil +to_json+ requirements.
-      # +value+ existing model to be included in the hash
-      # +hash+ hash containing configuration generated mainly from +@attributes_finder+
-      def hash_or_merge(value, hash)
-        value.is_a?(Hash) ? value.merge(hash) : { value => hash }
-      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 6ba25a31641bd87b844b30f3102a334caa3f9560..5e56b3d1aa7fd306277acb35fe0e76b79eb57dc8 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -13,6 +13,10 @@ module Gitlab
 
       BUILD_MODELS = %w[Ci::Build commit_status].freeze
 
+      IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
+
+      EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
+
       def self.create(*args)
         new(*args).create
       end
@@ -22,23 +26,35 @@ module Gitlab
         @relation_hash = relation_hash.except('id', 'noteable_id')
         @members_mapper = members_mapper
         @user = user
+        @imported_object_retries = 0
       end
 
       # Creates an object from an actual model with name "relation_sym" with params from
       # the relation_hash, updating references with new object IDs, mapping users using
       # the "members_mapper" object, also updating notes if required.
       def create
-        set_note_author if @relation_name == :notes
-        update_user_references
-        update_project_references
-        reset_ci_tokens if @relation_name == 'Ci::Trigger'
-        @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
+        setup_models
 
         generate_imported_object
       end
 
       private
 
+      def setup_models
+        if @relation_name == :notes
+          set_note_author
+
+          # attachment is deprecated and note uploads are handled by Markdown uploader
+          @relation_hash['attachment'] = nil
+        end
+
+        update_user_references
+        update_project_references
+        reset_ci_tokens if @relation_name == 'Ci::Trigger'
+        @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
+        set_st_diffs if @relation_name == :merge_request_diff
+      end
+
       def update_user_references
         USER_REFERENCES.each do |reference|
           if @relation_hash[reference]
@@ -111,10 +127,14 @@ module Gitlab
       end
 
       def imported_object
-        imported_object = relation_class.new(parsed_relation_hash)
-        yield(imported_object) if block_given?
-        imported_object.importing = true if imported_object.respond_to?(:importing)
-        imported_object
+        yield(existing_or_new_object) if block_given?
+        existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing)
+        existing_or_new_object
+      rescue ActiveRecord::RecordNotUnique
+        # as the operation is not atomic, retry in the unlikely scenario an INSERT is
+        # performed on the same object between the SELECT and the INSERT
+        @imported_object_retries += 1
+        retry if @imported_object_retries < IMPORTED_OBJECT_MAX_RETRIES
       end
 
       def update_note_for_missing_author(author_name)
@@ -129,6 +149,24 @@ module Gitlab
       def parsed_relation_hash
         @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
       end
+
+      def set_st_diffs
+        @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
+      end
+
+      def existing_or_new_object
+        # Only find existing records to avoid mapping tables such as milestones
+        # Otherwise always create the record, skipping the extra SELECT clause.
+        @existing_or_new_object ||= begin
+          if EXISTING_OBJECT_CHECK.include?(@relation_name)
+            existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
+            existing_object.assign_attributes(parsed_relation_hash)
+            existing_object
+          else
+            relation_class.new(parsed_relation_hash)
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index f84de652a572e602c7cb89b642eb712fef8f2e9f..6d9379acf25869458600edb6d4daf552a22fbd93 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -14,7 +14,7 @@ module Gitlab
 
         FileUtils.mkdir_p(path_to_repo)
 
-        git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle)
+        git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks
       rescue => e
         @shared.error(e)
         false
@@ -29,6 +29,16 @@ module Gitlab
       def path_to_repo
         @project.repository.path_to_repo
       end
+
+      def repo_restore_hooks
+        return true if wiki?
+
+        git_restore_hooks
+      end
+
+      def wiki?
+        @project.class.name == 'ProjectWiki'
+      end
     end
   end
 end
diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb
index d6f4fa5751055e8aaca02a95e835852fe616c059..62a2553675cd927dd434de381fcc0b32ea5e1bf2 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -1,6 +1,8 @@
 module Gitlab
   module ImportExport
     class UploadsSaver
+      include Gitlab::ImportExport::CommandLineUtil
+
       def initialize(project:, shared:)
         @project = project
         @shared = shared
@@ -17,12 +19,6 @@ module Gitlab
 
       private
 
-      def copy_files(source, destination)
-        FileUtils.mkdir_p(destination)
-        FileUtils.copy_entry(source, destination)
-        true
-      end
-
       def uploads_export_path
         File.join(@shared.export_path, 'uploads')
       end
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 8ce9d32abe043b615e957ee20ddc7a40a6248ff8..d7be50bd43725d8cee76cf04299b0cc3d7b80308 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,7 +1,7 @@
 module Gitlab
   module IncomingEmail
     class << self
-      FALLBACK_REPLY_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
+      FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
 
       def enabled?
         config.enabled && config.address
@@ -21,8 +21,8 @@ module Gitlab
         match[1]
       end
 
-      def key_from_fallback_reply_message_id(message_id)
-        match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX)
+      def key_from_fallback_message_id(mail_id)
+        match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX)
         return unless match
 
         match[1]
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 49f702f91f68e850da83cff3ac8f765b3214bef4..41fcd971c228c1341ba807c24e389d86d2a2c3c9 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -124,6 +124,11 @@ module Gitlab
       trans.action = action if trans
     end
 
+    # Returns the prefix to use for the name of a series.
+    def self.series_prefix
+      @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
+    end
+
     # When enabled this should be set before being used as the usual pattern
     # "@foo ||= bar" is _not_ thread-safe.
     if enabled?
@@ -136,8 +141,7 @@ module Gitlab
       end
     end
 
-    private
-
+    # Allow access from other metrics related middlewares
     def self.current_transaction
       Transaction.current
     end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index dcec7543c1398c8fde71cf6c888743ac892705ca..4b7a791e497245ae3b801ca9c2c77339c27559c9 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -9,14 +9,17 @@ module Gitlab
     #
     #     Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
     module Instrumentation
-      SERIES = 'method_calls'
-
       PROXY_IVAR = :@__gitlab_instrumentation_proxy
 
       def self.configure
         yield self
       end
 
+      # Returns the name of the series to use for storing method calls.
+      def self.series
+        @series ||= "#{Metrics.series_prefix}method_calls"
+      end
+
       # Instruments a class method.
       #
       # mod  - The module to instrument as a Module/Class.
@@ -141,15 +144,15 @@ module Gitlab
         # generated method _only_ accepts regular arguments if the underlying
         # method also accepts them.
         if method.arity == 0
-          args_signature = '&block'
+          args_signature = ''
         else
-          args_signature = '*args, &block'
+          args_signature = '*args'
         end
 
         proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
           def #{name}(#{args_signature})
             if trans = Gitlab::Metrics::Instrumentation.transaction
-              trans.measure_method(#{label.inspect}) { super }
+              trans.method_call_for(#{label.to_sym.inspect}).measure { super }
             else
               super
             end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index c048fe20ba7c8e0bbcddebf1d733f60ed90fb8b0..d3465e5ec196bdf1a6ce20fb45aef058f3302643 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -11,8 +11,8 @@ module Gitlab
       def initialize(name, series)
         @name = name
         @series = series
-        @real_time = 0.0
-        @cpu_time = 0.0
+        @real_time = 0
+        @cpu_time = 0
         @call_count = 0
       end
 
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 82c18bb108b6364d69ccba84af54ecc17986a4a0..287b7a83547656d023cdb7ce6a877a302d763918 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -35,12 +35,12 @@ module Gitlab
       if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
         def self.cpu_time
           Process.
-            clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f
+            clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
         end
       else
         def self.cpu_time
           Process.
-            clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f
+            clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
         end
       end
 
@@ -48,14 +48,14 @@ module Gitlab
       #
       # Returns the time as a Float.
       def self.real_time(precision = :millisecond)
-        Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f
+        Process.clock_gettime(Process::CLOCK_REALTIME, precision)
       end
 
       # Returns the current monotonic clock time in a given precision.
       #
       # Returns the time as a Float.
       def self.monotonic_time(precision = :millisecond)
-        Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f
+        Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
       end
     end
   end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index bded245da43de27432ecd1773ce9e8ad36cce053..968f32189505326c02c3c32a5a08498b83e46875 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -52,23 +52,16 @@ module Gitlab
       end
 
       def add_metric(series, values, tags = {})
-        @metrics << Metric.new("#{series_prefix}#{series}", values, tags)
+        @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
       end
 
-      # Measures the time it takes to execute a method.
-      #
-      # Multiple calls to the same method add up to the total runtime of the
-      # method.
-      #
-      # name - The full name of the method to measure (e.g. `User#sign_in`).
-      def measure_method(name, &block)
-        unless @methods[name]
-          series = "#{series_prefix}#{Instrumentation::SERIES}"
-
-          @methods[name] = MethodCall.new(name, series)
+      # Returns a MethodCall object for the given name.
+      def method_call_for(name)
+        unless method = @methods[name]
+          @methods[name] = method = MethodCall.new(name, Instrumentation.series)
         end
 
-        @methods[name].measure(&block)
+        method
       end
 
       def increment(name, value)
@@ -115,14 +108,6 @@ module Gitlab
 
         Metrics.submit_metrics(submit_hashes)
       end
-
-      def sidekiq?
-        Sidekiq.server?
-      end
-
-      def series_prefix
-        sidekiq? ? 'sidekiq_' : 'rails_'
-      end
     end
   end
 end
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8130e55351e2b5470669bcce845e8a3ccf96e428
--- /dev/null
+++ b/lib/gitlab/request_profiler.rb
@@ -0,0 +1,19 @@
+require 'fileutils'
+
+module Gitlab
+  module RequestProfiler
+    PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
+
+    def profile_token
+      Rails.cache.fetch('profile-token') do
+        Devise.friendly_token
+      end
+    end
+    module_function :profile_token
+
+    def remove_all_profiles
+      FileUtils.rm_rf(PROFILES_DIR)
+    end
+    module_function :remove_all_profiles
+  end
+end
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4e787dc0656c6888f9798a8a1ef17faabf321909
--- /dev/null
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -0,0 +1,54 @@
+require 'ruby-prof'
+require 'gitlab/request_profiler'
+
+module Gitlab
+  module RequestProfiler
+    class Middleware
+      def initialize(app)
+        @app = app
+      end
+
+      def call(env)
+        if profile?(env)
+          call_with_profiling(env)
+        else
+          @app.call(env)
+        end
+      end
+
+      def profile?(env)
+        header_token = env['HTTP_X_PROFILE_TOKEN']
+        return unless header_token.present?
+
+        profile_token = RequestProfiler.profile_token
+        return unless profile_token.present?
+
+        header_token == profile_token
+      end
+
+      def call_with_profiling(env)
+        ret = nil
+        result = RubyProf::Profile.profile do
+          ret = catch(:warden) do
+            @app.call(env)
+          end
+        end
+
+        printer   = RubyProf::CallStackPrinter.new(result)
+        file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
+        file_path = "#{PROFILES_DIR}/#{file_name}"
+
+        FileUtils.mkdir_p(PROFILES_DIR)
+        File.open(file_path, 'wb') do |file|
+          printer.print(file)
+        end
+
+        if ret.is_a?(Array)
+          ret
+        else
+          throw(:warden, ret)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f89d56903efe5bb5fa0ad0dd025b69f710c19e7e
--- /dev/null
+++ b/lib/gitlab/request_profiler/profile.rb
@@ -0,0 +1,43 @@
+module Gitlab
+  module RequestProfiler
+    class Profile
+      attr_reader :name, :time, :request_path
+
+      alias_method :to_param, :name
+
+      def self.all
+        Dir["#{PROFILES_DIR}/*.html"].map do |path|
+          new(File.basename(path))
+        end
+      end
+
+      def self.find(name)
+        name_dup = name.dup
+        name_dup << '.html' unless name.end_with?('.html')
+
+        file_path = "#{PROFILES_DIR}/#{name_dup}"
+        return unless File.exist?(file_path)
+
+        new(name_dup)
+      end
+
+      def initialize(name)
+        @name = name
+
+        set_attributes
+      end
+
+      def content
+        File.read("#{PROFILES_DIR}/#{name}")
+      end
+
+      private
+
+      def set_attributes
+        _, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
+        @request_path      = path.tr('|', '/')
+        @time              = Time.at(timestamp.to_i).utc
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/sidekiq_middleware/request_store_middleware.rb b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b1fa0e3cb4e21fceccebe852c30ed931bd71215e
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
@@ -0,0 +1,13 @@
+module Gitlab
+  module SidekiqMiddleware
+    class RequestStoreMiddleware
+      def call(worker, job, queue)
+        RequestStore.begin!
+        yield
+      ensure
+        RequestStore.end!
+        RequestStore.clear!
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 83f91de810cdb0a22b23c14c1a304b9257016617..d4020af76f9af2f87f4c324495091eee5d41a345 100644
--- a/lib/gitlab/themes.rb
+++ b/lib/gitlab/themes.rb
@@ -2,6 +2,8 @@ module Gitlab
   # Module containing GitLab's application theme definitions and helper methods
   # for accessing them.
   module Themes
+    extend self
+
     # Theme ID used when no `default_theme` configuration setting is provided.
     APPLICATION_DEFAULT = 2
 
@@ -22,7 +24,7 @@ module Gitlab
     # classes that might be applied to the `body` element
     #
     # Returns a String
-    def self.body_classes
+    def body_classes
       THEMES.collect(&:css_class).uniq.join(' ')
     end
 
@@ -33,26 +35,26 @@ module Gitlab
     # id - Integer ID
     #
     # Returns a Theme
-    def self.by_id(id)
+    def by_id(id)
       THEMES.detect { |t| t.id == id } || default
     end
 
     # Returns the number of defined Themes
-    def self.count
+    def count
       THEMES.size
     end
 
     # Get the default Theme
     #
     # Returns a Theme
-    def self.default
+    def default
       by_id(default_id)
     end
 
     # Iterate through each Theme
     #
     # Yields the Theme object
-    def self.each(&block)
+    def each(&block)
       THEMES.each(&block)
     end
 
@@ -61,7 +63,7 @@ module Gitlab
     # user - User record
     #
     # Returns a Theme
-    def self.for_user(user)
+    def for_user(user)
       if user
         by_id(user.theme_id)
       else
@@ -71,7 +73,7 @@ module Gitlab
 
     private
 
-    def self.default_id
+    def default_id
       id = Gitlab.config.gitlab.default_theme.to_i
 
       # Prevent an invalid configuration setting from causing an infinite loop
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index c0f85e9b3a85a509be84b9ece06c8ddc364d1160..3a69027368fdbd2c32c08f905381e6f9ee31e4e2 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -29,8 +29,9 @@ module Gitlab
     def can_push_to_branch?(ref)
       return false unless user
 
-      if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
-        user.can?(:push_code_to_protected_branches, project)
+      if project.protected_branch?(ref)
+        access_levels = project.protected_branches.matching(ref).map(&:push_access_level)
+        access_levels.any? { |access_level| access_level.check_access(user) }
       else
         user.can?(:push_code, project)
       end
@@ -39,8 +40,9 @@ module Gitlab
     def can_merge_to_branch?(ref)
       return false unless user
 
-      if project.protected_branch?(ref) && !project.developers_can_merge_to_protected_branch?(ref)
-        user.can?(:push_code_to_protected_branches, project)
+      if project.protected_branch?(ref)
+        access_levels = project.protected_branches.matching(ref).map(&:merge_access_level)
+        access_levels.any? { |access_level| access_level.check_access(user) }
       else
         user.can?(:push_code, project)
       end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 6aeb49c02196e5534f6159da131d56ea3431cab3..c6826a09bd285cf5e9b543f425460a22ebf84c18 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -4,6 +4,7 @@ require 'json'
 module Gitlab
   class Workhorse
     SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
+    VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'
 
     class << self
       def git_http_ok(repository, user)
@@ -75,6 +76,11 @@ module Gitlab
         ]
       end
 
+      def version
+        path = Rails.root.join(VERSION_FILE)
+        path.readable? ? path.read.chomp : 'unknown'
+      end
+
       protected
 
       def encode(hash)
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
index 8ddc3511293e5dfd7419bb368dd022cbbd604eda..068a95790c0a61f2da7372f28bc89b18a0774d0a 100644
--- a/lib/repository_cache.rb
+++ b/lib/repository_cache.rb
@@ -1,14 +1,15 @@
 # Interface to the Redis-backed cache store used by the Repository model
 class RepositoryCache
-  attr_reader :namespace, :backend
+  attr_reader :namespace, :backend, :project_id
 
-  def initialize(namespace, backend = Rails.cache)
+  def initialize(namespace, project_id, backend = Rails.cache)
     @namespace = namespace
     @backend = backend
+    @project_id = project_id
   end
 
   def cache_key(type)
-    "#{type}:#{namespace}"
+    "#{type}:#{namespace}:#{project_id}"
   end
 
   def expire(key)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index f818dc78d34c7f3f326ed41de2cda3d5f8a6cb3b..4edfd0150740b2d28483f9767d1014d339ae841d 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -18,7 +18,7 @@ module Rouge
           is_first = false
 
           yield %(<span id="LC#{@line_number}" class="line">)
-          line.each { |token, value| yield span(token, value) }
+          line.each { |token, value| yield span(token, value.chomp) }
           yield %(</span>)
 
           @line_number += 1
diff --git a/lib/tasks/downtime_check.rake b/lib/tasks/downtime_check.rake
new file mode 100644
index 0000000000000000000000000000000000000000..afe5d42910c281140719c5f9809f003a8f5c56a6
--- /dev/null
+++ b/lib/tasks/downtime_check.rake
@@ -0,0 +1,12 @@
+desc 'Checks if migrations in a branch require downtime'
+task downtime_check: :environment do
+  if defined?(Gitlab::License)
+    repo = 'gitlab-ee'
+  else
+    repo = 'gitlab-ce'
+  end
+
+  `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
+
+  Rake::Task['gitlab:db:downtime_check'].invoke('FETCH_HEAD')
+end
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index e930ace1041ad446e2203e136314b6019e293d2f..993112aee3be341125b045c64e91d7747f1ff8d4 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -4,7 +4,7 @@ namespace :gemojione do
     require 'digest/sha2'
     require 'json'
 
-    dir = Gemojione.index.images_path
+    dir = Gemojione.images_path
     digests = []
     aliases = Hash.new { |hash, key| hash[key] = [] }
     aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
@@ -50,9 +50,14 @@ namespace :gemojione do
     SIZE   = 20
     RETINA = SIZE * 2
 
+    # Update these values to the width and height of the spritesheet when
+    # new emoji are added.
+    SPRITESHEET_WIDTH = 860
+    SPRITESHEET_HEIGHT = 840
+
     Dir.mktmpdir do |tmpdir|
       # Copy the Gemojione assets to the temporary folder for resizing
-      FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+      FileUtils.cp_r(Gemojione.images_path, tmpdir)
 
       Dir.chdir(tmpdir) do
         Dir["**/*.png"].each do |png|
@@ -64,7 +69,7 @@ namespace :gemojione do
 
       # Combine the resized assets into a packed sprite and re-generate the SCSS
       SpriteFactory.cssurl = "image-url('$IMAGE')"
-      SpriteFactory.run!(File.join(tmpdir, 'images'), {
+      SpriteFactory.run!(File.join(tmpdir, 'png'), {
         output_style: style_path,
         output_image: "app/assets/images/emoji.png",
         selector:     '.emoji-',
@@ -97,7 +102,7 @@ namespace :gemojione do
                  only screen and (min-resolution: 192dpi),
                  only screen and (min-resolution: 2dppx) {
             background-image: image-url('emoji@2x.png');
-            background-size: 840px 820px;
+            background-size: #{SPRITESHEET_WIDTH}px #{SPRITESHEET_HEIGHT}px;
           }
         }
         CSS
@@ -107,7 +112,7 @@ namespace :gemojione do
     # Now do it again but for Retina
     Dir.mktmpdir do |tmpdir|
       # Copy the Gemojione assets to the temporary folder for resizing
-      FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+      FileUtils.cp_r(Gemojione.images_path, tmpdir)
 
       Dir.chdir(tmpdir) do
         Dir["**/*.png"].each do |png|
@@ -116,7 +121,7 @@ namespace :gemojione do
       end
 
       # Combine the resized assets into a packed sprite and re-generate the SCSS
-      SpriteFactory.run!(File.join(tmpdir, 'images'), {
+      SpriteFactory.run!(File.join(tmpdir), {
         output_image: "app/assets/images/emoji@2x.png",
         style:        false,
         nocomments:   true,
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index e9a4e37ec484b9063ee0d4aa6b3d3eab5b5d0305..60f4636e7379bedfec3f859502ce9b169c3de3ba 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -784,7 +784,7 @@ namespace :gitlab do
       servers.each do |server|
         puts "Server: #{server}"
         Gitlab::LDAP::Adapter.open(server) do |adapter|
-          users = adapter.users(adapter.config.uid, '*', 100)
+          users = adapter.users(adapter.config.uid, '*', limit)
           users.each do |user|
             puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
           end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 7230b9485bea741e5d64c6429321c680adb12fbd..7c96bc864ce2363159e3abb25078da59c2daf00f 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -25,6 +25,10 @@ namespace :gitlab do
     desc 'Drop all tables'
     task :drop_tables => :environment do
       connection = ActiveRecord::Base.connection
+
+      # If MySQL, turn off foreign key checks
+      connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
+
       tables = connection.tables
       tables.delete 'schema_migrations'
       # Truncate schema_migrations to ensure migrations re-run
@@ -35,6 +39,9 @@ namespace :gitlab do
       # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
       # Add `IF EXISTS` because cascade could have already deleted a table.
       tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
+
+      # If MySQL, re-enable foreign key checks
+      connection.execute('SET FOREIGN_KEY_CHECKS=1') if Gitlab::Database.mysql?
     end
 
     desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
@@ -46,5 +53,20 @@ namespace :gitlab do
         Rake::Task['db:seed_fu'].invoke
       end
     end
+
+    desc 'Checks if migrations require downtime or not'
+    task :downtime_check, [:ref] => :environment do |_, args|
+      abort 'You must specify a Git reference to compare with' unless args[:ref]
+
+      require 'shellwords'
+
+      ref = Shellwords.escape(args[:ref])
+
+      migrations = `git diff #{ref}.. --name-only -- db/migrate`.lines.
+        map { |file| Rails.root.join(file.strip).to_s }.
+        select { |file| File.file?(file) }
+
+      Gitlab::DowntimeCheck.new.check_and_print(migrations)
+    end
   end
 end
diff --git a/lib/tasks/gitlab/track_deployment.rake b/lib/tasks/gitlab/track_deployment.rake
new file mode 100644
index 0000000000000000000000000000000000000000..84aa2e8507a16c67b7c3ff2437b99974d4849e36
--- /dev/null
+++ b/lib/tasks/gitlab/track_deployment.rake
@@ -0,0 +1,9 @@
+namespace :gitlab do
+  desc 'GitLab | Tracks a deployment in GitLab Performance Monitoring'
+  task track_deployment: :environment do
+    metric = Gitlab::Metrics::Metric.
+      new('deployments', version: Gitlab::VERSION)
+
+    Gitlab::Metrics.submit_metrics([metric.to_hash])
+  end
+end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index 21c0e5f1d41c398765f2823cf0b3ec972dd4ae60..d3dcbd2c29b0861d98737cf7a6efe3be532eacc9 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -7,5 +7,5 @@ end
 
 unless Rails.env.production?
   desc "GitLab | Run all tests on CI with simplecov"
-  task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec]
+  task test_ci: [:rubocop, :brakeman, :teaspoon, :spinach, :spec]
 end
diff --git a/public/404.html b/public/404.html
index 4862770cc2a7d55621fded6960c4c32e92040f0a..92b7f4da0b9b577e722f093589b2b033e66b66d7 100644
--- a/public/404.html
+++ b/public/404.html
@@ -1,55 +1,65 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
   <title>The page you're looking for could not be found (404)</title>
   <style>
-      body {
-        color: #666;
-        text-align: center;
-        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-        margin: 0;
-        width: 800px;
-        margin: auto;
-        font-size: 14px;
-      }
+    body {
+      color: #666;
+      text-align: center;
+      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      margin: auto;
+      font-size: 14px;
+    }
 
-      h1 {
-        font-size: 56px;
-        line-height: 100px;
-        font-weight: normal;
-        color: #456;
-      }
+    h1 {
+      font-size: 56px;
+      line-height: 100px;
+      font-weight: normal;
+      color: #456;
+    }
 
-      h2 {
-        font-size: 24px;
-        color: #666;
-        line-height: 1.5em;
-      }
+    h2 {
+      font-size: 24px;
+      color: #666;
+      line-height: 1.5em;
+    }
 
-      h3 {
-        color: #456;
-        font-size: 20px;
-        font-weight: normal;
-        line-height: 28px;
-      }
+    h3 {
+      color: #456;
+      font-size: 20px;
+      font-weight: normal;
+      line-height: 28px;
+    }
 
-      hr {
-        margin: 18px 0;
-        border: 0;
-        border-top: 1px solid #EEE;
-        border-bottom: 1px solid white;
-      }
+    hr {
+      max-width: 800px;
+      margin: 18px auto;
+      border: 0;
+      border-top: 1px solid #EEE;
+      border-bottom: 1px solid white;
+    }
+
+    img {
+      max-width: 40vw;
+    }
+
+    .container {
+      margin: auto 20px;
+    }
   </style>
 </head>
 
 <body>
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     404
   </h1>
-  <h3>The page you're looking for could not be found.</h3>
-  <hr/>
-  <p>Make sure the address is correct and that the page hasn't moved.</p>
-  <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+  <div class="container">
+    <h3>The page you're looking for could not be found.</h3>
+    <hr />
+    <p>Make sure the address is correct and that the page hasn't moved.</p>
+    <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+  </div>
 </body>
 </html>
diff --git a/public/422.html b/public/422.html
index 055b0bde165159e82a4aefc50872b710ae6025bb..f625f8a33b7558d12f8c41430db97646a60808a7 100644
--- a/public/422.html
+++ b/public/422.html
@@ -1,55 +1,65 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
   <title>The change you requested was rejected (422)</title>
   <style>
     body {
       color: #666;
-       text-align: center;
-       font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-       margin: 0;
-       width: 800px;
-       margin: auto;
-       font-size: 14px;
-     }
+      text-align: center;
+      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      margin: auto;
+      font-size: 14px;
+    }
 
-     h1 {
-       font-size: 56px;
-       line-height: 100px;
-       font-weight: normal;
-       color: #456;
-     }
+    h1 {
+      font-size: 56px;
+      line-height: 100px;
+      font-weight: normal;
+      color: #456;
+    }
 
-     h2 {
-       font-size: 24px;
-       color: #666;
-       line-height: 1.5em;
-     }
+    h2 {
+      font-size: 24px;
+      color: #666;
+      line-height: 1.5em;
+    }
+
+    h3 {
+      color: #456;
+      font-size: 20px;
+      font-weight: normal;
+      line-height: 28px;
+    }
+
+    hr {
+      max-width: 800px;
+      margin: 18px auto;
+      border: 0;
+      border-top: 1px solid #EEE;
+      border-bottom: 1px solid white;
+    }
 
-     h3 {
-       color: #456;
-       font-size: 20px;
-       font-weight: normal;
-       line-height: 28px;
-     }
+    img {
+      max-width: 40vw;
+    }
 
-     hr {
-       margin: 18px 0;
-       border: 0;
-       border-top: 1px solid #EEE;
-       border-bottom: 1px solid white;
-     }
-   </style>
+    .container {
+      margin: auto 20px;
+    }
+  </style>
 </head>
 
 <body>
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     422
   </h1>
-  <h3>The change you requested was rejected.</h3>
-  <hr />
-  <p>Make sure you have access to the thing you tried to change.</p>
-  <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+  <div class="container">
+    <h3>The change you requested was rejected.</h3>
+    <hr />
+    <p>Make sure you have access to the thing you tried to change.</p>
+    <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+  </div>
 </body>
 </html>
diff --git a/public/500.html b/public/500.html
index 3d59d1392f5ee7b0be736c595971a9ddb7d3cc1a..d76c66ba92a1aadc0a417d1bda7ac922a8011e71 100644
--- a/public/500.html
+++ b/public/500.html
@@ -1,54 +1,65 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
   <title>Something went wrong (500)</title>
   <style>
-     body {
-       color: #666;
-       text-align: center;
-       font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-       margin: 0;
-       width: 800px;
-       margin: auto;
-       font-size: 14px;
-     }
+    body {
+      color: #666;
+      text-align: center;
+      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      margin: auto;
+      font-size: 14px;
+    }
 
-     h1 {
-       font-size: 56px;
-       line-height: 100px;
-       font-weight: normal;
-       color: #456;
-     }
+    h1 {
+      font-size: 56px;
+      line-height: 100px;
+      font-weight: normal;
+      color: #456;
+    }
 
-     h2 {
-       font-size: 24px;
-       color: #666;
-       line-height: 1.5em;
-     }
+    h2 {
+      font-size: 24px;
+      color: #666;
+      line-height: 1.5em;
+    }
 
-     h3 {
-       color: #456;
-       font-size: 20px;
-       font-weight: normal;
-       line-height: 28px;
-     }
+    h3 {
+      color: #456;
+      font-size: 20px;
+      font-weight: normal;
+      line-height: 28px;
+    }
 
-     hr {
-      margin: 18px 0;
+    hr {
+      max-width: 800px;
+      margin: 18px auto;
       border: 0;
       border-top: 1px solid #EEE;
       border-bottom: 1px solid white;
     }
+
+    img {
+      max-width: 40vw;
+    }
+
+    .container {
+      margin: auto 20px;
+    }
   </style>
 </head>
+
 <body>
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     500
   </h1>
-  <h3>Whoops, something went wrong on our end.</h3>
-  <hr/>
-  <p>Try refreshing the page, or going back and attempting the action again.</p>
-  <p>Please contact your GitLab administrator if this problem persists.</p>
+  <div class="container">
+    <h3>Whoops, something went wrong on our end.</h3>
+    <hr />
+    <p>Try refreshing the page, or going back and attempting the action again.</p>
+    <p>Please contact your GitLab administrator if this problem persists.</p>
+  </div>
 </body>
 </html>
diff --git a/public/502.html b/public/502.html
index 67dfd8a27438c6846b7b932f4bbdf50dc7ab3d47..1a3c7efc7696256e850b77ac87dd27b2a8cbb15f 100644
--- a/public/502.html
+++ b/public/502.html
@@ -1,14 +1,13 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
   <title>GitLab is not responding (502)</title>
   <style>
     body {
       color: #666;
       text-align: center;
       font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-      margin: 0;
-      width: 800px;
       margin: auto;
       font-size: 14px;
     }
@@ -34,21 +33,33 @@
     }
 
     hr {
-      margin: 18px 0;
+      max-width: 800px;
+      margin: 18px auto;
       border: 0;
       border-top: 1px solid #EEE;
       border-bottom: 1px solid white;
     }
+
+    img {
+      max-width: 40vw;
+    }
+
+    .container {
+      margin: auto 20px;
+    }
   </style>
 </head>
+
 <body>
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     502
   </h1>
-  <h3>Whoops, GitLab is taking too much time to respond.</h3>
-  <hr/>
-  <p>Try refreshing the page, or going back and attempting the action again.</p>
-  <p>Please contact your GitLab administrator if this problem persists.</p>
+  <div class="container">
+    <h3>Whoops, GitLab is taking too much time to respond.</h3>
+    <hr />
+    <p>Try refreshing the page, or going back and attempting the action again.</p>
+    <p>Please contact your GitLab administrator if this problem persists.</p>
+  </div>
 </body>
 </html>
diff --git a/public/503.html b/public/503.html
index 6ab1185658df2be4e55608ef700b11cc2b593d6c..c1c4e3ffdb8e85e299a9e7699adc1f40004ac7c7 100644
--- a/public/503.html
+++ b/public/503.html
@@ -1,14 +1,13 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
   <title>GitLab is not responding (503)</title>
   <style>
     body {
       color: #666;
       text-align: center;
       font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-      margin: 0;
-      width: 800px;
       margin: auto;
       font-size: 14px;
     }
@@ -34,21 +33,33 @@
     }
 
     hr {
-      margin: 18px 0;
+      max-width: 800px;
+      margin: 18px auto;
       border: 0;
       border-top: 1px solid #EEE;
       border-bottom: 1px solid white;
     }
+
+    img {
+      max-width: 40vw;
+    }
+
+    .container {
+      margin: auto 20px;
+    }
   </style>
 </head>
+
 <body>
   <h1>
-    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo"/><br />
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
     503
   </h1>
-  <h3>Whoops, GitLab is currently unavailable.</h3>
-  <hr/>
-  <p>Try refreshing the page, or going back and attempting the action again.</p>
-  <p>Please contact your GitLab administrator if this problem persists.</p>
+  <div class="container">
+    <h3>Whoops, GitLab is currently unavailable.</h3>
+    <hr />
+    <p>Try refreshing the page, or going back and attempting the action again.</p>
+    <p>Please contact your GitLab administrator if this problem persists.</p>
+  </div>
 </body>
 </html>
diff --git a/public/deploy.html b/public/deploy.html
index 48976dacf41f89a8f1f90c99fb5220c68b70b5bb..142472b6c35f716812f294376126939c614f26ab 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -1,54 +1,64 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>Deploy in progress</title>
-    <style>
-     body {
-        color: #666;
-        text-align: center;
-        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-        margin: 0;
-        width: 800px;
-        margin: auto;
-        font-size: 14px;
-      }
+<head>
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+  <title>Deploy in progress</title>
+  <style>
+    body {
+      color: #666;
+      text-align: center;
+      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      margin: auto;
+      font-size: 14px;
+    }
 
-      h1 {
-        font-size: 56px;
-        line-height: 100px;
-        font-weight: normal;
-        color: #456;
-      }
+    h1 {
+      font-size: 56px;
+      line-height: 100px;
+      font-weight: normal;
+      color: #456;
+    }
 
-      h2 {
-        font-size: 24px;
-        color: #666;
-        line-height: 1.5em;
-      }
+    h2 {
+      font-size: 24px;
+      color: #666;
+      line-height: 1.5em;
+    }
 
-      h3 {
-        color: #456;
-        font-size: 20px;
-        font-weight: normal;
-        line-height: 28px;
-      }
+    h3 {
+      color: #456;
+      font-size: 20px;
+      font-weight: normal;
+      line-height: 28px;
+    }
 
-      hr {
-        margin: 18px 0;
-        border: 0;
-        border-top: 1px solid #EEE;
-        border-bottom: 1px solid white;
-      }
-    </style>
-  </head>
+    hr {
+      max-width: 800px;
+      margin: 18px auto;
+      border: 0;
+      border-top: 1px solid #EEE;
+      border-bottom: 1px solid white;
+    }
 
-  <body>
-    <h1>
-      <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
-      Deploy in progress
-    </h1>
+    img {
+      max-width: 40vw;
+    }
+
+    .container {
+      margin: auto 20px;
+    }
+  </style>
+</head>
+
+<body>
+  <h1>
+    <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
+    Deploy in progress
+  </h1>
+  <div class="container">
     <h3>Please try again in a few minutes.</h3>
-    <hr/>
+    <hr />
     <p>Please contact your GitLab administrator if this problem persists.</p>
-  </body>
+  </div>
+</body>
 </html>
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
new file mode 100755
index 0000000000000000000000000000000000000000..65f93f8830b7a7a07c7b02345b1e9cccbf26cff5
--- /dev/null
+++ b/scripts/merge-simplecov
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+require_relative '../spec/simplecov_env'
+SimpleCovEnv.configure_profile
+
+module SimpleCov
+  module ResultMerger
+    class << self
+      def resultset_files
+        Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
+      end
+
+      def resultset_hashes
+        resultset_files.map do |path|
+          begin
+            JSON.parse(File.read(path))
+          rescue
+            {}
+          end
+        end
+      end
+
+      def resultset
+        resultset_hashes.reduce({}, :merge)
+      end
+    end
+  end
+end
+
+SimpleCov::ResultMerger.merged_result.format!
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 60c654f622d33d46731c24c7a42e37d74591f725..ed0b7f9e240d07f8f6f4e446e12cb641cb0dd1ec 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -163,4 +163,17 @@ describe AutocompleteController do
       expect(body.collect { |u| u['id'] }).not_to include(99999)
     end
   end
+
+  context 'skip_users parameter included' do
+    before { sign_in(user) }
+
+    it 'skips the user IDs passed' do
+      get(:users, skip_users: [user, user2].map(&:id))
+
+      other_user_ids = [non_member, project.owner, project.creator].map(&:id)
+      response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
+
+      expect(response_user_ids).to contain_exactly(*other_user_ids)
+    end
+  end
 end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 267d511c2dbca06342b188b0c7fa2dd3533b8a38..33c75e7584f39ec81dfad14b7a6ade27101933f0 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -36,7 +36,7 @@ describe HelpController do
       context 'when requested file exists' do
         it 'renders the raw file' do
           get :show,
-              path: 'workflow/protected_branches/protected_branches1',
+              path: 'user/project/img/labels_filter',
               format: :png
           expect(response).to be_success
           expect(response.content_type).to eq 'image/png'
@@ -63,4 +63,13 @@ describe HelpController do
       end
     end
   end
+
+  describe 'GET #ui' do
+    context 'for UI Development Kit' do
+      it 'renders found' do
+        get :ui
+        expect(response).to have_http_status(200)
+      end
+    end
+  end
 end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 3001d32e719bd7efd7276f63e140437aec68e227..df902da86f8f765bb7fc32ec2046583ba92034bd 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -24,15 +24,6 @@ describe Projects::CommitController do
       get :show, params.merge(extra_params)
     end
 
-    let(:project) { create(:project) }
-
-    before do
-      user = create(:user)
-      project.team << [user, :master]
-
-      sign_in(user)
-    end
-
     context 'with valid id' do
       it 'responds with 200' do
         go(id: commit.id)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..768105cae9580ed9d93add1448ebfaf968a76dbc
--- /dev/null
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::EnvironmentsController do
+  let(:environment) { create(:environment) }
+  let(:project)     { environment.project }
+  let(:user)        { create(:user) }
+
+  before do
+    project.team << [user, :master]
+
+    sign_in(user)
+  end
+
+  describe 'GET show' do
+    context 'with valid id' do
+      it 'responds with a status code 200' do
+        get :show, environment_params
+
+        expect(response).to be_ok
+      end
+    end
+
+    context 'with invalid id' do
+      it 'responds with a status code 404' do
+        params = environment_params
+        params[:id] = 12345
+        get :show, params
+
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'GET edit' do
+    it 'responds with a status code 200' do
+      get :edit, environment_params
+
+      expect(response).to be_ok
+    end
+  end
+
+  describe 'PATCH #update' do
+    it 'responds with a 302' do
+      patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' })
+      patch :update, patch_params
+
+      expect(response).to have_http_status(302)
+    end
+  end
+
+  def environment_params
+    {
+      namespace_id: project.namespace,
+      project_id: project,
+      id: environment.id
+    }
+  end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 7cf09fa4a4a4687173eb1a0fe80151c017f9b85e..ec820de3d09a330b7a22595e5d05fbf4dbb63b60 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -6,37 +6,65 @@ describe Projects::IssuesController do
   let(:issue)   { create(:issue, project: project) }
 
   describe "GET #index" do
-    before do
-      sign_in(user)
-      project.team << [user, :developer]
-    end
+    context 'external issue tracker' do
+      it 'redirects to the external issue tracker' do
+        external = double(issues_url: 'https://example.com/issues')
+        allow(project).to receive(:external_issue_tracker).and_return(external)
+        controller.instance_variable_set(:@project, project)
 
-    it "returns index" do
-      get :index, namespace_id: project.namespace.path, project_id: project.path
+        get :index, namespace_id: project.namespace.path, project_id: project
 
-      expect(response).to have_http_status(200)
+        expect(response).to redirect_to('https://example.com/issues')
+      end
     end
 
-    it "return 301 if request path doesn't match project path" do
-      get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+    context 'internal issue tracker' do
+      before do
+        sign_in(user)
+        project.team << [user, :developer]
+      end
 
-      expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
-    end
+      it "returns index" do
+        get :index, namespace_id: project.namespace.path, project_id: project.path
+
+        expect(response).to have_http_status(200)
+      end
 
-    it "returns 404 when issues are disabled" do
-      project.issues_enabled = false
-      project.save
+      it "return 301 if request path doesn't match project path" do
+        get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+
+        expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+      end
+
+      it "returns 404 when issues are disabled" do
+        project.issues_enabled = false
+        project.save
+
+        get :index, namespace_id: project.namespace.path, project_id: project.path
+        expect(response).to have_http_status(404)
+      end
 
-      get :index, namespace_id: project.namespace.path, project_id: project.path
-      expect(response).to have_http_status(404)
+      it "returns 404 when external issue tracker is enabled" do
+        controller.instance_variable_set(:@project, project)
+        allow(project).to receive(:default_issues_tracker?).and_return(false)
+
+        get :index, namespace_id: project.namespace.path, project_id: project.path
+        expect(response).to have_http_status(404)
+      end
     end
+  end
+
+  describe 'GET #new' do
+    context 'external issue tracker' do
+      it 'redirects to the external issue tracker' do
+        external = double(new_issue_path: 'https://example.com/issues/new')
+        allow(project).to receive(:external_issue_tracker).and_return(external)
+        controller.instance_variable_set(:@project, project)
 
-    it "returns 404 when external issue tracker is enabled" do
-      controller.instance_variable_set(:@project, project)
-      allow(project).to receive(:default_issues_tracker?).and_return(false)
+        get :new, namespace_id: project.namespace.path, project_id: project
 
-      get :index, namespace_id: project.namespace.path, project_id: project.path
-      expect(response).to have_http_status(404)
+        expect(response).to redirect_to('https://example.com/issues/new')
+      end
     end
   end
 
@@ -243,6 +271,37 @@ describe Projects::IssuesController do
     end
   end
 
+  describe 'POST #create' do
+    context 'Akismet is enabled' do
+      before do
+        allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true)
+        allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true)
+      end
+
+      def post_spam_issue
+        sign_in(user)
+        spam_project = create(:empty_project, :public)
+        post :create, {
+          namespace_id: spam_project.namespace.to_param,
+          project_id: spam_project.to_param,
+          issue: { title: 'Spam Title', description: 'Spam lives here' }
+        }
+      end
+
+      it 'rejects an issue recognized as spam' do
+        expect{ post_spam_issue }.not_to change(Issue, :count)
+        expect(response).to render_template(:new)
+      end
+
+      it 'creates a spam log' do
+        post_spam_issue
+        spam_logs = SpamLog.all
+        expect(spam_logs.count).to eq(1)
+        expect(spam_logs[0].title).to eq('Spam Title')
+      end
+    end
+  end
+
   describe "DELETE #destroy" do
     context "when the user is a developer" do
       before { sign_in(user) }
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a6995145cc19a8a7171adf26634f90257b1157e1
--- /dev/null
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::TagsController do
+  let(:project) { create(:project, :public) }
+  let!(:release) { create(:release, project: project) }
+  let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
+
+  describe 'GET index' do
+    before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+
+    it 'returns the tags for the page' do
+      expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
+    end
+
+    it 'returns releases matching those tags' do
+      expect(assigns(:releases)).to include(release)
+      expect(assigns(:releases)).not_to include(invalid_release)
+    end
+  end
+end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 0893ee89f6af5cdd084198aca9a0132bcd280c88..71d0e4be834fb99462cb5d9b0708647488df3f9a 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -14,9 +14,9 @@ describe Projects::UploadsController do
 
     context "without params['file']" do
       it "returns an error" do
-        post :create, 
+        post :create,
           namespace_id: project.namespace.to_param,
-          project_id: project.to_param, 
+          project_id: project.to_param,
           format: :json
         expect(response).to have_http_status(422)
       end
@@ -34,23 +34,21 @@ describe Projects::UploadsController do
       it 'returns a content with original filename, new link, and correct type.' do
         expect(response.body).to match '\"alt\":\"rails_sample\"'
         expect(response.body).to match "\"url\":\"/uploads"
-        expect(response.body).to match '\"is_image\":true'
       end
     end
 
     context 'with valid non-image file' do
       before do
-        post :create, 
+        post :create,
           namespace_id: project.namespace.to_param,
-          project_id: project.to_param, 
-          file: txt, 
+          project_id: project.to_param,
+          file: txt,
           format: :json
       end
 
       it 'returns a content with original filename, new link, and correct type.' do
         expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
         expect(response.body).to match "\"url\":\"/uploads"
-        expect(response.body).to match '\"is_image\":false'
       end
     end
   end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5fb671df570cc4f02c35c0d4ec97981ece3b2a40..1b32d560b1620e4fb199235570202e5eef6e4a51 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
 FactoryGirl.define do
   factory :ci_build, class: Ci::Build do
     name 'test'
+    stage 'test'
+    stage_idx 0
     ref 'master'
     tag false
     created_at 'Di 29. Okt 09:50:00 CET 2013'
@@ -43,6 +45,11 @@ FactoryGirl.define do
       status 'pending'
     end
 
+    trait :manual do
+      status 'skipped'
+      self.when 'manual'
+    end
+
     trait :allowed_to_fail do
       allow_failure true
     end
@@ -83,5 +90,21 @@ FactoryGirl.define do
         build.save!
       end
     end
+
+    trait :artifacts_expired do
+      after(:create) do |build, _|
+        build.artifacts_file =
+          fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+            'application/zip')
+
+        build.artifacts_metadata =
+          fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+            'application/x-gzip')
+
+        build.artifacts_expire_at = 1.minute.ago
+
+        build.save!
+      end
+    end
   end
 end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 6d47d05f8ad6bfb51bcadf82eac31800c63a77a0..b8d8fab0e0b4f261c23826a669c9d481d33d2b49 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -5,7 +5,8 @@ FactoryGirl.define do
 
       variables do
         {
-          TRIGGER_KEY: 'TRIGGER_VALUE'
+          TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
+          TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
         }
       end
     end
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 07265c26ca3dcdcbd540383e06cfb5118d036700..846cccfc7fabea2ff5cd1529bf2ff1fb0ff06339 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -3,5 +3,6 @@ FactoryGirl.define do
     sequence(:name) { |n| "environment#{n}" }
 
     project factory: :empty_project
+    sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" }
   end
 end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index e72aa9479b757973db6330557595826e4e17a9c7..2c0a2dd94ca1bfa60ac75eda28d6d49557cf8323 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -18,5 +18,15 @@ FactoryGirl.define do
 
     factory :closed_issue, traits: [:closed]
     factory :reopened_issue, traits: [:reopened]
+
+    factory :labeled_issue do
+      transient do
+        labels []
+      end
+
+      after(:create) do |issue, evaluator|
+        issue.update_attributes(labels: evaluator.labels)
+      end
+    end
   end
 end
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 28ed8078157f37861fb75c7d077f558dda41496a..5575852c2d77af2f5105580147d0ff43d9a402b5 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -2,5 +2,28 @@ FactoryGirl.define do
   factory :protected_branch do
     name
     project
+
+    after(:create) do |protected_branch|
+      protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER)
+      protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER)
+    end
+
+    trait :developers_can_push do
+      after(:create) do |protected_branch|
+        protected_branch.push_access_level.update!(access_level: Gitlab::Access::DEVELOPER)
+      end
+    end
+
+    trait :developers_can_merge do
+      after(:create) do |protected_branch|
+        protected_branch.merge_access_level.update!(access_level: Gitlab::Access::DEVELOPER)
+      end
+    end
+
+    trait :no_one_can_push do
+      after(:create) do |protected_branch|
+        protected_branch.push_access_level.update!(access_level: Gitlab::Access::NO_ACCESS)
+      end
+    end
   end
 end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index cab3dc1d167c8672259767e2512d185a4dcff864..0cfeb2e57d8ebd32ef8cb8bec9b571b6f1b4e626 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -199,9 +199,13 @@ describe "Builds" do
         click_link 'Retry'
       end
 
-      it { expect(page.status_code).to eq(200) }
-      it { expect(page).to have_content 'pending' }
-      it { expect(page).to have_content 'Cancel' }
+      it 'shows the right status and buttons' do
+        expect(page).to have_http_status(200)
+        expect(page).to have_content 'pending'
+        page.within('aside.right-sidebar') do
+          expect(page).to have_content 'Cancel'
+        end
+      end
     end
 
     context "Build from other project" do
@@ -212,7 +216,25 @@ describe "Builds" do
         page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
       end
 
-      it { expect(page.status_code).to eq(404) }
+      it { expect(page).to have_http_status(404) }
+    end
+
+    context "Build that current user is not allowed to retry" do
+      before do
+        @build.run!
+        @build.cancel!
+        @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+        logout_direct
+        login_with(create(:user))
+        visit namespace_project_build_path(@project.namespace, @project, @build)
+      end
+
+      it 'does not show the Retry button' do
+        page.within('aside.right-sidebar') do
+          expect(page).not_to have_content 'Retry'
+        end
+      end
     end
   end
 
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 7fb28f4174b01be2c13a32657d016700ecb6ecf0..fcd41b38413112748114d42fd628de10a680be05 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
   describe 'when showing environments' do
     given!(:environment) { }
     given!(:deployment) { }
+    given!(:manual) { }
 
     before do
       visit namespace_project_environments_path(project.namespace, project)
@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
         scenario 'does show deployment SHA' do
           expect(page).to have_link(deployment.short_sha)
         end
+
+        context 'with build and manual actions' do
+          given(:pipeline) { create(:ci_pipeline, project: project) }
+          given(:build) { create(:ci_build, pipeline: pipeline) }
+          given(:deployment) { create(:deployment, environment: environment, deployable: build) }
+          given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+          scenario 'does show a play button' do
+            expect(page).to have_link(manual.name.humanize)
+          end
+
+          scenario 'does allow to play manual action' do
+            expect(manual).to be_skipped
+            expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+            expect(page).to have_content(manual.name)
+            expect(manual.reload).to be_pending
+          end
+        end
       end
     end
 
@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
   describe 'when showing the environment' do
     given(:environment) { create(:environment, project: project) }
     given!(:deployment) { }
+    given!(:manual) { }
 
     before do
       visit namespace_project_environment_path(project.namespace, project, environment)
@@ -72,20 +92,36 @@ feature 'Environments', feature: true do
         expect(page).to have_link(deployment.short_sha)
       end
 
-      scenario 'does not show a retry button for deployment without build' do
-        expect(page).not_to have_link('Retry')
+      scenario 'does not show a re-deploy button for deployment without build' do
+        expect(page).not_to have_link('Re-deploy')
       end
 
       context 'with build' do
-        given(:build) { create(:ci_build, project: project) }
+        given(:pipeline) { create(:ci_pipeline, project: project) }
+        given(:build) { create(:ci_build, pipeline: pipeline) }
         given(:deployment) { create(:deployment, environment: environment, deployable: build) }
 
         scenario 'does show build name' do
           expect(page).to have_link("#{build.name} (##{build.id})")
         end
 
-        scenario 'does show retry button' do
-          expect(page).to have_link('Retry')
+        scenario 'does show re-deploy button' do
+          expect(page).to have_link('Re-deploy')
+        end
+
+        context 'with manual action' do
+          given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+          scenario 'does show a play button' do
+            expect(page).to have_link(manual.name.humanize)
+          end
+
+          scenario 'does allow to play manual action' do
+            expect(manual).to be_skipped
+            expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+            expect(page).to have_content(manual.name)
+            expect(manual.reload).to be_pending
+          end
         end
       end
     end
@@ -104,7 +140,7 @@ feature 'Environments', feature: true do
       context 'for valid name' do
         before do
           fill_in('Name', with: 'production')
-          click_on 'Create environment'
+          click_on 'Save'
         end
 
         scenario 'does create a new pipeline' do
@@ -115,7 +151,7 @@ feature 'Environments', feature: true do
       context 'for invalid name' do
         before do
           fill_in('Name', with: 'name with spaces')
-          click_on 'Create environment'
+          click_on 'Save'
         end
 
         scenario 'does show errors' do
diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb
index d1a6a98ab7219687b11406d02e3d624ecfa41505..b3baa2ab57ca29f805bc269ac7bdf8ac56accc82 100644
--- a/spec/features/groups/members/user_requests_access_spec.rb
+++ b/spec/features/groups/members/user_requests_access_spec.rb
@@ -12,6 +12,13 @@ feature 'Groups > Members > User requests access', feature: true do
     visit group_path(group)
   end
 
+  scenario 'request access feature is disabled' do
+    group.update_attributes(request_access_enabled: false)
+    visit group_path(group)
+
+    expect(page).not_to have_content 'Request Access'
+  end
+
   scenario 'user can request access to a group' do
     perform_enqueued_jobs { click_link 'Request Access' }
 
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0d495cd04aa9cddb86398d8e795fe2e5e1eda8c7
--- /dev/null
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+
+describe 'Projects > Issuables > Default sort order', feature: true do
+  let(:project) { create(:empty_project, :public) }
+
+  let(:first_created_issuable) { issuables.order_created_asc.first }
+  let(:last_created_issuable) { issuables.order_created_desc.first }
+
+  let(:first_updated_issuable) { issuables.order_updated_asc.first }
+  let(:last_updated_issuable) { issuables.order_updated_desc.first }
+
+  context 'for merge requests' do
+    include MergeRequestHelpers
+
+    let!(:issuables) do
+      timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+                    { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+                    { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+      timestamps.each_with_index do |ts, i|
+        create issuable_type, { title: "#{issuable_type}_#{i}",
+                                source_branch: "#{issuable_type}_#{i}",
+                                source_project: project }.merge(ts)
+      end
+
+      MergeRequest.all
+    end
+
+    context 'in the "merge requests" tab', js: true do
+      let(:issuable_type) { :merge_request }
+
+      it 'is "last created"' do
+        visit_merge_requests project
+
+        expect(first_merge_request).to include(last_created_issuable.title)
+        expect(last_merge_request).to include(first_created_issuable.title)
+      end
+    end
+
+    context 'in the "merge requests / open" tab', js: true do
+      let(:issuable_type) { :merge_request }
+
+      it 'is "last created"' do
+        visit_merge_requests_with_state(project, 'open')
+
+        expect(selected_sort_order).to eq('last created')
+        expect(first_merge_request).to include(last_created_issuable.title)
+        expect(last_merge_request).to include(first_created_issuable.title)
+      end
+    end
+
+    context 'in the "merge requests / merged" tab', js: true do
+      let(:issuable_type) { :merged_merge_request }
+
+      it 'is "last updated"' do
+        visit_merge_requests_with_state(project, 'merged')
+
+        expect(selected_sort_order).to eq('last updated')
+        expect(first_merge_request).to include(last_updated_issuable.title)
+        expect(last_merge_request).to include(first_updated_issuable.title)
+      end
+    end
+
+    context 'in the "merge requests / closed" tab', js: true do
+      let(:issuable_type) { :closed_merge_request }
+
+      it 'is "last updated"' do
+        visit_merge_requests_with_state(project, 'closed')
+
+        expect(selected_sort_order).to eq('last updated')
+        expect(first_merge_request).to include(last_updated_issuable.title)
+        expect(last_merge_request).to include(first_updated_issuable.title)
+      end
+    end
+
+    context 'in the "merge requests / all" tab', js: true do
+      let(:issuable_type) { :merge_request }
+
+      it 'is "last created"' do
+        visit_merge_requests_with_state(project, 'all')
+
+        expect(selected_sort_order).to eq('last created')
+        expect(first_merge_request).to include(last_created_issuable.title)
+        expect(last_merge_request).to include(first_created_issuable.title)
+      end
+    end
+  end
+
+  context 'for issues' do
+    include IssueHelpers
+
+    let!(:issuables) do
+      timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+                    { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+                    { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+      timestamps.each_with_index do |ts, i|
+        create issuable_type, { title: "#{issuable_type}_#{i}",
+                                project: project }.merge(ts)
+      end
+
+      Issue.all
+    end
+
+    context 'in the "issues" tab', js: true do
+      let(:issuable_type) { :issue }
+
+      it 'is "last created"' do
+        visit_issues project
+
+        expect(selected_sort_order).to eq('last created')
+        expect(first_issue).to include(last_created_issuable.title)
+        expect(last_issue).to include(first_created_issuable.title)
+      end
+    end
+
+    context 'in the "issues / open" tab', js: true do
+      let(:issuable_type) { :issue }
+
+      it 'is "last created"' do
+        visit_issues_with_state(project, 'open')
+
+        expect(selected_sort_order).to eq('last created')
+        expect(first_issue).to include(last_created_issuable.title)
+        expect(last_issue).to include(first_created_issuable.title)
+      end
+    end
+
+    context 'in the "issues / closed" tab', js: true do
+      let(:issuable_type) { :closed_issue }
+
+      it 'is "last updated"' do
+        visit_issues_with_state(project, 'closed')
+
+        expect(selected_sort_order).to eq('last updated')
+        expect(first_issue).to include(last_updated_issuable.title)
+        expect(last_issue).to include(first_updated_issuable.title)
+      end
+    end
+
+    context 'in the "issues / all" tab', js: true do
+      let(:issuable_type) { :issue }
+
+      it 'is "last created"' do
+        visit_issues_with_state(project, 'all')
+
+        expect(selected_sort_order).to eq('last created')
+        expect(first_issue).to include(last_created_issuable.title)
+        expect(last_issue).to include(first_created_issuable.title)
+      end
+    end
+  end
+
+  def selected_sort_order
+    find('.pull-right .dropdown button').text.downcase
+  end
+
+  def visit_merge_requests_with_state(project, state)
+    visit_merge_requests project
+    visit_issuables_with_state state
+  end
+
+  def visit_issues_with_state(project, state)
+    visit_issues project
+    visit_issuables_with_state state
+  end
+
+  def visit_issuables_with_state(state)
+    within('.issues-state-filters') { find("span", text: state.titleize).click }
+  end
+end
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
index 5ea02b8d39c4d4adc0880e044eaffc4f31f0d27c..cb117d2476f16e221c175fdd7d9659b13d8cd156 100644
--- a/spec/features/issues/filter_by_labels_spec.rb
+++ b/spec/features/issues/filter_by_labels_spec.rb
@@ -205,7 +205,7 @@ feature 'Issue filtering by Labels', feature: true do
       page.within '.labels-filter' do
         click_button 'Label'
         wait_for_ajax
-        fill_in 'label-name', with: 'bug'
+        find('.dropdown-input input').set 'bug'
 
         page.within '.dropdown-content' do
           expect(page).not_to have_content 'enhancement'
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d51c9abea19f061cbd71fed251da78aa94f85bf7..9c92b52898c3916e846f6611b827f92a0d42ac48 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 describe 'Issues', feature: true do
+  include IssueHelpers
   include SortingHelper
 
   let(:project) { create(:project) }
@@ -186,15 +187,15 @@ describe 'Issues', feature: true do
     it 'sorts by newest' do
       visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
 
-      expect(first_issue).to include('baz')
-      expect(last_issue).to include('foo')
+      expect(first_issue).to include('foo')
+      expect(last_issue).to include('baz')
     end
 
     it 'sorts by oldest' do
       visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
 
-      expect(first_issue).to include('foo')
-      expect(last_issue).to include('baz')
+      expect(first_issue).to include('baz')
+      expect(last_issue).to include('foo')
     end
 
     it 'sorts by most recently updated' do
@@ -350,8 +351,8 @@ describe 'Issues', feature: true do
                                             sort: sort_value_oldest_created,
                                             assignee_id: user2.id)
 
-        expect(first_issue).to include('foo')
-        expect(last_issue).to include('bar')
+        expect(first_issue).to include('bar')
+        expect(last_issue).to include('foo')
         expect(page).not_to have_content 'baz'
       end
     end
@@ -524,6 +525,35 @@ describe 'Issues', feature: true do
     end
   end
 
+  describe 'new issue by email' do
+    shared_examples 'show the email in the modal' do
+      before do
+        stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+
+        visit namespace_project_issues_path(project.namespace, project)
+        click_button('Email a new issue')
+      end
+
+      it 'click the button to show modal for the new email' do
+        page.within '#issue-email-modal' do
+          email = project.new_issue_address(@user)
+
+          expect(page).to have_selector("input[value='#{email}']")
+        end
+      end
+    end
+
+    context 'with existing issues' do
+      let!(:issue) { create(:issue, project: project, author: @user) }
+
+      it_behaves_like 'show the email in the modal'
+    end
+
+    context 'without existing issues' do
+      it_behaves_like 'show the email in the modal'
+    end
+  end
+
   describe 'due date' do
     context 'update due on issue#show', js: true do
       let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
@@ -561,14 +591,6 @@ describe 'Issues', feature: true do
     end
   end
 
-  def first_issue
-    page.all('ul.issues-list > li').first.text
-  end
-
-  def last_issue
-    page.all('ul.issues-list > li').last.text
-  end
-
   def drop_in_dropzone(file_path)
     # Generate a fake input selector
     page.execute_script <<-JS
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 09ccc77c101780bcac82fde639ac78509ba0ffad..32159559c379bcd530929728d6082f007b5d53de 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -236,6 +236,14 @@ describe 'GitLab Markdown', feature: true do
     it 'includes TaskListFilter' do
       expect(doc).to parse_task_lists
     end
+
+    it 'includes InlineDiffFilter' do
+      expect(doc).to parse_inline_diffs
+    end
+
+    it 'includes VideoLinkFilter' do
+      expect(doc).to parse_video_links
+    end
   end
 
   context 'wiki pipeline' do
@@ -293,6 +301,10 @@ describe 'GitLab Markdown', feature: true do
     it 'includes InlineDiffFilter' do
       expect(doc).to parse_inline_diffs
     end
+
+    it 'includes VideoLinkFilter' do
+      expect(doc).to parse_video_links
+    end
   end
 
   # Fake a `current_user` helper
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 1c130057c5693a3063849b15a946378105d59ef9..cabb8e455f9d2053d6e6fa781a7affc0f78a18ce 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,7 @@
 require 'spec_helper'
 
 describe 'Projects > Merge requests > User lists merge requests', feature: true do
+  include MergeRequestHelpers
   include SortingHelper
 
   let(:project) { create(:project, :public) }
@@ -23,10 +24,12 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
            milestone: create(:milestone, due_date: '2013-12-12'),
            created_at: 2.minutes.ago,
            updated_at: 2.minutes.ago)
+    # lfs in itself is not a great choice for the title if one wants to match the whole body content later on
+    # just think about the scenario when faker generates 'Chester Runolfsson' as the user's name
     create(:merge_request,
-           title: 'lfs',
+           title: 'merge_lfs',
            source_project: project,
-           source_branch: 'lfs',
+           source_branch: 'merge_lfs',
            created_at: 3.minutes.ago,
            updated_at: 10.seconds.ago)
   end
@@ -35,7 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
     visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
 
     expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
-    expect(page).to have_content 'lfs'
+    expect(page).to have_content 'merge_lfs'
     expect(page).not_to have_content 'fix'
     expect(page).not_to have_content 'markdown'
     expect(count_merge_requests).to eq(1)
@@ -44,7 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
   it 'filters on a specific assignee' do
     visit_merge_requests(project, assignee_id: user.id)
 
-    expect(page).not_to have_content 'lfs'
+    expect(page).not_to have_content 'merge_lfs'
     expect(page).to have_content 'fix'
     expect(page).to have_content 'markdown'
     expect(count_merge_requests).to eq(2)
@@ -53,23 +56,23 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
   it 'sorts by newest' do
     visit_merge_requests(project, sort: sort_value_recently_created)
 
-    expect(first_merge_request).to include('lfs')
-    expect(last_merge_request).to include('fix')
+    expect(first_merge_request).to include('fix')
+    expect(last_merge_request).to include('merge_lfs')
     expect(count_merge_requests).to eq(3)
   end
 
   it 'sorts by oldest' do
     visit_merge_requests(project, sort: sort_value_oldest_created)
 
-    expect(first_merge_request).to include('fix')
-    expect(last_merge_request).to include('lfs')
+    expect(first_merge_request).to include('merge_lfs')
+    expect(last_merge_request).to include('fix')
     expect(count_merge_requests).to eq(3)
   end
 
   it 'sorts by last updated' do
     visit_merge_requests(project, sort: sort_value_recently_updated)
 
-    expect(first_merge_request).to include('lfs')
+    expect(first_merge_request).to include('merge_lfs')
     expect(count_merge_requests).to eq(3)
   end
 
@@ -143,18 +146,6 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
     end
   end
 
-  def visit_merge_requests(project, opts = {})
-    visit namespace_project_merge_requests_path(project.namespace, project, opts)
-  end
-
-  def first_merge_request
-    page.all('ul.mr-list > li').first.text
-  end
-
-  def last_merge_request
-    page.all('ul.mr-list > li').last.text
-  end
-
   def count_merge_requests
     page.all('ul.mr-list > li').count
   end
diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/pipelines_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dcc364a3d01cd09753777ceda9b9287df29d99ec
--- /dev/null
+++ b/spec/features/pipelines_settings_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+feature "Pipelines settings", feature: true do
+  include GitlabRoutingHelper
+
+  let(:project) { create(:empty_project) }
+  let(:user) { create(:user) }
+  let(:role) { :developer }
+
+  background do
+    login_as(user)
+    project.team << [user, role]
+    visit namespace_project_pipelines_settings_path(project.namespace, project)
+  end
+
+  context 'for developer' do
+    given(:role) { :developer }
+
+    scenario 'to be disallowed to view' do
+      expect(page.status_code).to eq(404)
+    end
+  end
+
+  context 'for master' do
+    given(:role) { :master }
+
+    scenario 'be allowed to change' do
+      fill_in('Test coverage parsing', with: 'coverage_regex')
+      click_on 'Save changes'
+
+      expect(page.status_code).to eq(200)
+      expect(page).to have_field('Test coverage parsing', with: 'coverage_regex')
+    end
+  end
+end
diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb
index e7ee0aaea3c6cf7feabecca37be9f54132f91acd..377a9aba60d890761e28b5838e8af239280bfcaa 100644
--- a/spec/features/pipelines_spec.rb
+++ b/spec/features/pipelines_spec.rb
@@ -62,6 +62,20 @@ describe "Pipelines" do
       end
     end
 
+    context 'with manual actions' do
+      let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
+
+      before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+      it { expect(page).to have_link('Manual build') }
+
+      context 'when playing' do
+        before { click_link('Manual build') }
+
+        it { expect(manual.reload).to be_pending }
+      end
+    end
+
     context 'for generic statuses' do
       context 'when running' do
         let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
@@ -102,9 +116,19 @@ describe "Pipelines" do
         it { expect(page).to have_link(with_artifacts.name) }
       end
 
+      context 'with artifacts expired' do
+        let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
+
+        before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+        it { expect(page).not_to have_selector('.build-artifacts') }
+      end
+
       context 'without artifacts' do
         let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
 
+        before { visit namespace_project_pipelines_path(project.namespace, project) }
+
         it { expect(page).not_to have_selector('.build-artifacts') }
       end
     end
@@ -117,6 +141,7 @@ describe "Pipelines" do
       @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
       @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
       @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+      @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
       @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
     end
 
@@ -131,6 +156,7 @@ describe "Pipelines" do
       expect(page).to have_content(@external.id)
       expect(page).to have_content('Retry failed')
       expect(page).to have_content('Cancel running')
+      expect(page).to have_link('Play')
     end
 
     context 'retrying builds' do
@@ -154,6 +180,12 @@ describe "Pipelines" do
         it { expect(page).to have_selector('.ci-canceled') }
       end
     end
+
+    context 'playing manual build' do
+      before { click_link('Play') }
+
+      it { expect(@manual.reload).to be_pending }
+    end
   end
 
   describe 'POST /:project/pipelines' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 01e90618a98cd68e233e6a4d655cb1bd1586fc53..75166bca119de2e50785ec53245c1e6dd00964a7 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -6,7 +6,7 @@ feature 'list of badges' do
     project = create(:project)
     project.team << [user, :master]
     login_as(user)
-    visit namespace_project_badges_path(project.namespace, project)
+    visit namespace_project_pipelines_settings_path(project.namespace, project)
   end
 
   scenario 'user displays list of badges' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..79abba21854309b77ba86fadfdc721d4d636e1f0
--- /dev/null
+++ b/spec/features/projects/branches_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Branches', feature: true do
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+
+  before do
+    login_as :user
+    project.team << [@user, :developer]
+  end
+
+  describe 'Initial branches page' do
+    it 'shows all the branches' do
+      visit namespace_project_branches_path(project.namespace, project)
+
+      repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
+      expect(page).to have_content("Protected branches can be managed in project settings")
+    end
+  end
+
+  describe 'Find branches' do
+    it 'shows filtered branches', js: true do
+      visit namespace_project_branches_path(project.namespace, project, project.id)
+
+      fill_in 'branch-search', with: 'fix'
+      find('#branch-search').native.send_keys(:enter)
+
+      expect(page).to have_content('fix')
+      expect(find('.all-branches')).to have_selector('li', count: 1)
+    end
+  end
+end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index bc3bf53fe9d55fac92b2c90aab72aca88aa89130..7835e1678ad0596a1ed2c60fedada963261df467 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -8,6 +8,7 @@ feature 'project import', feature: true, js: true do
   let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
   let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
   let(:project) { Project.last }
+  let(:project_hook) { Gitlab::Git::Hook.new('post-receive', project.repository.path) }
 
   background do
     allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
@@ -37,7 +38,7 @@ feature 'project import', feature: true, js: true do
     expect(project).not_to be_nil
     expect(project.issues).not_to be_empty
     expect(project.merge_requests).not_to be_empty
-    expect(project.repo_exists?).to be true
+    expect(project_hook).to exist
     expect(wiki_exists?).to be true
     expect(project.import_status).to eq('finished')
   end
@@ -59,6 +60,21 @@ feature 'project import', feature: true, js: true do
     end
   end
 
+  scenario 'project with no name' do
+    create(:project, namespace_id: 2)
+
+    visit new_project_path
+
+    select2('2', from: '#project_namespace_id')
+
+    # click on disabled element
+    find(:link, 'GitLab export').trigger('click')
+
+    page.within('.flash-container') do
+      expect(page).to have_content('Please enter path and name')
+    end
+  end
+
   def wiki_exists?
     wiki = ProjectWiki.new(project)
     File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty?
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index f2fe3ef364d0f777cabd24bdd1bef857a79ac762..56ede8eb5be5e304b44193ae1c05bdf0e5791e07 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -11,6 +11,13 @@ feature 'Projects > Members > User requests access', feature: true do
     visit namespace_project_path(project.namespace, project)
   end
 
+  scenario 'request access feature is disabled' do
+    project.update_attributes(request_access_enabled: false)
+    visit namespace_project_path(project.namespace, project)
+
+    expect(page).not_to have_content 'Request Access'
+  end
+
   scenario 'user can request access to a project' do
     perform_enqueued_jobs { click_link 'Request Access' }
 
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3de25d7af7d2cbcd7f28dfeeaabae8c34bfa5706
--- /dev/null
+++ b/spec/features/projects/project_settings_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe 'Edit Project Settings', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
+
+  before do
+    login_as(user)
+    project.team << [user, :master]
+  end
+
+  describe 'Project settings', js: true do
+    it 'shows errors for invalid project name' do
+      visit edit_namespace_project_path(project.namespace, project)
+
+      fill_in 'project_name_edit', with: 'foo&bar'
+
+      click_button 'Save changes'
+
+      expect(page).to have_field 'project_name_edit', with: 'foo&bar'
+      expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+      expect(page).to have_button 'Save changes'
+    end
+  end
+
+  describe 'Rename repository' do
+    it 'shows errors for invalid project path/name' do
+      visit edit_namespace_project_path(project.namespace, project)
+
+      fill_in 'Project name', with: 'foo&bar'
+      fill_in 'Path', with: 'foo&bar'
+
+      click_button 'Rename project'
+
+      expect(page).to have_field 'Project name', with: 'foo&bar'
+      expect(page).to have_field 'Path', with: 'foo&bar'
+      expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+      expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
+    end
+  end
+end
diff --git a/spec/features/projects/slack_service/slack_service_spec.rb b/spec/features/projects/slack_service/slack_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..16541f51d98cb9fc6e54b9ba6189db1929325efb
--- /dev/null
+++ b/spec/features/projects/slack_service/slack_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+feature 'Projects > Slack service > Setup events', feature: true do
+  let(:user) { create(:user) }
+  let(:service) { SlackService.new }
+  let(:project) { create(:project, slack_service: service) }
+
+  background do
+    service.fields
+    service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, build_channel: 6, wiki_page_channel: 7)
+    project.team << [user, :master]
+    login_as(user)
+  end
+
+  scenario 'user can filter events by channel' do
+    visit edit_namespace_project_service_path(project.namespace, project, service)
+
+    expect(page.find_field("service_push_channel").value).to have_content '1'
+    expect(page.find_field("service_issue_channel").value).to have_content '2'
+    expect(page.find_field("service_merge_request_channel").value).to have_content '3'
+    expect(page.find_field("service_note_channel").value).to have_content '4'
+    expect(page.find_field("service_tag_push_channel").value).to have_content '5'
+    expect(page.find_field("service_build_channel").value).to have_content '6'
+    expect(page.find_field("service_wiki_page_channel").value).to have_content '7'
+  end
+end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1c386ddc181c1746b5868026e2d869e2c9bd9c6
--- /dev/null
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -0,0 +1,140 @@
+require 'spec_helper'
+
+feature 'Projects > Wiki > User previews markdown changes', feature: true, js: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+  let(:wiki_content) do
+    <<-HEREDOC
+[regular link](regular)
+[relative link 1](../relative)
+[relative link 2](./relative)
+[relative link 3](./e/f/relative)
+    HEREDOC
+  end
+
+  background do
+    project.team << [user, :master]
+    login_as(user)
+
+    visit namespace_project_path(project.namespace, project)
+    click_link 'Wiki'
+    WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+  end
+
+  context "while creating a new wiki page" do
+    context "when there are no spaces or hyphens in the page name" do
+      it "rewrites relative links as expected" do
+        click_link 'New Page'
+        fill_in :new_wiki_path, with: 'a/b/c/d'
+        click_button 'Create Page'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
+      end
+    end
+
+    context "when there are spaces in the page name" do
+      it "rewrites relative links as expected" do
+        click_link 'New Page'
+        fill_in :new_wiki_path, with: 'a page/b page/c page/d page'
+        click_button 'Create Page'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
+      end
+    end
+
+    context "when there are hyphens in the page name" do
+      it "rewrites relative links as expected" do
+        click_link 'New Page'
+        fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page'
+        click_button 'Create Page'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
+      end
+    end
+  end
+
+  context "while editing a wiki page" do
+    def create_wiki_page(path)
+      click_link 'New Page'
+      fill_in :new_wiki_path, with: path
+      click_button 'Create Page'
+      fill_in :wiki_content, with: 'content'
+      click_on "Create page"
+    end
+
+    context "when there are no spaces or hyphens in the page name" do
+      it "rewrites relative links as expected" do
+        create_wiki_page 'a/b/c/d'
+        click_link 'Edit'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a/b/c/e/f/relative\">relative link 3</a>")
+      end
+    end
+
+    context "when there are spaces in the page name" do
+      it "rewrites relative links as expected" do
+        create_wiki_page 'a page/b page/c page/d page'
+        click_link 'Edit'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
+      end
+    end
+
+    context "when there are hyphens in the page name" do
+      it "rewrites relative links as expected" do
+        create_wiki_page 'a-page/b-page/c-page/d-page'
+        click_link 'Edit'
+
+        fill_in :wiki_content, with: wiki_content
+        click_on "Preview"
+
+        expect(page).to have_content("regular link")
+
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/regular\">regular link</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/relative\">relative link 1</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/relative\">relative link 2</a>")
+        expect(page.html).to include("<a href=\"/#{project.path_with_namespace}/wikis/a-page/b-page/c-page/e/f/relative\">relative link 3</a>")
+      end
+    end
+  end
+end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 7e6eef6587379b2c96c0dd202329caee77d4350a..7afd83b7250d6ef289466e16cd1d45fc49b5ee18 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -30,18 +30,48 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do
         WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
       end
 
-      scenario 'via the "new wiki page" page', js: true do
-        click_link 'New Page'
+      context 'via the "new wiki page" page' do
+        scenario 'when the wiki page has a single word name', js: true do
+          click_link 'New Page'
 
-        fill_in :new_wiki_path, with: 'foo'
-        click_button 'Create Page'
+          fill_in :new_wiki_path, with: 'foo'
+          click_button 'Create Page'
 
-        fill_in :wiki_content, with: 'My awesome wiki!'
-        click_button 'Create page'
+          fill_in :wiki_content, with: 'My awesome wiki!'
+          click_button 'Create page'
 
-        expect(page).to have_content('Foo')
-        expect(page).to have_content("last edited by #{user.name}")
-        expect(page).to have_content('My awesome wiki!')
+          expect(page).to have_content('Foo')
+          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content('My awesome wiki!')
+        end
+
+        scenario 'when the wiki page has spaces in the name', js: true do
+          click_link 'New Page'
+
+          fill_in :new_wiki_path, with: 'Spaces in the name'
+          click_button 'Create Page'
+
+          fill_in :wiki_content, with: 'My awesome wiki!'
+          click_button 'Create page'
+
+          expect(page).to have_content('Spaces in the name')
+          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content('My awesome wiki!')
+        end
+
+        scenario 'when the wiki page has hyphens in the name', js: true do
+          click_link 'New Page'
+
+          fill_in :new_wiki_path, with: 'hyphens-in-the-name'
+          click_button 'Create Page'
+
+          fill_in :wiki_content, with: 'My awesome wiki!'
+          click_button 'Create page'
+
+          expect(page).to have_content('Hyphens in the name')
+          expect(page).to have_content("last edited by #{user.name}")
+          expect(page).to have_content('My awesome wiki!')
+        end
       end
     end
   end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index d94dee0c797628df3652597e98a97d0b4b93e61d..57734b33a44a81a89efeba35a2311a98b22e054a 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 feature 'Projected Branches', feature: true, js: true do
+  include WaitForAjax
+
   let(:user) { create(:user, :admin) }
   let(:project) { create(:project) }
 
@@ -81,4 +83,68 @@ feature 'Projected Branches', feature: true, js: true do
       end
     end
   end
+
+  describe "access control" do
+    ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
+      it "allows creating protected branches that #{access_type_name} can push to" do
+        visit namespace_project_protected_branches_path(project.namespace, project)
+        set_protected_branch_name('master')
+        within('.new_protected_branch') do
+          find(".allowed-to-push").click
+          within(".dropdown.open .dropdown-menu") { click_on access_type_name }
+        end
+        click_on "Protect"
+
+        expect(ProtectedBranch.count).to eq(1)
+        expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id)
+      end
+
+      it "allows updating protected branches so that #{access_type_name} can push to them" do
+        visit namespace_project_protected_branches_path(project.namespace, project)
+        set_protected_branch_name('master')
+        click_on "Protect"
+
+        expect(ProtectedBranch.count).to eq(1)
+
+        within(".protected-branches-list") do
+          find(".allowed-to-push").click
+          within('.dropdown-menu.push') { click_on access_type_name }
+        end
+
+        wait_for_ajax
+        expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id)
+      end
+    end
+
+    ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
+      it "allows creating protected branches that #{access_type_name} can merge to" do
+        visit namespace_project_protected_branches_path(project.namespace, project)
+        set_protected_branch_name('master')
+        within('.new_protected_branch') do
+          find(".allowed-to-merge").click
+          within(".dropdown.open .dropdown-menu") { click_on access_type_name }
+        end
+        click_on "Protect"
+
+        expect(ProtectedBranch.count).to eq(1)
+        expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id)
+      end
+
+      it "allows updating protected branches so that #{access_type_name} can merge to them" do
+        visit namespace_project_protected_branches_path(project.namespace, project)
+        set_protected_branch_name('master')
+        click_on "Protect"
+
+        expect(ProtectedBranch.count).to eq(1)
+
+        within(".protected-branches-list") do
+          find(".allowed-to-merge").click
+          within('.dropdown-menu.merge') { click_on access_type_name }
+        end
+
+        wait_for_ajax
+        expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id)
+      end
+    end
+  end
 end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index d0a301038c46c18d9b0f779074c25f5afc924b73..09f70cd3b002e57db9504b09a3752ab855ad93ba 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -28,6 +28,26 @@ describe "Search", feature: true  do
   end
 
   context 'search for comments' do
+    context 'when comment belongs to a invalid commit' do
+      let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') }
+
+      before { note.update_attributes(commit_id: 12345678) }
+
+      it 'finds comment' do
+        visit namespace_project_path(project.namespace, project)
+
+        page.within '.search' do
+          fill_in 'search', with: note.note
+          click_button 'Go'
+        end
+
+        click_link 'Comments'
+
+        expect(page).to have_text("Commit deleted")
+        expect(page).to have_text("12345678")
+      end
+    end
+
     it 'finds a snippet' do
       snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
       note = create(:note,
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6fce11de30fb32369557241ee43e6f32f4ba1055
--- /dev/null
+++ b/spec/finders/branches_finder_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe BranchesFinder do
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+
+  describe '#execute' do
+    context 'sort only' do
+      it 'sorts by name' do
+        branches_finder = described_class.new(repository, {})
+
+        result = branches_finder.execute
+
+        expect(result.first.name).to eq("'test'")
+      end
+
+      it 'sorts by recently_updated' do
+        branches_finder = described_class.new(repository, { sort: 'updated_desc' })
+
+        result = branches_finder.execute
+
+        recently_updated_branch = repository.branches.max do |a, b|
+          repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date
+        end
+
+        expect(result.first.name).to eq(recently_updated_branch.name)
+      end
+
+      it 'sorts by last_updated' do
+        branches_finder = described_class.new(repository, { sort: 'updated_asc' })
+
+        result = branches_finder.execute
+
+        expect(result.first.name).to eq('feature')
+      end
+    end
+
+    context 'filter only' do
+      it 'filters branches by name' do
+        branches_finder = described_class.new(repository, { search: 'fix' })
+
+        result = branches_finder.execute
+
+        expect(result.first.name).to eq('fix')
+        expect(result.count).to eq(1)
+      end
+
+      it 'does not find any branch with that name' do
+        branches_finder = described_class.new(repository, { search: 'random' })
+
+        result = branches_finder.execute
+
+        expect(result.count).to eq(0)
+      end
+    end
+
+    context 'filter and sort' do
+      it 'filters branches by name and sorts by recently_updated' do
+        params = { sort: 'updated_desc', search: 'feature' }
+        branches_finder = described_class.new(repository, params)
+
+        result = branches_finder.execute
+
+        expect(result.first.name).to eq('feature_conflict')
+        expect(result.count).to eq(2)
+      end
+
+      it 'filters branches by name and sorts by last_updated' do
+        params = { sort: 'updated_asc', search: 'feature' }
+        branches_finder = described_class.new(repository, params)
+
+        result = branches_finder.execute
+
+        expect(result.first.name).to eq('feature')
+        expect(result.count).to eq(2)
+      end
+    end
+  end
+end
diff --git a/spec/fixtures/domain_blacklist.txt b/spec/fixtures/domain_blacklist.txt
new file mode 100644
index 0000000000000000000000000000000000000000..baeb11eda9a95576ff7ef410b4dd4e7ffaaf9f24
--- /dev/null
+++ b/spec/fixtures/domain_blacklist.txt
@@ -0,0 +1,3 @@
+example.com
+test.com
+foo.bar
\ No newline at end of file
diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml
new file mode 100644
index 0000000000000000000000000000000000000000..3cf53a656a5424da3b1bf464b8f4fbbe153196c2
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue.eml
@@ -0,0 +1,23 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+The reply by email functionality should be extended to allow creating a new issue by email.
+
+* Allow an admin to specify which project the issue should be created under by checking the sender domain.
+* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under.
diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml
new file mode 100644
index 0000000000000000000000000000000000000000..fc1d52a3f42941253e12659345512187d9108d7a
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue_empty.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_authentication_token.eml b/spec/fixtures/emails/wrong_authentication_token.eml
new file mode 100644
index 0000000000000000000000000000000000000000..0994c2f7775ca673b8493e58a16ee23a43b9b8ec
--- /dev/null
+++ b/spec/fixtures/emails/wrong_authentication_token.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_mail_key.eml
similarity index 100%
rename from spec/fixtures/emails/wrong_reply_key.eml
rename to spec/fixtures/emails/wrong_mail_key.eml
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index c75d28d98012710e6e3c642d8b4611e35b1db363..f3e7c2d1a9f2ed31ec12af540249360baa8c2fb3 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -256,3 +256,7 @@ However the wrapping tags can not be mixed as such -
 - [+ additions +}
 - {- delletions -]
 - [- delletions -}
+
+### Videos
+
+![My Video](/assets/videos/gitlab-demo.mp4)
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
deleted file mode 100644
index 37066c8e9302bed55e518a58d9d79105b94137af..0000000000000000000000000000000000000000
--- a/spec/fixtures/parallel_diff_result.yml
+++ /dev/null
@@ -1,800 +0,0 @@
----
-- :left:
-    :type: match
-    :number: 6
-    :text: "@@ -6,12 +6,18 @@ module Popen"
-    :line_code:
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: match
-    :number: 6
-    :text: "@@ -6,12 +6,18 @@ module Popen"
-    :line_code:
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 6
-    :text: |2
-       <span id="LC6" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 6
-        :new_line: 6
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 6
-    :text: |2
-       <span id="LC6" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 6
-        :new_line: 6
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 7
-    :text: |2
-       <span id="LC7" class="line">  <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 7
-        :new_line: 7
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 7
-    :text: |2
-       <span id="LC7" class="line">  <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 7
-        :new_line: 7
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 8
-    :text: |2
-       <span id="LC8" class="line">    <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 8
-        :new_line: 8
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 8
-    :text: |2
-       <span id="LC8" class="line">    <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 8
-        :new_line: 8
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type: old
-    :number: 9
-    :text: |
-      -<span id="LC9" class="line">      <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 9
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 9
-    :text: |
-      +<span id="LC9" class="line">      <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 9
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 10
-    :text: |2
-       <span id="LC10" class="line">    <span class="k">end</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 10
-        :new_line: 10
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 10
-    :text: |2
-       <span id="LC10" class="line">    <span class="k">end</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 10
-        :new_line: 10
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 11
-    :text: |2
-       <span id="LC11" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 11
-        :new_line: 11
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 11
-    :text: |2
-       <span id="LC11" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 11
-        :new_line: 11
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 12
-    :text: |2
-       <span id="LC12" class="line">    <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 12
-        :new_line: 12
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 12
-    :text: |2
-       <span id="LC12" class="line">    <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 12
-        :new_line: 12
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type: old
-    :number: 13
-    :text: |
-      -<span id="LC13" class="line">    <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 13
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 13
-    :text: |
-      +<span id="LC13" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 13
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type: old
-    :number: 14
-    :text: |
-      -<span id="LC14" class="line">    <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 14
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 14
-    :text: |
-      +<span id="LC14" class="line">    <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 14
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 15
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 15
-    :text: |
-      +<span id="LC15" class="line">      <span class="s2">"PWD"</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 15
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 16
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 16
-    :text: |
-      +<span id="LC16" class="line">    <span class="p">}</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 16
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 17
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 17
-    :text: |
-      +<span id="LC17" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 17
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 18
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 18
-    :text: |
-      +<span id="LC18" class="line">    <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 18
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 19
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 19
-    :text: |
-      +<span id="LC19" class="line">      <span class="ss">chdir: </span><span class="n">path</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 19
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 20
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 20
-    :text: |
-      +<span id="LC20" class="line">    <span class="p">}</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 20
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 15
-    :text: |2
-       <span id="LC21" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 15
-        :new_line: 21
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 21
-    :text: |2
-       <span id="LC21" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 15
-        :new_line: 21
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 16
-    :text: |2
-       <span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 16
-        :new_line: 22
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 22
-    :text: |2
-       <span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 16
-        :new_line: 22
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 17
-    :text: |2
-       <span id="LC23" class="line">      <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 17
-        :new_line: 23
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 23
-    :text: |2
-       <span id="LC23" class="line">      <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 17
-        :new_line: 23
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type: match
-    :number: 19
-    :text: "@@ -19,6 +25,7 @@ module Popen"
-    :line_code:
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: match
-    :number: 25
-    :text: "@@ -19,6 +25,7 @@ module Popen"
-    :line_code:
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line:
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 19
-    :text: |2
-       <span id="LC25" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 19
-        :new_line: 25
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 25
-    :text: |2
-       <span id="LC25" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 19
-        :new_line: 25
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 20
-    :text: |2
-       <span id="LC26" class="line">    <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 20
-        :new_line: 26
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 26
-    :text: |2
-       <span id="LC26" class="line">    <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 20
-        :new_line: 26
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 21
-    :text: |2
-       <span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 21
-        :new_line: 27
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 27
-    :text: |2
-       <span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 21
-        :new_line: 27
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number:
-    :text: ''
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 28
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type: new
-    :number: 28
-    :text: |
-      +<span id="LC28" class="line"></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line:
-        :new_line: 28
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 22
-    :text: |2
-       <span id="LC29" class="line">    <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 22
-        :new_line: 29
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 29
-    :text: |2
-       <span id="LC29" class="line">    <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 22
-        :new_line: 29
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 23
-    :text: |2
-       <span id="LC30" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 23
-        :new_line: 30
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 30
-    :text: |2
-       <span id="LC30" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 23
-        :new_line: 30
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
-    :type:
-    :number: 24
-    :text: |2
-       <span id="LC31" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 24
-        :new_line: 31
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-  :right:
-    :type:
-    :number: 31
-    :text: |2
-       <span id="LC31" class="line">      <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
-    :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
-    :position: !ruby/object:Gitlab::Diff::Position
-      attributes:
-        :old_path: files/ruby/popen.rb
-        :new_path: files/ruby/popen.rb
-        :old_line: 24
-        :new_line: 31
-        :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
-        :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
diff --git a/spec/fixtures/video_sample.mp4 b/spec/fixtures/video_sample.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..acd45190998c226562731c54c681cdb5a26e927f
Binary files /dev/null and b/spec/fixtures/video_sample.mp4 differ
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index bd0108f99380c74e56d4960ea6f72cd45a6f2c58..b2d6d59b1ee0c467bb01c143c777e80dbb278398 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -1,6 +1,8 @@
 require 'spec_helper'
 
 describe BlobHelper do
+  include TreeHelper
+
   let(:blob_name) { 'test.lisp' }
   let(:no_context_content) { ":type \"assem\"))" }
   let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
@@ -65,4 +67,20 @@ describe BlobHelper do
       expect(sanitize_svg(blob).data).to eq(expected)
     end
   end
+
+  describe "#edit_blob_link" do
+    let(:project) { create(:project) }
+
+    before do
+      allow(self).to receive(:current_user).and_return(double)
+    end
+
+    it 'verifies blob is text' do
+      expect(self).not_to receive(:blob_text_viewable?)
+
+      button = edit_blob_link(project, 'refs/heads/master', 'README.md')
+
+      expect(button).to start_with('<button')
+    end
+  end
 end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 45199d0f09da61489db4f2d3ed95452faa654605..637b02d938877df3b8d3df37b5d5d3a107522afe 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -7,7 +7,13 @@ describe CiStatusHelper do
   let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
 
   describe 'ci_icon_for_status' do
-    it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
-    it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
+    it 'renders to correct svg on success' do
+      expect(helper).to receive(:render).with('shared/icons/icon_status_success.svg', anything)
+      helper.ci_icon_for_status(success_commit.status)
+    end
+    it 'renders the correct svg on failure' do
+      expect(helper).to receive(:render).with('shared/icons/icon_status_failed.svg', anything)
+      helper.ci_icon_for_status(failed_commit.status)
+    end
   end
 end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 831ae7fb69c51daa7c51fe82e7c5507bf5e989c2..9ee46dd2508b246d49c9743fc5ca70c5b798574b 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -5,52 +5,6 @@ describe IssuesHelper do
   let(:issue) { create :issue, project: project }
   let(:ext_project) { create :redmine_project }
 
-  describe "url_for_project_issues" do
-    let(:project_url) { ext_project.external_issue_tracker.project_url }
-    let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) }
-    let(:int_expected) { polymorphic_path([@project.namespace, project]) }
-
-    it "should return internal path if used internal tracker" do
-      @project = project
-      expect(url_for_project_issues).to match(int_expected)
-    end
-
-    it "should return path to external tracker" do
-      @project = ext_project
-
-      expect(url_for_project_issues).to match(ext_expected)
-    end
-
-    it "should return empty string if project nil" do
-      @project = nil
-
-      expect(url_for_project_issues).to eq ""
-    end
-
-    it 'returns an empty string if project_url is invalid' do
-      expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
-
-      expect(url_for_project_issues(project)).to eq ''
-    end
-
-    it 'returns an empty string if project_path is invalid' do
-      expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
-
-      expect(url_for_project_issues(project, only_path: true)).to eq ''
-    end
-
-    describe "when external tracker was enabled and then config removed" do
-      before do
-        @project = ext_project
-        allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
-      end
-
-      it "should return path to external tracker" do
-        expect(url_for_project_issues).to match(ext_expected)
-      end
-    end
-  end
-
   describe "url_for_issue" do
     let(:issues_url) { ext_project.external_issue_tracker.issues_url}
     let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
@@ -97,52 +51,6 @@ describe IssuesHelper do
     end
   end
 
-  describe 'url_for_new_issue' do
-    let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
-    let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) }
-    let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) }
-
-    it "should return internal path if used internal tracker" do
-      @project = project
-      expect(url_for_new_issue).to match(int_expected)
-    end
-
-    it "should return path to external tracker" do
-      @project = ext_project
-
-      expect(url_for_new_issue).to match(ext_expected)
-    end
-
-    it "should return empty string if project nil" do
-      @project = nil
-
-      expect(url_for_new_issue).to eq ""
-    end
-
-    it 'returns an empty string if issue_url is invalid' do
-      expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
-
-      expect(url_for_new_issue(project)).to eq ''
-    end
-
-    it 'returns an empty string if issue_path is invalid' do
-      expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
-
-      expect(url_for_new_issue(project, only_path: true)).to eq ''
-    end
-
-    describe "when external tracker was enabled and then config removed" do
-      before do
-        @project = ext_project
-        allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
-      end
-
-      it "should return internal path" do
-        expect(url_for_new_issue).to match(ext_expected)
-      end
-    end
-  end
-
   describe "merge_requests_sentence" do
     subject { merge_requests_sentence(merge_requests)}
     let(:merge_requests) do
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 08a9350325872fb451a6657f9791ba41ba4826c5..af371248ae95ee943a14fa2b377ea368d8dbd1a4 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -1,37 +1,30 @@
 require "spec_helper"
 
 describe NotesHelper do
-  describe "#notes_max_access_for_users" do
-    let(:owner) { create(:owner) }
-    let(:group) { create(:group) }
-    let(:project) { create(:empty_project, namespace: group) }
-    let(:master) { create(:user) }
-    let(:reporter) { create(:user) }
-    let(:guest) { create(:user) }
-
-    let(:owner_note) { create(:note, author: owner, project: project) }
-    let(:master_note) { create(:note, author: master, project: project) }
-    let(:reporter_note) { create(:note, author: reporter, project: project) }
-    let!(:notes) { [owner_note, master_note, reporter_note] }
-
-    before do
-      group.add_owner(owner)
-      project.team << [master, :master]
-      project.team << [reporter, :reporter]
-      project.team << [guest, :guest]
-    end
+  let(:owner) { create(:owner) }
+  let(:group) { create(:group) }
+  let(:project) { create(:empty_project, namespace: group) }
+  let(:master) { create(:user) }
+  let(:reporter) { create(:user) }
+  let(:guest) { create(:user) }
 
-    it 'return human access levels' do
-      original_method = project.team.method(:human_max_access)
-      expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args|
-        original_method.call(args[1])
-      end
+  let(:owner_note) { create(:note, author: owner, project: project) }
+  let(:master_note) { create(:note, author: master, project: project) }
+  let(:reporter_note) { create(:note, author: reporter, project: project) }
+  let!(:notes) { [owner_note, master_note, reporter_note] }
 
+  before do
+    group.add_owner(owner)
+    project.team << [master, :master]
+    project.team << [reporter, :reporter]
+    project.team << [guest, :guest]
+  end
+
+  describe "#notes_max_access_for_users" do
+    it 'return human access levels' do
       expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
       expect(helper.note_max_access_for_user(master_note)).to eq('Master')
       expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
-      # Call it again to ensure value is cached
-      expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
     end
 
     it 'handles access in different projects' do
@@ -43,4 +36,16 @@ describe NotesHelper do
       expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
     end
   end
+
+  describe '#preload_max_access_for_authors' do
+    it 'loads multiple users' do
+      expected_access = {
+        owner.id => Gitlab::Access::OWNER,
+        master.id => Gitlab::Access::MASTER,
+        reporter.id => Gitlab::Access::REPORTER
+      }
+
+      expect(helper.preload_max_access_for_authors(notes, project)).to eq(expected_access)
+    end
+  end
 end
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 3f62527c5bbc1e756e69b89ba376d6e198530f04..bf3ed5c094c3779398955743fce81302455a0a04 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -1,36 +1,34 @@
 require 'spec_helper'
 
 describe TimeHelper do
-  describe "#duration_in_words" do
+  describe "#time_interval_in_words" do
     it "returns minutes and seconds" do
       intervals_in_words = {
         100 => "1 minute 40 seconds",
+        100.32 => "1 minute 40 seconds",
         121 => "2 minutes 1 second",
         3721 => "62 minutes 1 second",
         0 => "0 seconds"
       }
 
       intervals_in_words.each do |interval, expectation|
-        expect(duration_in_words(Time.now + interval, Time.now)).to eq(expectation)
+        expect(time_interval_in_words(interval)).to eq(expectation)
       end
     end
-
-    it "calculates interval from now if there is no finished_at" do
-      expect(duration_in_words(nil, Time.now - 5)).to eq("5 seconds")
-    end
   end
 
-  describe "#time_interval_in_words" do
+  describe "#duration_in_numbers" do
     it "returns minutes and seconds" do
-      intervals_in_words = {
-        100 => "1 minute 40 seconds",
-        121 => "2 minutes 1 second",
-        3721 => "62 minutes 1 second",
-        0 => "0 seconds"
+      duration_in_numbers = {
+        [100, 0] => "01:40",
+        [121, 0] => "02:01",
+        [3721, 0] => "01:02:01",
+        [0, 0] => "00:00",
+        [nil, Time.now.to_i - 42] => "00:42"
       }
 
-      intervals_in_words.each do |interval, expectation|
-        expect(time_interval_in_words(interval)).to eq(expectation)
+      duration_in_numbers.each do |interval, expectation|
+        expect(duration_in_numbers(*interval)).to eq(expectation)
       end
     end
   end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 5178bd130f4c1748935693ddbae1266abf5f9448..baab30f482f6554efed482fd6cca8b4f598adc60 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -1,41 +1,58 @@
 require 'spec_helper'
+require_relative '../../config/initializers/6_validations.rb'
 
 describe '6_validations', lib: true do
+  before :all do
+    FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
+    FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
+    FileUtils.mkdir_p('tmp/tests/paths/a/b/d')
+  end
+
+  after :all do
+    FileUtils.rm_rf('tmp/tests/paths')
+  end
+
   context 'with correct settings' do
     before do
-      mock_storages('foo' => '/a/b/c', 'bar' => 'a/b/d')
+      mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d')
     end
 
     it 'passes through' do
-      expect { load_validations }.not_to raise_error
+      expect { validate_storages }.not_to raise_error
     end
   end
 
   context 'with invalid storage names' do
     before do
-      mock_storages('name with spaces' => '/a/b/c')
+      mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c')
     end
 
     it 'throws an error' do
-      expect { load_validations }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
+      expect { validate_storages }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
     end
   end
 
   context 'with nested storage paths' do
     before do
-      mock_storages('foo' => '/a/b/c', 'bar' => '/a/b/c/d')
+      mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d')
     end
 
     it 'throws an error' do
-      expect { load_validations }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.')
+      expect { validate_storages }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.')
     end
   end
 
-  def mock_storages(storages)
-    allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+  context 'with similar but un-nested storage paths' do
+    before do
+      mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2')
+    end
+
+    it 'passes through' do
+      expect { validate_storages }.not_to raise_error
+    end
   end
 
-  def load_validations
-    load File.join(__dir__, '../../config/initializers/6_validations.rb')
+  def mock_storages(storages)
+    allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
   end
 end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 14c8df954a6fc7b41a8cf7be7f9f3b8e1e6ef25b..290e47763eb286e69530d11f7928aa5a86900de5 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -17,6 +17,12 @@ describe 'trusted_proxies', lib: true do
       expect(request.remote_ip).to eq('10.1.5.89')
       expect(request.ip).to eq('10.1.5.89')
     end
+
+    it 'filters out bad values' do
+      request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 10.1.5.89')
+      expect(request.remote_ip).to eq('10.1.5.89')
+      expect(request.ip).to eq('10.1.5.89')
+    end
   end
 
   context 'with private IP ranges added' do
@@ -41,6 +47,12 @@ describe 'trusted_proxies', lib: true do
       expect(request.remote_ip).to eq('1.1.1.1')
       expect(request.ip).to eq('1.1.1.1')
     end
+
+    it 'handles invalid ip addresses' do
+      request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 1.1.1.1:12345, 1.1.1.1')
+      expect(request.remote_ip).to eq('1.1.1.1')
+      expect(request.ip).to eq('1.1.1.1')
+    end
   end
 
   def stub_request(headers = {})
diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b48026c3b773950e5e58e6816a8909e4719234d1
--- /dev/null
+++ b/spec/javascripts/application_spec.js
@@ -0,0 +1,32 @@
+
+/*= require lib/utils/common_utils */
+
+(function() {
+  describe('Application', function() {
+    return describe('disable buttons', function() {
+      fixture.preload('application.html');
+      beforeEach(function() {
+        return fixture.load('application.html');
+      });
+      it('should prevent default action for disabled buttons', function() {
+        var $button, isClicked;
+        gl.utils.preventDisabledButtons();
+        isClicked = false;
+        $button = $('#test-button');
+        $button.click(function() {
+          return isClicked = true;
+        });
+        $button.trigger('click');
+        return expect(isClicked).toBe(false);
+      });
+      return it('should be on the same page if a disabled link clicked', function() {
+        var locationBeforeLinkClick;
+        locationBeforeLinkClick = window.location.href;
+        gl.utils.preventDisabledButtons();
+        $('#test-link').click();
+        return expect(window.location.href).toBe(locationBeforeLinkClick);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee
deleted file mode 100644
index 4b6a2bb544073f19e1250533ff7a504d3579568c..0000000000000000000000000000000000000000
--- a/spec/javascripts/application_spec.js.coffee
+++ /dev/null
@@ -1,30 +0,0 @@
-#= require lib/utils/common_utils
-
-describe 'Application', ->
-  describe 'disable buttons', ->
-    fixture.preload('application.html')
-
-    beforeEach ->
-      fixture.load('application.html')
-
-    it 'should prevent default action for disabled buttons', ->
-
-      gl.utils.preventDisabledButtons()
-
-      isClicked = false
-      $button   = $ '#test-button'
-
-      $button.click -> isClicked = true
-      $button.trigger 'click'
-
-      expect(isClicked).toBe false
-
-
-    it 'should be on the same page if a disabled link clicked', ->
-
-      locationBeforeLinkClick = window.location.href
-      gl.utils.preventDisabledButtons()
-
-      $('#test-link').click()
-
-      expect(window.location.href).toBe locationBeforeLinkClick
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ddc163033e4705c73d900967417553d05393577
--- /dev/null
+++ b/spec/javascripts/awards_handler_spec.js
@@ -0,0 +1,187 @@
+
+/*= require awards_handler */
+
+
+/*= require jquery */
+
+
+/*= require jquery.cookie */
+
+
+/*= require ./fixtures/emoji_menu */
+
+(function() {
+  var awardsHandler, lazyAssert;
+
+  awardsHandler = null;
+
+  window.gl || (window.gl = {});
+
+  window.gon || (window.gon = {});
+
+  gl.emojiAliases = function() {
+    return {
+      '+1': 'thumbsup',
+      '-1': 'thumbsdown'
+    };
+  };
+
+  gon.award_menu_url = '/emojis';
+
+  lazyAssert = function(done, assertFn) {
+    return setTimeout(function() {
+      assertFn();
+      return done();
+    }, 333);
+  };
+
+  describe('AwardsHandler', function() {
+    fixture.preload('awards_handler.html');
+    beforeEach(function() {
+      fixture.load('awards_handler.html');
+      awardsHandler = new AwardsHandler;
+      spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
+        return function(url, emoji, cb) {
+          return cb();
+        };
+      })(this));
+      return spyOn(jQuery, 'get').and.callFake(function(req, cb) {
+        return cb(window.emojiMenu);
+      });
+    });
+    describe('::showEmojiMenu', function() {
+      it('should show emoji menu when Add emoji button clicked', function(done) {
+        $('.js-add-award').eq(0).click();
+        return lazyAssert(done, function() {
+          var $emojiMenu;
+          $emojiMenu = $('.emoji-menu');
+          expect($emojiMenu.length).toBe(1);
+          expect($emojiMenu.hasClass('is-visible')).toBe(true);
+          expect($emojiMenu.find('#emoji_search').length).toBe(1);
+          return expect($('.js-awards-block.current').length).toBe(1);
+        });
+      });
+      it('should also show emoji menu for the smiley icon in notes', function(done) {
+        $('.note-action-button').click();
+        return lazyAssert(done, function() {
+          var $emojiMenu;
+          $emojiMenu = $('.emoji-menu');
+          return expect($emojiMenu.length).toBe(1);
+        });
+      });
+      return it('should remove emoji menu when body is clicked', function(done) {
+        $('.js-add-award').eq(0).click();
+        return lazyAssert(done, function() {
+          var $emojiMenu;
+          $emojiMenu = $('.emoji-menu');
+          $('body').click();
+          expect($emojiMenu.length).toBe(1);
+          expect($emojiMenu.hasClass('is-visible')).toBe(false);
+          return expect($('.js-awards-block.current').length).toBe(0);
+        });
+      });
+    });
+    describe('::addAwardToEmojiBar', function() {
+      it('should add emoji to votes block', function() {
+        var $emojiButton, $votesBlock;
+        $votesBlock = $('.js-awards-block').eq(0);
+        awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+        $emojiButton = $votesBlock.find('[data-emoji=heart]');
+        expect($emojiButton.length).toBe(1);
+        expect($emojiButton.next('.js-counter').text()).toBe('1');
+        return expect($votesBlock.hasClass('hidden')).toBe(false);
+      });
+      it('should remove the emoji when we click again', function() {
+        var $emojiButton, $votesBlock;
+        $votesBlock = $('.js-awards-block').eq(0);
+        awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+        awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+        $emojiButton = $votesBlock.find('[data-emoji=heart]');
+        return expect($emojiButton.length).toBe(0);
+      });
+      return it('should decrement the emoji counter', function() {
+        var $emojiButton, $votesBlock;
+        $votesBlock = $('.js-awards-block').eq(0);
+        awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+        $emojiButton = $votesBlock.find('[data-emoji=heart]');
+        $emojiButton.next('.js-counter').text(5);
+        awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
+        expect($emojiButton.length).toBe(1);
+        return expect($emojiButton.next('.js-counter').text()).toBe('4');
+      });
+    });
+    describe('::getAwardUrl', function() {
+      return it('should return the url for request', function() {
+        return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji');
+      });
+    });
+    describe('::addAward and ::checkMutuality', function() {
+      return it('should handle :+1: and :-1: mutuality', function() {
+        var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        $thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent();
+        $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent();
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
+        expect($thumbsUpEmoji.hasClass('active')).toBe(true);
+        expect($thumbsDownEmoji.hasClass('active')).toBe(false);
+        $thumbsUpEmoji.tooltip();
+        $thumbsDownEmoji.tooltip();
+        awardsHandler.addAward($votesBlock, awardUrl, 'thumbsdown', true);
+        expect($thumbsUpEmoji.hasClass('active')).toBe(false);
+        return expect($thumbsDownEmoji.hasClass('active')).toBe(true);
+      });
+    });
+    describe('::removeEmoji', function() {
+      return it('should remove emoji', function() {
+        var $votesBlock, awardUrl;
+        awardUrl = awardsHandler.getAwardUrl();
+        $votesBlock = $('.js-awards-block').eq(0);
+        awardsHandler.addAward($votesBlock, awardUrl, 'fire', false);
+        expect($votesBlock.find('[data-emoji=fire]').length).toBe(1);
+        awardsHandler.removeEmoji($votesBlock.find('[data-emoji=fire]').closest('button'));
+        return expect($votesBlock.find('[data-emoji=fire]').length).toBe(0);
+      });
+    });
+    describe('search', function() {
+      return it('should filter the emoji', function() {
+        $('.js-add-award').eq(0).click();
+        expect($('[data-emoji=angel]').is(':visible')).toBe(true);
+        expect($('[data-emoji=anger]').is(':visible')).toBe(true);
+        $('#emoji_search').val('ali').trigger('keyup');
+        expect($('[data-emoji=angel]').is(':visible')).toBe(false);
+        expect($('[data-emoji=anger]').is(':visible')).toBe(false);
+        return expect($('[data-emoji=alien]').is(':visible')).toBe(true);
+      });
+    });
+    return describe('emoji menu', function() {
+      var openEmojiMenuAndAddEmoji, selector;
+      selector = '[data-emoji=sunglasses]';
+      openEmojiMenuAndAddEmoji = function() {
+        var $block, $emoji, $menu;
+        $('.js-add-award').eq(0).click();
+        $menu = $('.emoji-menu');
+        $block = $('.js-awards-block');
+        $emoji = $menu.find(".emoji-menu-list-item " + selector);
+        expect($emoji.length).toBe(1);
+        expect($block.find(selector).length).toBe(0);
+        $emoji.click();
+        expect($menu.hasClass('.is-visible')).toBe(false);
+        return expect($block.find(selector).length).toBe(1);
+      };
+      it('should add selected emoji to awards block', function() {
+        return openEmojiMenuAndAddEmoji();
+      });
+      return it('should remove already selected emoji', function() {
+        var $block, $emoji;
+        openEmojiMenuAndAddEmoji();
+        $('.js-add-award').eq(0).click();
+        $block = $('.js-awards-block');
+        $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector);
+        $emoji.click();
+        return expect($block.find(selector).length).toBe(0);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee
deleted file mode 100644
index d7f9c6fc076fc828b38b2c951c1b84be23d902af..0000000000000000000000000000000000000000
--- a/spec/javascripts/awards_handler_spec.js.coffee
+++ /dev/null
@@ -1,200 +0,0 @@
-#= require awards_handler
-#= require jquery
-#= require jquery.cookie
-#= require ./fixtures/emoji_menu
-
-awardsHandler      = null
-window.gl        or= {}
-window.gon       or= {}
-gl.emojiAliases    = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
-gon.award_menu_url = '/emojis'
-
-
-lazyAssert = (done, assertFn) ->
-
-  setTimeout -> # Maybe jasmine.clock here?
-    assertFn()
-    done()
-  , 333
-
-
-describe 'AwardsHandler', ->
-
-  fixture.preload 'awards_handler.html'
-
-  beforeEach ->
-    fixture.load 'awards_handler.html'
-    awardsHandler = new AwardsHandler
-    spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
-    spyOn(jQuery, 'get').and.callFake (req, cb) -> cb window.emojiMenu
-
-
-  describe '::showEmojiMenu', ->
-
-    it 'should show emoji menu when Add emoji button clicked', (done) ->
-
-      $('.js-add-award').eq(0).click()
-
-      lazyAssert done, ->
-        $emojiMenu = $ '.emoji-menu'
-        expect($emojiMenu.length).toBe 1
-        expect($emojiMenu.hasClass('is-visible')).toBe yes
-        expect($emojiMenu.find('#emoji_search').length).toBe 1
-        expect($('.js-awards-block.current').length).toBe 1
-
-
-    it 'should also show emoji menu for the smiley icon in notes', (done) ->
-
-      $('.note-action-button').click()
-
-      lazyAssert done, ->
-        $emojiMenu = $ '.emoji-menu'
-        expect($emojiMenu.length).toBe 1
-
-
-    it 'should remove emoji menu when body is clicked', (done) ->
-
-      $('.js-add-award').eq(0).click()
-
-      lazyAssert done, ->
-        $emojiMenu = $('.emoji-menu')
-        $('body').click()
-        expect($emojiMenu.length).toBe 1
-        expect($emojiMenu.hasClass('is-visible')).toBe no
-        expect($('.js-awards-block.current').length).toBe 0
-
-
-  describe '::addAwardToEmojiBar', ->
-
-    it 'should add emoji to votes block', ->
-
-      $votesBlock = $('.js-awards-block').eq 0
-      awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
-      $emojiButton = $votesBlock.find '[data-emoji=heart]'
-
-      expect($emojiButton.length).toBe 1
-      expect($emojiButton.next('.js-counter').text()).toBe '1'
-      expect($votesBlock.hasClass('hidden')).toBe no
-
-
-    it 'should remove the emoji when we click again', ->
-
-      $votesBlock = $('.js-awards-block').eq 0
-      awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-      awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-      $emojiButton = $votesBlock.find '[data-emoji=heart]'
-
-      expect($emojiButton.length).toBe 0
-
-
-    it 'should decrement the emoji counter', ->
-
-      $votesBlock = $('.js-awards-block').eq 0
-      awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
-      $emojiButton = $votesBlock.find '[data-emoji=heart]'
-      $emojiButton.next('.js-counter').text 5
-
-      awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
-
-      expect($emojiButton.length).toBe 1
-      expect($emojiButton.next('.js-counter').text()).toBe '4'
-
-
-  describe '::getAwardUrl', ->
-
-    it 'should return the url for request', ->
-
-      expect(awardsHandler.getAwardUrl()).toBe '/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'
-
-
-  describe '::addAward and ::checkMutuality', ->
-
-    it 'should handle :+1: and :-1: mutuality', ->
-
-      awardUrl         = awardsHandler.getAwardUrl()
-      $votesBlock      = $('.js-awards-block').eq 0
-      $thumbsUpEmoji   = $votesBlock.find('[data-emoji=thumbsup]').parent()
-      $thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent()
-
-      awardsHandler.addAward $votesBlock, awardUrl, 'thumbsup', no
-
-      expect($thumbsUpEmoji.hasClass('active')).toBe yes
-      expect($thumbsDownEmoji.hasClass('active')).toBe no
-
-      $thumbsUpEmoji.tooltip()
-      $thumbsDownEmoji.tooltip()
-
-      awardsHandler.addAward $votesBlock, awardUrl, 'thumbsdown', yes
-
-      expect($thumbsUpEmoji.hasClass('active')).toBe no
-      expect($thumbsDownEmoji.hasClass('active')).toBe yes
-
-
-  describe '::removeEmoji', ->
-
-    it 'should remove emoji', ->
-
-      awardUrl    = awardsHandler.getAwardUrl()
-      $votesBlock = $('.js-awards-block').eq 0
-
-      awardsHandler.addAward $votesBlock, awardUrl, 'fire',  no
-      expect($votesBlock.find('[data-emoji=fire]').length).toBe  1
-
-      awardsHandler.removeEmoji $votesBlock.find('[data-emoji=fire]').closest('button')
-      expect($votesBlock.find('[data-emoji=fire]').length).toBe  0
-
-
-  describe 'search', ->
-
-    it 'should filter the emoji', ->
-
-      $('.js-add-award').eq(0).click()
-
-      expect($('[data-emoji=angel]').is(':visible')).toBe yes
-      expect($('[data-emoji=anger]').is(':visible')).toBe yes
-
-      $('#emoji_search').val('ali').trigger 'keyup'
-
-      expect($('[data-emoji=angel]').is(':visible')).toBe no
-      expect($('[data-emoji=anger]').is(':visible')).toBe no
-      expect($('[data-emoji=alien]').is(':visible')).toBe yes
-
-
-  describe 'emoji menu', ->
-
-    selector = '[data-emoji=sunglasses]'
-
-    openEmojiMenuAndAddEmoji = ->
-
-      $('.js-add-award').eq(0).click()
-
-      $menu  = $ '.emoji-menu'
-      $block = $ '.js-awards-block'
-      $emoji = $menu.find ".emoji-menu-list-item #{selector}"
-
-      expect($emoji.length).toBe 1
-      expect($block.find(selector).length).toBe 0
-
-      $emoji.click()
-
-      expect($menu.hasClass('.is-visible')).toBe no
-      expect($block.find(selector).length).toBe 1
-
-
-    it 'should add selected emoji to awards block', ->
-
-      openEmojiMenuAndAddEmoji()
-
-
-    it 'should remove already selected emoji', ->
-
-      openEmojiMenuAndAddEmoji()
-      $('.js-add-award').eq(0).click()
-
-      $block = $ '.js-awards-block'
-      $emoji = $('.emoji-menu').find ".emoji-menu-list-item #{selector}"
-
-      $emoji.click()
-      expect($block.find(selector).length).toBe 0
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..78795f7654a6978d2166281dfae8293ba5c105c7
--- /dev/null
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -0,0 +1,21 @@
+
+/*= require behaviors/autosize */
+
+(function() {
+  describe('Autosize behavior', function() {
+    var load;
+    beforeEach(function() {
+      return fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>');
+    });
+    it('does not overwrite the resize property', function() {
+      load();
+      return expect($('textarea')).toHaveCss({
+        resize: 'vertical'
+      });
+    });
+    return load = function() {
+      return $(document).trigger('page:load');
+    };
+  });
+
+}).call(this);
diff --git a/spec/javascripts/behaviors/autosize_spec.js.coffee b/spec/javascripts/behaviors/autosize_spec.js.coffee
deleted file mode 100644
index 7fc1d19c35fa964d199ef1240e7d1a2d11456f9f..0000000000000000000000000000000000000000
--- a/spec/javascripts/behaviors/autosize_spec.js.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-#= require behaviors/autosize
-
-describe 'Autosize behavior', ->
-  beforeEach ->
-    fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
-
-  it 'does not overwrite the resize property', ->
-    load()
-    expect($('textarea')).toHaveCss(resize: 'vertical')
-
-  load = -> $(document).trigger('page:load')
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c52ecd903d6c1b9431423b10003682650972999
--- /dev/null
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -0,0 +1,93 @@
+
+/*= require behaviors/quick_submit */
+
+(function() {
+  describe('Quick Submit behavior', function() {
+    var keydownEvent;
+    fixture.preload('behaviors/quick_submit.html');
+    beforeEach(function() {
+      fixture.load('behaviors/quick_submit.html');
+      $('form').submit(function(e) {
+        return e.preventDefault();
+      });
+      return this.spies = {
+        submit: spyOnEvent('form', 'submit')
+      };
+    });
+    it('does not respond to other keyCodes', function() {
+      $('input.quick-submit-input').trigger(keydownEvent({
+        keyCode: 32
+      }));
+      return expect(this.spies.submit).not.toHaveBeenTriggered();
+    });
+    it('does not respond to Enter alone', function() {
+      $('input.quick-submit-input').trigger(keydownEvent({
+        ctrlKey: false,
+        metaKey: false
+      }));
+      return expect(this.spies.submit).not.toHaveBeenTriggered();
+    });
+    it('does not respond to repeated events', function() {
+      $('input.quick-submit-input').trigger(keydownEvent({
+        repeat: true
+      }));
+      return expect(this.spies.submit).not.toHaveBeenTriggered();
+    });
+    it('disables submit buttons', function() {
+      $('textarea').trigger(keydownEvent());
+      expect($('input[type=submit]')).toBeDisabled();
+      return expect($('button[type=submit]')).toBeDisabled();
+    });
+    if (navigator.userAgent.match(/Macintosh/)) {
+      it('responds to Meta+Enter', function() {
+        $('input.quick-submit-input').trigger(keydownEvent());
+        return expect(this.spies.submit).toHaveBeenTriggered();
+      });
+      it('excludes other modifier keys', function() {
+        $('input.quick-submit-input').trigger(keydownEvent({
+          altKey: true
+        }));
+        $('input.quick-submit-input').trigger(keydownEvent({
+          ctrlKey: true
+        }));
+        $('input.quick-submit-input').trigger(keydownEvent({
+          shiftKey: true
+        }));
+        return expect(this.spies.submit).not.toHaveBeenTriggered();
+      });
+    } else {
+      it('responds to Ctrl+Enter', function() {
+        $('input.quick-submit-input').trigger(keydownEvent());
+        return expect(this.spies.submit).toHaveBeenTriggered();
+      });
+      it('excludes other modifier keys', function() {
+        $('input.quick-submit-input').trigger(keydownEvent({
+          altKey: true
+        }));
+        $('input.quick-submit-input').trigger(keydownEvent({
+          metaKey: true
+        }));
+        $('input.quick-submit-input').trigger(keydownEvent({
+          shiftKey: true
+        }));
+        return expect(this.spies.submit).not.toHaveBeenTriggered();
+      });
+    }
+    return keydownEvent = function(options) {
+      var defaults;
+      if (navigator.userAgent.match(/Macintosh/)) {
+        defaults = {
+          keyCode: 13,
+          metaKey: true
+        };
+      } else {
+        defaults = {
+          keyCode: 13,
+          ctrlKey: true
+        };
+      }
+      return $.Event('keydown', $.extend({}, defaults, options));
+    };
+  });
+
+}).call(this);
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js.coffee b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
deleted file mode 100644
index d3b003a328a70213b172255b22e38501100237ba..0000000000000000000000000000000000000000
--- a/spec/javascripts/behaviors/quick_submit_spec.js.coffee
+++ /dev/null
@@ -1,70 +0,0 @@
-#= require behaviors/quick_submit
-
-describe 'Quick Submit behavior', ->
-  fixture.preload('behaviors/quick_submit.html')
-
-  beforeEach ->
-    fixture.load('behaviors/quick_submit.html')
-
-    # Prevent a form submit from moving us off the testing page
-    $('form').submit (e) -> e.preventDefault()
-
-    @spies = {
-      submit: spyOnEvent('form', 'submit')
-    }
-
-  it 'does not respond to other keyCodes', ->
-    $('input.quick-submit-input').trigger(keydownEvent(keyCode: 32))
-
-    expect(@spies.submit).not.toHaveBeenTriggered()
-
-  it 'does not respond to Enter alone', ->
-    $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
-
-    expect(@spies.submit).not.toHaveBeenTriggered()
-
-  it 'does not respond to repeated events', ->
-    $('input.quick-submit-input').trigger(keydownEvent(repeat: true))
-
-    expect(@spies.submit).not.toHaveBeenTriggered()
-
-  it 'disables submit buttons', ->
-    $('textarea').trigger(keydownEvent())
-
-    expect($('input[type=submit]')).toBeDisabled()
-    expect($('button[type=submit]')).toBeDisabled()
-
-  # We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
-  # only run the tests that apply to the current platform
-  if navigator.userAgent.match(/Macintosh/)
-    it 'responds to Meta+Enter', ->
-      $('input.quick-submit-input').trigger(keydownEvent())
-
-      expect(@spies.submit).toHaveBeenTriggered()
-
-    it 'excludes other modifier keys', ->
-      $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
-      $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: true))
-      $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
-
-      expect(@spies.submit).not.toHaveBeenTriggered()
-  else
-    it 'responds to Ctrl+Enter', ->
-      $('input.quick-submit-input').trigger(keydownEvent())
-
-      expect(@spies.submit).toHaveBeenTriggered()
-
-    it 'excludes other modifier keys', ->
-      $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
-      $('input.quick-submit-input').trigger(keydownEvent(metaKey: true))
-      $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
-
-      expect(@spies.submit).not.toHaveBeenTriggered()
-
-  keydownEvent = (options) ->
-    if navigator.userAgent.match(/Macintosh/)
-      defaults = { keyCode: 13, metaKey: true }
-    else
-      defaults = { keyCode: 13, ctrlKey: true }
-
-    $.Event('keydown', $.extend({}, defaults, options))
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..724c3baf98902217d53578dfa95e351999026d65
--- /dev/null
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -0,0 +1,44 @@
+
+/*= require behaviors/requires_input */
+
+(function() {
+  describe('requiresInput', function() {
+    fixture.preload('behaviors/requires_input.html');
+    beforeEach(function() {
+      return fixture.load('behaviors/requires_input.html');
+    });
+    it('disables submit when any field is required', function() {
+      $('.js-requires-input').requiresInput();
+      return expect($('.submit')).toBeDisabled();
+    });
+    it('enables submit when no field is required', function() {
+      $('*[required=required]').removeAttr('required');
+      $('.js-requires-input').requiresInput();
+      return expect($('.submit')).not.toBeDisabled();
+    });
+    it('enables submit when all required fields are pre-filled', function() {
+      $('*[required=required]').remove();
+      $('.js-requires-input').requiresInput();
+      return expect($('.submit')).not.toBeDisabled();
+    });
+    it('enables submit when all required fields receive input', function() {
+      $('.js-requires-input').requiresInput();
+      $('#required1').val('input1').change();
+      expect($('.submit')).toBeDisabled();
+      $('#optional1').val('input1').change();
+      expect($('.submit')).toBeDisabled();
+      $('#required2').val('input2').change();
+      $('#required3').val('input3').change();
+      $('#required4').val('input4').change();
+      $('#required5').val('1').change();
+      return expect($('.submit')).not.toBeDisabled();
+    });
+    return it('is called on page:load event', function() {
+      var spy;
+      spy = spyOn($.fn, 'requiresInput');
+      $(document).trigger('page:load');
+      return expect(spy).toHaveBeenCalled();
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/behaviors/requires_input_spec.js.coffee b/spec/javascripts/behaviors/requires_input_spec.js.coffee
deleted file mode 100644
index 61a176321739d59fa916777fb8e6d767ed9e382b..0000000000000000000000000000000000000000
--- a/spec/javascripts/behaviors/requires_input_spec.js.coffee
+++ /dev/null
@@ -1,49 +0,0 @@
-#= require behaviors/requires_input
-
-describe 'requiresInput', ->
-  fixture.preload('behaviors/requires_input.html')
-
-  beforeEach ->
-    fixture.load('behaviors/requires_input.html')
-
-  it 'disables submit when any field is required', ->
-    $('.js-requires-input').requiresInput()
-
-    expect($('.submit')).toBeDisabled()
-
-  it 'enables submit when no field is required', ->
-    $('*[required=required]').removeAttr('required')
-
-    $('.js-requires-input').requiresInput()
-
-    expect($('.submit')).not.toBeDisabled()
-
-  it 'enables submit when all required fields are pre-filled', ->
-    $('*[required=required]').remove()
-
-    $('.js-requires-input').requiresInput()
-
-    expect($('.submit')).not.toBeDisabled()
-
-  it 'enables submit when all required fields receive input', ->
-    $('.js-requires-input').requiresInput()
-
-    $('#required1').val('input1').change()
-    expect($('.submit')).toBeDisabled()
-
-    $('#optional1').val('input1').change()
-    expect($('.submit')).toBeDisabled()
-
-    $('#required2').val('input2').change()
-    $('#required3').val('input3').change()
-    $('#required4').val('input4').change()
-    $('#required5').val('1').change()
-
-    expect($('.submit')).not.toBeDisabled()
-
-  it 'is called on page:load event', ->
-    spy = spyOn($.fn, 'requiresInput')
-
-    $(document).trigger('page:load')
-
-    expect(spy).toHaveBeenCalled()
diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..eced2f6575d3ad9f3e4211f03d73f7bd732e3907
--- /dev/null
+++ b/spec/javascripts/extensions/array_spec.js
@@ -0,0 +1,22 @@
+
+/*= require extensions/array */
+
+(function() {
+  describe('Array extensions', function() {
+    describe('first', function() {
+      return it('returns the first item', function() {
+        var arr;
+        arr = [0, 1, 2, 3, 4, 5];
+        return expect(arr.first()).toBe(0);
+      });
+    });
+    return describe('last', function() {
+      return it('returns the last item', function() {
+        var arr;
+        arr = [0, 1, 2, 3, 4, 5];
+        return expect(arr.last()).toBe(5);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee
deleted file mode 100644
index 4ceac619422869f0f2c420743fc44d9cc711bb6c..0000000000000000000000000000000000000000
--- a/spec/javascripts/extensions/array_spec.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require extensions/array
-
-describe 'Array extensions', ->
-  describe 'first', ->
-    it 'returns the first item', ->
-      arr = [0, 1, 2, 3, 4, 5]
-      expect(arr.first()).toBe(0)
-
-  describe 'last', ->
-    it 'returns the last item', ->
-      arr = [0, 1, 2, 3, 4, 5]
-      expect(arr.last()).toBe(5)
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b644344b95a5e411a5e6126a82d43261d11fdb4c
--- /dev/null
+++ b/spec/javascripts/extensions/jquery_spec.js
@@ -0,0 +1,42 @@
+
+/*= require extensions/jquery */
+
+(function() {
+  describe('jQuery extensions', function() {
+    describe('disable', function() {
+      beforeEach(function() {
+        return fixture.set('<input type="text" />');
+      });
+      it('adds the disabled attribute', function() {
+        var $input;
+        $input = $('input').first();
+        $input.disable();
+        return expect($input).toHaveAttr('disabled', 'disabled');
+      });
+      return it('adds the disabled class', function() {
+        var $input;
+        $input = $('input').first();
+        $input.disable();
+        return expect($input).toHaveClass('disabled');
+      });
+    });
+    return describe('enable', function() {
+      beforeEach(function() {
+        return fixture.set('<input type="text" disabled="disabled" class="disabled" />');
+      });
+      it('removes the disabled attribute', function() {
+        var $input;
+        $input = $('input').first();
+        $input.enable();
+        return expect($input).not.toHaveAttr('disabled');
+      });
+      return it('removes the disabled class', function() {
+        var $input;
+        $input = $('input').first();
+        $input.enable();
+        return expect($input).not.toHaveClass('disabled');
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee
deleted file mode 100644
index b10e16b7d01353db9df47e695279cf63f4347aff..0000000000000000000000000000000000000000
--- a/spec/javascripts/extensions/jquery_spec.js.coffee
+++ /dev/null
@@ -1,34 +0,0 @@
-#= require extensions/jquery
-
-describe 'jQuery extensions', ->
-  describe 'disable', ->
-    beforeEach ->
-      fixture.set '<input type="text" />'
-
-    it 'adds the disabled attribute', ->
-      $input = $('input').first()
-
-      $input.disable()
-      expect($input).toHaveAttr('disabled', 'disabled')
-
-    it 'adds the disabled class', ->
-      $input = $('input').first()
-
-      $input.disable()
-      expect($input).toHaveClass('disabled')
-
-  describe 'enable', ->
-    beforeEach ->
-      fixture.set '<input type="text" disabled="disabled" class="disabled" />'
-
-    it 'removes the disabled attribute', ->
-      $input = $('input').first()
-
-      $input.enable()
-      expect($input).not.toHaveAttr('disabled')
-
-    it 'removes the disabled class', ->
-      $input = $('input').first()
-
-      $input.enable()
-      expect($input).not.toHaveClass('disabled')
diff --git a/spec/javascripts/fixtures/emoji_menu.coffee b/spec/javascripts/fixtures/emoji_menu.coffee
deleted file mode 100644
index ce1a41390d20a0fbda5967712c2c176dec1794b2..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/emoji_menu.coffee
+++ /dev/null
@@ -1,957 +0,0 @@
-window.emojiMenu = """
-  <div class='emoji-menu'>
-    <input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" />
-    <div class='emoji-menu-content'>
-      <h5 class='emoji-menu-title'>
-      Emoticons
-      </h5>
-      <ul class='clearfix emoji-menu-list'>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F47D" title="alien" data-aliases="" data-emoji="alien" data-unicode-name="1F47D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F47C" title="angel" data-aliases="" data-emoji="angel" data-unicode-name="1F47C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A2" title="anger" data-aliases="" data-emoji="anger" data-unicode-name="1F4A2"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F620" title="angry" data-aliases="" data-emoji="angry" data-unicode-name="1F620"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F627" title="anguished" data-aliases="" data-emoji="anguished" data-unicode-name="1F627"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F632" title="astonished" data-aliases="" data-emoji="astonished" data-unicode-name="1F632"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45F" title="athletic_shoe" data-aliases="" data-emoji="athletic_shoe" data-unicode-name="1F45F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F476" title="baby" data-aliases="" data-emoji="baby" data-unicode-name="1F476"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F459" title="bikini" data-aliases="" data-emoji="bikini" data-unicode-name="1F459"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F499" title="blue_heart" data-aliases="" data-emoji="blue_heart" data-unicode-name="1F499"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60A" title="blush" data-aliases="" data-emoji="blush" data-unicode-name="1F60A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A5" title="boom" data-aliases="" data-emoji="boom" data-unicode-name="1F4A5"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F462" title="boot" data-aliases="" data-emoji="boot" data-unicode-name="1F462"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F647" title="bow" data-aliases="" data-emoji="bow" data-unicode-name="1F647"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F466" title="boy" data-aliases="" data-emoji="boy" data-unicode-name="1F466"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F470" title="bride_with_veil" data-aliases="" data-emoji="bride_with_veil" data-unicode-name="1F470"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4BC" title="briefcase" data-aliases="" data-emoji="briefcase" data-unicode-name="1F4BC"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F494" title="broken_heart" data-aliases="" data-emoji="broken_heart" data-unicode-name="1F494"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F464" title="bust_in_silhouette" data-aliases="" data-emoji="bust_in_silhouette" data-unicode-name="1F464"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F465" title="busts_in_silhouette" data-aliases="" data-emoji="busts_in_silhouette" data-unicode-name="1F465"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44F" title="clap" data-aliases="" data-emoji="clap" data-unicode-name="1F44F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F302" title="closed_umbrella" data-aliases="" data-emoji="closed_umbrella" data-unicode-name="1F302"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F630" title="cold_sweat" data-aliases="" data-emoji="cold_sweat" data-unicode-name="1F630"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F616" title="confounded" data-aliases="" data-emoji="confounded" data-unicode-name="1F616"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F615" title="confused" data-aliases="" data-emoji="confused" data-unicode-name="1F615"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F477" title="construction_worker" data-aliases="" data-emoji="construction_worker" data-unicode-name="1F477"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46E" title="cop" data-aliases="" data-emoji="cop" data-unicode-name="1F46E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46B" title="couple" data-aliases="" data-emoji="couple" data-unicode-name="1F46B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F491" title="couple_with_heart" data-aliases="" data-emoji="couple_with_heart" data-unicode-name="1F491"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F48F" title="couplekiss" data-aliases="" data-emoji="couplekiss" data-unicode-name="1F48F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F451" title="crown" data-aliases="" data-emoji="crown" data-unicode-name="1F451"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F622" title="cry" data-aliases="" data-emoji="cry" data-unicode-name="1F622"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63F" title="crying_cat_face" data-aliases="" data-emoji="crying_cat_face" data-unicode-name="1F63F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F498" title="cupid" data-aliases="" data-emoji="cupid" data-unicode-name="1F498"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F483" title="dancer" data-aliases="" data-emoji="dancer" data-unicode-name="1F483"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46F" title="dancers" data-aliases="" data-emoji="dancers" data-unicode-name="1F46F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A8" title="dash" data-aliases="" data-emoji="dash" data-unicode-name="1F4A8"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61E" title="disappointed" data-aliases="" data-emoji="disappointed" data-unicode-name="1F61E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F625" title="disappointed_relieved" data-aliases="" data-emoji="disappointed_relieved" data-unicode-name="1F625"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4AB" title="dizzy" data-aliases="" data-emoji="dizzy" data-unicode-name="1F4AB"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F635" title="dizzy_face" data-aliases="" data-emoji="dizzy_face" data-unicode-name="1F635"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F457" title="dress" data-aliases="" data-emoji="dress" data-unicode-name="1F457"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A7" title="droplet" data-aliases="" data-emoji="droplet" data-unicode-name="1F4A7"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F442" title="ear" data-aliases="" data-emoji="ear" data-unicode-name="1F442"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F611" title="expressionless" data-aliases="" data-emoji="expressionless" data-unicode-name="1F611"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F453" title="eyeglasses" data-aliases="" data-emoji="eyeglasses" data-unicode-name="1F453"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F440" title="eyes" data-aliases="" data-emoji="eyes" data-unicode-name="1F440"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46A" title="family" data-aliases="" data-emoji="family" data-unicode-name="1F46A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F628" title="fearful" data-aliases="" data-emoji="fearful" data-unicode-name="1F628"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F525" title="fire" data-aliases=":flame:" data-emoji="fire" data-unicode-name="1F525"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-270A" title="fist" data-aliases="" data-emoji="fist" data-unicode-name="270A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F633" title="flushed" data-aliases="" data-emoji="flushed" data-unicode-name="1F633"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F463" title="footprints" data-aliases="" data-emoji="footprints" data-unicode-name="1F463"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F626" title="frowning" data-aliases=":anguished:" data-emoji="frowning" data-unicode-name="1F626"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F48E" title="gem" data-aliases="" data-emoji="gem" data-unicode-name="1F48E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F467" title="girl" data-aliases="" data-emoji="girl" data-unicode-name="1F467"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F49A" title="green_heart" data-aliases="" data-emoji="green_heart" data-unicode-name="1F49A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62C" title="grimacing" data-aliases="" data-emoji="grimacing" data-unicode-name="1F62C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F601" title="grin" data-aliases="" data-emoji="grin" data-unicode-name="1F601"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F600" title="grinning" data-aliases="" data-emoji="grinning" data-unicode-name="1F600"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F482" title="guardsman" data-aliases="" data-emoji="guardsman" data-unicode-name="1F482"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F487" title="haircut" data-aliases="" data-emoji="haircut" data-unicode-name="1F487"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45C" title="handbag" data-aliases="" data-emoji="handbag" data-unicode-name="1F45C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F649" title="hear_no_evil" data-aliases="" data-emoji="hear_no_evil" data-unicode-name="1F649"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-2764" title="heart" data-aliases="" data-emoji="heart" data-unicode-name="2764"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60D" title="heart_eyes" data-aliases="" data-emoji="heart_eyes" data-unicode-name="1F60D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63B" title="heart_eyes_cat" data-aliases="" data-emoji="heart_eyes_cat" data-unicode-name="1F63B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F493" title="heartbeat" data-aliases="" data-emoji="heartbeat" data-unicode-name="1F493"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F497" title="heartpulse" data-aliases="" data-emoji="heartpulse" data-unicode-name="1F497"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F460" title="high_heel" data-aliases="" data-emoji="high_heel" data-unicode-name="1F460"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62F" title="hushed" data-aliases="" data-emoji="hushed" data-unicode-name="1F62F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F47F" title="imp" data-aliases="" data-emoji="imp" data-unicode-name="1F47F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F481" title="information_desk_person" data-aliases="" data-emoji="information_desk_person" data-unicode-name="1F481"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F607" title="innocent" data-aliases="" data-emoji="innocent" data-unicode-name="1F607"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F47A" title="japanese_goblin" data-aliases="" data-emoji="japanese_goblin" data-unicode-name="1F47A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F479" title="japanese_ogre" data-aliases="" data-emoji="japanese_ogre" data-unicode-name="1F479"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F456" title="jeans" data-aliases="" data-emoji="jeans" data-unicode-name="1F456"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F602" title="joy" data-aliases="" data-emoji="joy" data-unicode-name="1F602"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F639" title="joy_cat" data-aliases="" data-emoji="joy_cat" data-unicode-name="1F639"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F458" title="kimono" data-aliases="" data-emoji="kimono" data-unicode-name="1F458"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F48B" title="kiss" data-aliases="" data-emoji="kiss" data-unicode-name="1F48B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F617" title="kissing" data-aliases="" data-emoji="kissing" data-unicode-name="1F617"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63D" title="kissing_cat" data-aliases="" data-emoji="kissing_cat" data-unicode-name="1F63D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61A" title="kissing_closed_eyes" data-aliases="" data-emoji="kissing_closed_eyes" data-unicode-name="1F61A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F618" title="kissing_heart" data-aliases="" data-emoji="kissing_heart" data-unicode-name="1F618"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F619" title="kissing_smiling_eyes" data-aliases="" data-emoji="kissing_smiling_eyes" data-unicode-name="1F619"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F606" title="laughing" data-aliases=":satisfied:" data-emoji="laughing" data-unicode-name="1F606"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F444" title="lips" data-aliases="" data-emoji="lips" data-unicode-name="1F444"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F484" title="lipstick" data-aliases="" data-emoji="lipstick" data-unicode-name="1F484"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F48C" title="love_letter" data-aliases="" data-emoji="love_letter" data-unicode-name="1F48C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F468" title="man" data-aliases="" data-emoji="man" data-unicode-name="1F468"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F472" title="man_with_gua_pi_mao" data-aliases="" data-emoji="man_with_gua_pi_mao" data-unicode-name="1F472"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F473" title="man_with_turban" data-aliases="" data-emoji="man_with_turban" data-unicode-name="1F473"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45E" title="mans_shoe" data-aliases="" data-emoji="mans_shoe" data-unicode-name="1F45E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F637" title="mask" data-aliases="" data-emoji="mask" data-unicode-name="1F637"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F486" title="massage" data-aliases="" data-emoji="massage" data-unicode-name="1F486"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4AA" title="muscle" data-aliases="" data-emoji="muscle" data-unicode-name="1F4AA"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F485" title="nail_care" data-aliases="" data-emoji="nail_care" data-unicode-name="1F485"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F454" title="necktie" data-aliases="" data-emoji="necktie" data-unicode-name="1F454"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F610" title="neutral_face" data-aliases="" data-emoji="neutral_face" data-unicode-name="1F610"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F645" title="no_good" data-aliases="" data-emoji="no_good" data-unicode-name="1F645"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F636" title="no_mouth" data-aliases="" data-emoji="no_mouth" data-unicode-name="1F636"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F443" title="nose" data-aliases="" data-emoji="nose" data-unicode-name="1F443"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44C" title="ok_hand" data-aliases="" data-emoji="ok_hand" data-unicode-name="1F44C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F646" title="ok_woman" data-aliases="" data-emoji="ok_woman" data-unicode-name="1F646"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F474" title="older_man" data-aliases="" data-emoji="older_man" data-unicode-name="1F474"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F475" title="older_woman" data-aliases=":grandma:" data-emoji="older_woman" data-unicode-name="1F475"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F450" title="open_hands" data-aliases="" data-emoji="open_hands" data-unicode-name="1F450"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62E" title="open_mouth" data-aliases="" data-emoji="open_mouth" data-unicode-name="1F62E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F614" title="pensive" data-aliases="" data-emoji="pensive" data-unicode-name="1F614"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F623" title="persevere" data-aliases="" data-emoji="persevere" data-unicode-name="1F623"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64D" title="person_frowning" data-aliases="" data-emoji="person_frowning" data-unicode-name="1F64D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F471" title="person_with_blond_hair" data-aliases="" data-emoji="person_with_blond_hair" data-unicode-name="1F471"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64E" title="person_with_pouting_face" data-aliases="" data-emoji="person_with_pouting_face" data-unicode-name="1F64E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F447" title="point_down" data-aliases="" data-emoji="point_down" data-unicode-name="1F447"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F448" title="point_left" data-aliases="" data-emoji="point_left" data-unicode-name="1F448"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F449" title="point_right" data-aliases="" data-emoji="point_right" data-unicode-name="1F449"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-261D" title="point_up" data-aliases="" data-emoji="point_up" data-unicode-name="261D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F446" title="point_up_2" data-aliases="" data-emoji="point_up_2" data-unicode-name="1F446"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A9" title="poop" data-aliases=":shit: :hankey: :poo:" data-emoji="poop" data-unicode-name="1F4A9"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45D" title="pouch" data-aliases="" data-emoji="pouch" data-unicode-name="1F45D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63E" title="pouting_cat" data-aliases="" data-emoji="pouting_cat" data-unicode-name="1F63E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64F" title="pray" data-aliases="" data-emoji="pray" data-unicode-name="1F64F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F478" title="princess" data-aliases="" data-emoji="princess" data-unicode-name="1F478"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44A" title="punch" data-aliases="" data-emoji="punch" data-unicode-name="1F44A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F49C" title="purple_heart" data-aliases="" data-emoji="purple_heart" data-unicode-name="1F49C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45B" title="purse" data-aliases="" data-emoji="purse" data-unicode-name="1F45B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F621" title="rage" data-aliases="" data-emoji="rage" data-unicode-name="1F621"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-270B" title="raised_hand" data-aliases="" data-emoji="raised_hand" data-unicode-name="270B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64C" title="raised_hands" data-aliases="" data-emoji="raised_hands" data-unicode-name="1F64C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64B" title="raising_hand" data-aliases="" data-emoji="raising_hand" data-unicode-name="1F64B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-263A" title="relaxed" data-aliases="" data-emoji="relaxed" data-unicode-name="263A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60C" title="relieved" data-aliases="" data-emoji="relieved" data-unicode-name="1F60C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F49E" title="revolving_hearts" data-aliases="" data-emoji="revolving_hearts" data-unicode-name="1F49E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F380" title="ribbon" data-aliases="" data-emoji="ribbon" data-unicode-name="1F380"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F48D" title="ring" data-aliases="" data-emoji="ring" data-unicode-name="1F48D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F3C3" title="runner" data-aliases="" data-emoji="runner" data-unicode-name="1F3C3"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F3BD" title="running_shirt_with_sash" data-aliases="" data-emoji="running_shirt_with_sash" data-unicode-name="1F3BD"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F461" title="sandal" data-aliases="" data-emoji="sandal" data-unicode-name="1F461"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F631" title="scream" data-aliases="" data-emoji="scream" data-unicode-name="1F631"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F640" title="scream_cat" data-aliases="" data-emoji="scream_cat" data-unicode-name="1F640"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F648" title="see_no_evil" data-aliases="" data-emoji="see_no_evil" data-unicode-name="1F648"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F455" title="shirt" data-aliases="" data-emoji="shirt" data-unicode-name="1F455"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F480" title="skull" data-aliases=":skeleton:" data-emoji="skull" data-unicode-name="1F480"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F634" title="sleeping" data-aliases="" data-emoji="sleeping" data-unicode-name="1F634"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62A" title="sleepy" data-aliases="" data-emoji="sleepy" data-unicode-name="1F62A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F604" title="smile" data-aliases="" data-emoji="smile" data-unicode-name="1F604"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F638" title="smile_cat" data-aliases="" data-emoji="smile_cat" data-unicode-name="1F638"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F603" title="smiley" data-aliases="" data-emoji="smiley" data-unicode-name="1F603"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63A" title="smiley_cat" data-aliases="" data-emoji="smiley_cat" data-unicode-name="1F63A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F608" title="smiling_imp" data-aliases="" data-emoji="smiling_imp" data-unicode-name="1F608"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60F" title="smirk" data-aliases="" data-emoji="smirk" data-unicode-name="1F60F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F63C" title="smirk_cat" data-aliases="" data-emoji="smirk_cat" data-unicode-name="1F63C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62D" title="sob" data-aliases="" data-emoji="sob" data-unicode-name="1F62D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-2728" title="sparkles" data-aliases="" data-emoji="sparkles" data-unicode-name="2728"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F496" title="sparkling_heart" data-aliases="" data-emoji="sparkling_heart" data-unicode-name="1F496"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F64A" title="speak_no_evil" data-aliases="" data-emoji="speak_no_evil" data-unicode-name="1F64A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4AC" title="speech_balloon" data-aliases="" data-emoji="speech_balloon" data-unicode-name="1F4AC"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F31F" title="star2" data-aliases="" data-emoji="star2" data-unicode-name="1F31F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61B" title="stuck_out_tongue" data-aliases="" data-emoji="stuck_out_tongue" data-unicode-name="1F61B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61D" title="stuck_out_tongue_closed_eyes" data-aliases="" data-emoji="stuck_out_tongue_closed_eyes" data-unicode-name="1F61D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61C" title="stuck_out_tongue_winking_eye" data-aliases="" data-emoji="stuck_out_tongue_winking_eye" data-unicode-name="1F61C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60E" title="sunglasses" data-aliases="" data-emoji="sunglasses" data-unicode-name="1F60E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F613" title="sweat" data-aliases="" data-emoji="sweat" data-unicode-name="1F613"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A6" title="sweat_drops" data-aliases="" data-emoji="sweat_drops" data-unicode-name="1F4A6"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F605" title="sweat_smile" data-aliases="" data-emoji="sweat_smile" data-unicode-name="1F605"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4AD" title="thought_balloon" data-aliases="" data-emoji="thought_balloon" data-unicode-name="1F4AD"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44E" title="thumbsdown" data-aliases=":-1:" data-emoji="thumbsdown" data-unicode-name="1F44E"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44D" title="thumbsup" data-aliases=":+1:" data-emoji="thumbsup" data-unicode-name="1F44D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F62B" title="tired_face" data-aliases="" data-emoji="tired_face" data-unicode-name="1F62B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F445" title="tongue" data-aliases="" data-emoji="tongue" data-unicode-name="1F445"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F3A9" title="tophat" data-aliases="" data-emoji="tophat" data-unicode-name="1F3A9"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F624" title="triumph" data-aliases="" data-emoji="triumph" data-unicode-name="1F624"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F495" title="two_hearts" data-aliases="" data-emoji="two_hearts" data-unicode-name="1F495"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46C" title="two_men_holding_hands" data-aliases="" data-emoji="two_men_holding_hands" data-unicode-name="1F46C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F46D" title="two_women_holding_hands" data-aliases="" data-emoji="two_women_holding_hands" data-unicode-name="1F46D"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F612" title="unamused" data-aliases="" data-emoji="unamused" data-unicode-name="1F612"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-270C" title="v" data-aliases="" data-emoji="v" data-unicode-name="270C"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F6B6" title="walking" data-aliases="" data-emoji="walking" data-unicode-name="1F6B6"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F44B" title="wave" data-aliases="" data-emoji="wave" data-unicode-name="1F44B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F629" title="weary" data-aliases="" data-emoji="weary" data-unicode-name="1F629"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F609" title="wink" data-aliases="" data-emoji="wink" data-unicode-name="1F609"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F469" title="woman" data-aliases="" data-emoji="woman" data-unicode-name="1F469"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F45A" title="womans_clothes" data-aliases="" data-emoji="womans_clothes" data-unicode-name="1F45A"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F452" title="womans_hat" data-aliases="" data-emoji="womans_hat" data-unicode-name="1F452"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F61F" title="worried" data-aliases="" data-emoji="worried" data-unicode-name="1F61F"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F49B" title="yellow_heart" data-aliases="" data-emoji="yellow_heart" data-unicode-name="1F49B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F60B" title="yum" data-aliases="" data-emoji="yum" data-unicode-name="1F60B"></div>
-          </button>
-        </li>
-        <li class='pull-left text-center emoji-menu-list-item'>
-          <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
-          <div class="icon emoji-icon emoji-1F4A4" title="zzz" data-aliases="" data-emoji="zzz" data-unicode-name="1F4A4"></div>
-          </button>
-        </li>
-      </ul>
-    </div>
-  </div>
-"""
diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..99e3f7247bdfa0aa10bb7016383cb7464cf3ffff
--- /dev/null
+++ b/spec/javascripts/fixtures/emoji_menu.js
@@ -0,0 +1,4 @@
+(function() {
+  window.emojiMenu = "<div class='emoji-menu'>\n  <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n  <div class='emoji-menu-content'>\n    <h5 class='emoji-menu-title'>\n    Emoticons\n    </h5>\n    <ul class='clearfix emoji-menu-list'>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n        </button>\n      </li>\n      <li class='pull-left text-center emoji-menu-list-item'>\n        <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n        <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n        </button>\n      </li>\n    </ul>\n  </div>\n</div>";
+
+}).call(this);
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc6231ebb38469ab22b38d64057775ad5ab469e5
--- /dev/null
+++ b/spec/javascripts/issue_spec.js
@@ -0,0 +1,121 @@
+
+/*= require lib/utils/text_utility */
+
+
+/*= require issue */
+
+(function() {
+  describe('Issue', function() {
+    return describe('task lists', function() {
+      fixture.preload('issues_show.html');
+      beforeEach(function() {
+        fixture.load('issues_show.html');
+        return this.issue = new Issue();
+      });
+      it('modifies the Markdown field', function() {
+        spyOn(jQuery, 'ajax').and.stub();
+        $('input[type=checkbox]').attr('checked', true).trigger('change');
+        return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+      });
+      return it('submits an ajax request on tasklist:changed', function() {
+        spyOn(jQuery, 'ajax').and.callFake(function(req) {
+          expect(req.type).toBe('PATCH');
+          expect(req.url).toBe('/foo');
+          return expect(req.data.issue.description).not.toBe(null);
+        });
+        return $('.js-task-list-field').trigger('tasklist:changed');
+      });
+    });
+  });
+
+  describe('reopen/close issue', function() {
+    fixture.preload('issues_show.html');
+    beforeEach(function() {
+      fixture.load('issues_show.html');
+      return this.issue = new Issue();
+    });
+    it('closes an issue', function() {
+      var $btnClose, $btnReopen;
+      spyOn(jQuery, 'ajax').and.callFake(function(req) {
+        expect(req.type).toBe('PUT');
+        expect(req.url).toBe('http://gitlab.com/issues/6/close');
+        return req.success({
+          id: 34
+        });
+      });
+      $btnClose = $('a.btn-close');
+      $btnReopen = $('a.btn-reopen');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose.text()).toBe('Close');
+      expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+      $btnClose.trigger('click');
+      expect($btnReopen).toBeVisible();
+      expect($btnClose).toBeHidden();
+      expect($('div.status-box-closed')).toBeVisible();
+      return expect($('div.status-box-open')).toBeHidden();
+    });
+    it('fails to close an issue with success:false', function() {
+      var $btnClose, $btnReopen;
+      spyOn(jQuery, 'ajax').and.callFake(function(req) {
+        expect(req.type).toBe('PUT');
+        expect(req.url).toBe('http://goesnowhere.nothing/whereami');
+        return req.success({
+          saved: false
+        });
+      });
+      $btnClose = $('a.btn-close');
+      $btnReopen = $('a.btn-reopen');
+      $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose.text()).toBe('Close');
+      expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+      $btnClose.trigger('click');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose).toBeVisible();
+      expect($('div.status-box-closed')).toBeHidden();
+      expect($('div.status-box-open')).toBeVisible();
+      expect($('div.flash-alert')).toBeVisible();
+      return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+    });
+    it('fails to closes an issue with HTTP error', function() {
+      var $btnClose, $btnReopen;
+      spyOn(jQuery, 'ajax').and.callFake(function(req) {
+        expect(req.type).toBe('PUT');
+        expect(req.url).toBe('http://goesnowhere.nothing/whereami');
+        return req.error();
+      });
+      $btnClose = $('a.btn-close');
+      $btnReopen = $('a.btn-reopen');
+      $btnClose.attr('href', 'http://goesnowhere.nothing/whereami');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose.text()).toBe('Close');
+      expect(typeof $btnClose.prop('disabled')).toBe('undefined');
+      $btnClose.trigger('click');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose).toBeVisible();
+      expect($('div.status-box-closed')).toBeHidden();
+      expect($('div.status-box-open')).toBeVisible();
+      expect($('div.flash-alert')).toBeVisible();
+      return expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.');
+    });
+    return it('reopens an issue', function() {
+      var $btnClose, $btnReopen;
+      spyOn(jQuery, 'ajax').and.callFake(function(req) {
+        expect(req.type).toBe('PUT');
+        expect(req.url).toBe('http://gitlab.com/issues/6/reopen');
+        return req.success({
+          id: 34
+        });
+      });
+      $btnClose = $('a.btn-close');
+      $btnReopen = $('a.btn-reopen');
+      expect($btnReopen.text()).toBe('Reopen');
+      $btnReopen.trigger('click');
+      expect($btnReopen).toBeHidden();
+      expect($btnClose).toBeVisible();
+      expect($('div.status-box-open')).toBeVisible();
+      return expect($('div.status-box-closed')).toBeHidden();
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
deleted file mode 100644
index d84d80f266bc950a7f203b8d73f440601391c942..0000000000000000000000000000000000000000
--- a/spec/javascripts/issue_spec.js.coffee
+++ /dev/null
@@ -1,109 +0,0 @@
-#= require lib/utils/text_utility
-#= require issue
-
-describe 'Issue', ->
-  describe 'task lists', ->
-    fixture.preload('issues_show.html')
-
-    beforeEach ->
-      fixture.load('issues_show.html')
-      @issue = new Issue()
-
-    it 'modifies the Markdown field', ->
-      spyOn(jQuery, 'ajax').and.stub()
-      $('input[type=checkbox]').attr('checked', true).trigger('change')
-      expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
-    it 'submits an ajax request on tasklist:changed', ->
-      spyOn(jQuery, 'ajax').and.callFake (req) ->
-        expect(req.type).toBe('PATCH')
-        expect(req.url).toBe('/foo')
-        expect(req.data.issue.description).not.toBe(null)
-
-      $('.js-task-list-field').trigger('tasklist:changed')
-describe 'reopen/close issue', ->
-  fixture.preload('issues_show.html')
-  beforeEach ->
-    fixture.load('issues_show.html')
-    @issue = new Issue()
-  it 'closes an issue', ->
-    spyOn(jQuery, 'ajax').and.callFake (req) ->
-      expect(req.type).toBe('PUT')
-      expect(req.url).toBe('http://gitlab.com/issues/6/close')
-      req.success id: 34
-
-    $btnClose = $('a.btn-close')
-    $btnReopen = $('a.btn-reopen')
-    expect($btnReopen).toBeHidden()
-    expect($btnClose.text()).toBe('Close')
-    expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
-    $btnClose.trigger('click')
-
-    expect($btnReopen).toBeVisible()
-    expect($btnClose).toBeHidden()
-    expect($('div.status-box-closed')).toBeVisible()
-    expect($('div.status-box-open')).toBeHidden()
-
-  it 'fails to close an issue with success:false', ->
-
-    spyOn(jQuery, 'ajax').and.callFake (req) ->
-      expect(req.type).toBe('PUT')
-      expect(req.url).toBe('http://goesnowhere.nothing/whereami')
-      req.success saved: false
-
-    $btnClose = $('a.btn-close')
-    $btnReopen = $('a.btn-reopen')
-    $btnClose.attr('href','http://goesnowhere.nothing/whereami')
-    expect($btnReopen).toBeHidden()
-    expect($btnClose.text()).toBe('Close')
-    expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
-    $btnClose.trigger('click')
-
-    expect($btnReopen).toBeHidden()
-    expect($btnClose).toBeVisible()
-    expect($('div.status-box-closed')).toBeHidden()
-    expect($('div.status-box-open')).toBeVisible()
-    expect($('div.flash-alert')).toBeVisible()
-    expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
-
-  it 'fails to closes an issue with HTTP error', ->
-
-    spyOn(jQuery, 'ajax').and.callFake (req) ->
-      expect(req.type).toBe('PUT')
-      expect(req.url).toBe('http://goesnowhere.nothing/whereami')
-      req.error()
-
-    $btnClose = $('a.btn-close')
-    $btnReopen = $('a.btn-reopen')
-    $btnClose.attr('href','http://goesnowhere.nothing/whereami')
-    expect($btnReopen).toBeHidden()
-    expect($btnClose.text()).toBe('Close')
-    expect(typeof $btnClose.prop('disabled')).toBe('undefined')
-
-    $btnClose.trigger('click')
-
-    expect($btnReopen).toBeHidden()
-    expect($btnClose).toBeVisible()
-    expect($('div.status-box-closed')).toBeHidden()
-    expect($('div.status-box-open')).toBeVisible()
-    expect($('div.flash-alert')).toBeVisible()
-    expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
-
-  it 'reopens an issue', ->
-    spyOn(jQuery, 'ajax').and.callFake (req) ->
-      expect(req.type).toBe('PUT')
-      expect(req.url).toBe('http://gitlab.com/issues/6/reopen')
-      req.success id: 34
-
-    $btnClose = $('a.btn-close')
-    $btnReopen = $('a.btn-reopen')
-    expect($btnReopen.text()).toBe('Reopen')
-
-    $btnReopen.trigger('click')
-
-    expect($btnReopen).toBeHidden()
-    expect($btnClose).toBeVisible()
-    expect($('div.status-box-open')).toBeVisible()
-    expect($('div.status-box-closed')).toBeHidden()
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2789571607dd066cfa1db7942fcd467decafadc
--- /dev/null
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -0,0 +1,229 @@
+
+/*= require line_highlighter */
+
+(function() {
+  describe('LineHighlighter', function() {
+    var clickLine;
+    fixture.preload('line_highlighter.html');
+    clickLine = function(number, eventData) {
+      var e;
+      if (eventData == null) {
+        eventData = {};
+      }
+      if ($.isEmptyObject(eventData)) {
+        return $("#L" + number).mousedown().click();
+      } else {
+        e = $.Event('mousedown', eventData);
+        return $("#L" + number).trigger(e).click();
+      }
+    };
+    beforeEach(function() {
+      fixture.load('line_highlighter.html');
+      this["class"] = new LineHighlighter();
+      this.css = this["class"].highlightClass;
+      return this.spies = {
+        __setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {})
+      };
+    });
+    describe('behavior', function() {
+      it('highlights one line given in the URL hash', function() {
+        new LineHighlighter('#L13');
+        return expect($('#LC13')).toHaveClass(this.css);
+      });
+      it('highlights a range of lines given in the URL hash', function() {
+        var i, line, results;
+        new LineHighlighter('#L5-25');
+        expect($("." + this.css).length).toBe(21);
+        results = [];
+        for (line = i = 5; i <= 25; line = ++i) {
+          results.push(expect($("#LC" + line)).toHaveClass(this.css));
+        }
+        return results;
+      });
+      it('scrolls to the first highlighted line on initial load', function() {
+        var spy;
+        spy = spyOn($, 'scrollTo');
+        new LineHighlighter('#L5-25');
+        return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything());
+      });
+      it('discards click events', function() {
+        var spy;
+        spy = spyOnEvent('a[data-line-number]', 'click');
+        clickLine(13);
+        return expect(spy).toHaveBeenPrevented();
+      });
+      return it('handles garbage input from the hash', function() {
+        var func;
+        func = function() {
+          return new LineHighlighter('#blob-content-holder');
+        };
+        return expect(func).not.toThrow();
+      });
+    });
+    describe('#clickHandler', function() {
+      it('discards the mousedown event', function() {
+        var spy;
+        spy = spyOnEvent('a[data-line-number]', 'mousedown');
+        clickLine(13);
+        return expect(spy).toHaveBeenPrevented();
+      });
+      it('handles clicking on a child icon element', function() {
+        var spy;
+        spy = spyOn(this["class"], 'setHash').and.callThrough();
+        $('#L13 i').mousedown().click();
+        expect(spy).toHaveBeenCalledWith(13);
+        return expect($('#LC13')).toHaveClass(this.css);
+      });
+      describe('without shiftKey', function() {
+        it('highlights one line when clicked', function() {
+          clickLine(13);
+          return expect($('#LC13')).toHaveClass(this.css);
+        });
+        it('unhighlights previously highlighted lines', function() {
+          clickLine(13);
+          clickLine(20);
+          expect($('#LC13')).not.toHaveClass(this.css);
+          return expect($('#LC20')).toHaveClass(this.css);
+        });
+        return it('sets the hash', function() {
+          var spy;
+          spy = spyOn(this["class"], 'setHash').and.callThrough();
+          clickLine(13);
+          return expect(spy).toHaveBeenCalledWith(13);
+        });
+      });
+      return describe('with shiftKey', function() {
+        it('sets the hash', function() {
+          var spy;
+          spy = spyOn(this["class"], 'setHash').and.callThrough();
+          clickLine(13);
+          clickLine(20, {
+            shiftKey: true
+          });
+          expect(spy).toHaveBeenCalledWith(13);
+          return expect(spy).toHaveBeenCalledWith(13, 20);
+        });
+        describe('without existing highlight', function() {
+          it('highlights the clicked line', function() {
+            clickLine(13, {
+              shiftKey: true
+            });
+            expect($('#LC13')).toHaveClass(this.css);
+            return expect($("." + this.css).length).toBe(1);
+          });
+          return it('sets the hash', function() {
+            var spy;
+            spy = spyOn(this["class"], 'setHash');
+            clickLine(13, {
+              shiftKey: true
+            });
+            return expect(spy).toHaveBeenCalledWith(13);
+          });
+        });
+        describe('with existing single-line highlight', function() {
+          it('uses existing line as last line when target is lesser', function() {
+            var i, line, results;
+            clickLine(20);
+            clickLine(15, {
+              shiftKey: true
+            });
+            expect($("." + this.css).length).toBe(6);
+            results = [];
+            for (line = i = 15; i <= 20; line = ++i) {
+              results.push(expect($("#LC" + line)).toHaveClass(this.css));
+            }
+            return results;
+          });
+          return it('uses existing line as first line when target is greater', function() {
+            var i, line, results;
+            clickLine(5);
+            clickLine(10, {
+              shiftKey: true
+            });
+            expect($("." + this.css).length).toBe(6);
+            results = [];
+            for (line = i = 5; i <= 10; line = ++i) {
+              results.push(expect($("#LC" + line)).toHaveClass(this.css));
+            }
+            return results;
+          });
+        });
+        return describe('with existing multi-line highlight', function() {
+          beforeEach(function() {
+            clickLine(10, {
+              shiftKey: true
+            });
+            return clickLine(13, {
+              shiftKey: true
+            });
+          });
+          it('uses target as first line when it is less than existing first line', function() {
+            var i, line, results;
+            clickLine(5, {
+              shiftKey: true
+            });
+            expect($("." + this.css).length).toBe(6);
+            results = [];
+            for (line = i = 5; i <= 10; line = ++i) {
+              results.push(expect($("#LC" + line)).toHaveClass(this.css));
+            }
+            return results;
+          });
+          return it('uses target as last line when it is greater than existing first line', function() {
+            var i, line, results;
+            clickLine(15, {
+              shiftKey: true
+            });
+            expect($("." + this.css).length).toBe(6);
+            results = [];
+            for (line = i = 10; i <= 15; line = ++i) {
+              results.push(expect($("#LC" + line)).toHaveClass(this.css));
+            }
+            return results;
+          });
+        });
+      });
+    });
+    describe('#hashToRange', function() {
+      beforeEach(function() {
+        return this.subject = this["class"].hashToRange;
+      });
+      it('extracts a single line number from the hash', function() {
+        return expect(this.subject('#L5')).toEqual([5, null]);
+      });
+      it('extracts a range of line numbers from the hash', function() {
+        return expect(this.subject('#L5-15')).toEqual([5, 15]);
+      });
+      return it('returns [null, null] when the hash is not a line number', function() {
+        return expect(this.subject('#foo')).toEqual([null, null]);
+      });
+    });
+    describe('#highlightLine', function() {
+      beforeEach(function() {
+        return this.subject = this["class"].highlightLine;
+      });
+      it('highlights the specified line', function() {
+        this.subject(13);
+        return expect($('#LC13')).toHaveClass(this.css);
+      });
+      return it('accepts a String-based number', function() {
+        this.subject('13');
+        return expect($('#LC13')).toHaveClass(this.css);
+      });
+    });
+    return describe('#setHash', function() {
+      beforeEach(function() {
+        return this.subject = this["class"].setHash;
+      });
+      it('sets the location hash for a single line', function() {
+        this.subject(5);
+        return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5');
+      });
+      return it('sets the location hash for a range', function() {
+        this.subject(5, 15);
+        return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
deleted file mode 100644
index a073f21e7bcbe563f53bab788be747dbf9fea754..0000000000000000000000000000000000000000
--- a/spec/javascripts/line_highlighter_spec.js.coffee
+++ /dev/null
@@ -1,158 +0,0 @@
-#= require line_highlighter
-
-describe 'LineHighlighter', ->
-  fixture.preload('line_highlighter.html')
-
-  clickLine = (number, eventData = {}) ->
-    if $.isEmptyObject(eventData)
-      $("#L#{number}").mousedown().click()
-    else
-      e = $.Event 'mousedown', eventData
-      $("#L#{number}").trigger(e).click()
-
-  beforeEach ->
-    fixture.load('line_highlighter.html')
-    @class = new LineHighlighter()
-    @css   = @class.highlightClass
-    @spies = {
-      __setLocationHash__: spyOn(@class, '__setLocationHash__').and.callFake ->
-    }
-
-  describe 'behavior', ->
-    it 'highlights one line given in the URL hash', ->
-      new LineHighlighter('#L13')
-      expect($('#LC13')).toHaveClass(@css)
-
-    it 'highlights a range of lines given in the URL hash', ->
-      new LineHighlighter('#L5-25')
-      expect($(".#{@css}").length).toBe(21)
-      expect($("#LC#{line}")).toHaveClass(@css) for line in [5..25]
-
-    it 'scrolls to the first highlighted line on initial load', ->
-      spy = spyOn($, 'scrollTo')
-      new LineHighlighter('#L5-25')
-      expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything())
-
-    it 'discards click events', ->
-      spy = spyOnEvent('a[data-line-number]', 'click')
-      clickLine(13)
-      expect(spy).toHaveBeenPrevented()
-
-    it 'handles garbage input from the hash', ->
-      func = -> new LineHighlighter('#blob-content-holder')
-      expect(func).not.toThrow()
-
-  describe '#clickHandler', ->
-    it 'discards the mousedown event', ->
-      spy = spyOnEvent('a[data-line-number]', 'mousedown')
-      clickLine(13)
-      expect(spy).toHaveBeenPrevented()
-
-    it 'handles clicking on a child icon element', ->
-      spy = spyOn(@class, 'setHash').and.callThrough()
-
-      $('#L13 i').mousedown().click()
-
-      expect(spy).toHaveBeenCalledWith(13)
-      expect($('#LC13')).toHaveClass(@css)
-
-    describe 'without shiftKey', ->
-      it 'highlights one line when clicked', ->
-        clickLine(13)
-        expect($('#LC13')).toHaveClass(@css)
-
-      it 'unhighlights previously highlighted lines', ->
-        clickLine(13)
-        clickLine(20)
-
-        expect($('#LC13')).not.toHaveClass(@css)
-        expect($('#LC20')).toHaveClass(@css)
-
-      it 'sets the hash', ->
-        spy = spyOn(@class, 'setHash').and.callThrough()
-        clickLine(13)
-        expect(spy).toHaveBeenCalledWith(13)
-
-    describe 'with shiftKey', ->
-      it 'sets the hash', ->
-        spy = spyOn(@class, 'setHash').and.callThrough()
-        clickLine(13)
-        clickLine(20, shiftKey: true)
-        expect(spy).toHaveBeenCalledWith(13)
-        expect(spy).toHaveBeenCalledWith(13, 20)
-
-      describe 'without existing highlight', ->
-        it 'highlights the clicked line', ->
-          clickLine(13, shiftKey: true)
-          expect($('#LC13')).toHaveClass(@css)
-          expect($(".#{@css}").length).toBe(1)
-
-        it 'sets the hash', ->
-          spy = spyOn(@class, 'setHash')
-          clickLine(13, shiftKey: true)
-          expect(spy).toHaveBeenCalledWith(13)
-
-      describe 'with existing single-line highlight', ->
-        it 'uses existing line as last line when target is lesser', ->
-          clickLine(20)
-          clickLine(15, shiftKey: true)
-          expect($(".#{@css}").length).toBe(6)
-          expect($("#LC#{line}")).toHaveClass(@css) for line in [15..20]
-
-        it 'uses existing line as first line when target is greater', ->
-          clickLine(5)
-          clickLine(10, shiftKey: true)
-          expect($(".#{@css}").length).toBe(6)
-          expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
-
-      describe 'with existing multi-line highlight', ->
-        beforeEach ->
-          clickLine(10, shiftKey: true)
-          clickLine(13, shiftKey: true)
-
-        it 'uses target as first line when it is less than existing first line', ->
-          clickLine(5, shiftKey: true)
-          expect($(".#{@css}").length).toBe(6)
-          expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
-
-        it 'uses target as last line when it is greater than existing first line', ->
-          clickLine(15, shiftKey: true)
-          expect($(".#{@css}").length).toBe(6)
-          expect($("#LC#{line}")).toHaveClass(@css) for line in [10..15]
-
-  describe '#hashToRange', ->
-    beforeEach ->
-      @subject = @class.hashToRange
-
-    it 'extracts a single line number from the hash', ->
-      expect(@subject('#L5')).toEqual([5, null])
-
-    it 'extracts a range of line numbers from the hash', ->
-      expect(@subject('#L5-15')).toEqual([5, 15])
-
-    it 'returns [null, null] when the hash is not a line number', ->
-      expect(@subject('#foo')).toEqual([null, null])
-
-  describe '#highlightLine', ->
-    beforeEach ->
-      @subject = @class.highlightLine
-
-    it 'highlights the specified line', ->
-      @subject(13)
-      expect($('#LC13')).toHaveClass(@css)
-
-    it 'accepts a String-based number', ->
-      @subject('13')
-      expect($('#LC13')).toHaveClass(@css)
-
-  describe '#setHash', ->
-    beforeEach ->
-      @subject = @class.setHash
-
-    it 'sets the location hash for a single line', ->
-      @subject(5)
-      expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5')
-
-    it 'sets the location hash for a range', ->
-      @subject(5, 15)
-      expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15')
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..61830d267a9c15ab3157dba3ee95fabd737a1fc1
--- /dev/null
+++ b/spec/javascripts/merge_request_spec.js
@@ -0,0 +1,28 @@
+
+/*= require merge_request */
+
+(function() {
+  describe('MergeRequest', function() {
+    return describe('task lists', function() {
+      fixture.preload('merge_requests_show.html');
+      beforeEach(function() {
+        fixture.load('merge_requests_show.html');
+        return this.merge = new MergeRequest();
+      });
+      it('modifies the Markdown field', function() {
+        spyOn(jQuery, 'ajax').and.stub();
+        $('input[type=checkbox]').attr('checked', true).trigger('change');
+        return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+      });
+      return it('submits an ajax request on tasklist:changed', function() {
+        spyOn(jQuery, 'ajax').and.callFake(function(req) {
+          expect(req.type).toBe('PATCH');
+          expect(req.url).toBe('/foo');
+          return expect(req.data.merge_request.description).not.toBe(null);
+        });
+        return $('.js-task-list-field').trigger('tasklist:changed');
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
deleted file mode 100644
index 3cb67d51c85fa893df70ed2fb71ce7d542f87f29..0000000000000000000000000000000000000000
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ /dev/null
@@ -1,23 +0,0 @@
-#= require merge_request
-
-describe 'MergeRequest', ->
-  describe 'task lists', ->
-    fixture.preload('merge_requests_show.html')
-
-    beforeEach ->
-      fixture.load('merge_requests_show.html')
-      @merge = new MergeRequest()
-
-    it 'modifies the Markdown field', ->
-      spyOn(jQuery, 'ajax').and.stub()
-
-      $('input[type=checkbox]').attr('checked', true).trigger('change')
-      expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
-    it 'submits an ajax request on tasklist:changed', ->
-      spyOn(jQuery, 'ajax').and.callFake (req) ->
-        expect(req.type).toBe('PATCH')
-        expect(req.url).toBe('/foo')
-        expect(req.data.merge_request.description).not.toBe(null)
-
-      $('.js-task-list-field').trigger('tasklist:changed')
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..395032a74167d0415de157814e0234321c79cdf8
--- /dev/null
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -0,0 +1,106 @@
+
+/*= require merge_request_tabs */
+
+(function() {
+  describe('MergeRequestTabs', function() {
+    var stubLocation;
+    stubLocation = function(stubs) {
+      var defaults;
+      defaults = {
+        pathname: '',
+        search: '',
+        hash: ''
+      };
+      return $.extend(defaults, stubs);
+    };
+    fixture.preload('merge_request_tabs.html');
+    beforeEach(function() {
+      this["class"] = new MergeRequestTabs();
+      return this.spies = {
+        ajax: spyOn($, 'ajax').and.callFake(function() {}),
+        history: spyOn(history, 'replaceState').and.callFake(function() {})
+      };
+    });
+    describe('#activateTab', function() {
+      beforeEach(function() {
+        fixture.load('merge_request_tabs.html');
+        return this.subject = this["class"].activateTab;
+      });
+      it('shows the first tab when action is show', function() {
+        this.subject('show');
+        return expect($('#notes')).toHaveClass('active');
+      });
+      it('shows the notes tab when action is notes', function() {
+        this.subject('notes');
+        return expect($('#notes')).toHaveClass('active');
+      });
+      it('shows the commits tab when action is commits', function() {
+        this.subject('commits');
+        return expect($('#commits')).toHaveClass('active');
+      });
+      return it('shows the diffs tab when action is diffs', function() {
+        this.subject('diffs');
+        return expect($('#diffs')).toHaveClass('active');
+      });
+    });
+    return describe('#setCurrentAction', function() {
+      beforeEach(function() {
+        return this.subject = this["class"].setCurrentAction;
+      });
+      it('changes from commits', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1/commits'
+        });
+        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        return expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
+      });
+      it('changes from diffs', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1/diffs'
+        });
+        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+      });
+      it('changes from diffs.html', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1/diffs.html'
+        });
+        expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
+        return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+      });
+      it('changes from notes', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1'
+        });
+        expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
+        return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+      });
+      it('includes search parameters and hash string', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1/diffs',
+          search: '?view=parallel',
+          hash: '#L15-35'
+        });
+        return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
+      });
+      it('replaces the current history state', function() {
+        var new_state;
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1'
+        });
+        new_state = this.subject('commits');
+        return expect(this.spies.history).toHaveBeenCalledWith({
+          turbolinks: true,
+          url: new_state
+        }, document.title, new_state);
+      });
+      return it('treats "show" like "notes"', function() {
+        this["class"]._location = stubLocation({
+          pathname: '/foo/bar/merge_requests/1/commits'
+        });
+        return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/merge_request_tabs_spec.js.coffee b/spec/javascripts/merge_request_tabs_spec.js.coffee
deleted file mode 100644
index a0cfba455eaf40fbb8f58542367b085a942f80ed..0000000000000000000000000000000000000000
--- a/spec/javascripts/merge_request_tabs_spec.js.coffee
+++ /dev/null
@@ -1,88 +0,0 @@
-#= require merge_request_tabs
-
-describe 'MergeRequestTabs', ->
-  stubLocation = (stubs) ->
-    defaults = {pathname: '', search: '', hash: ''}
-    $.extend(defaults, stubs)
-
-  fixture.preload('merge_request_tabs.html')
-
-  beforeEach ->
-    @class = new MergeRequestTabs()
-    @spies = {
-      ajax:    spyOn($, 'ajax').and.callFake ->
-      history: spyOn(history, 'replaceState').and.callFake ->
-    }
-
-  describe '#activateTab', ->
-    beforeEach ->
-      fixture.load('merge_request_tabs.html')
-      @subject = @class.activateTab
-
-    it 'shows the first tab when action is show', ->
-      @subject('show')
-      expect($('#notes')).toHaveClass('active')
-
-    it 'shows the notes tab when action is notes', ->
-      @subject('notes')
-      expect($('#notes')).toHaveClass('active')
-
-    it 'shows the commits tab when action is commits', ->
-      @subject('commits')
-      expect($('#commits')).toHaveClass('active')
-
-    it 'shows the diffs tab when action is diffs', ->
-      @subject('diffs')
-      expect($('#diffs')).toHaveClass('active')
-
-  describe '#setCurrentAction', ->
-    beforeEach ->
-      @subject = @class.setCurrentAction
-
-    it 'changes from commits', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
-
-      expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
-      expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
-
-    it 'changes from diffs', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs')
-
-      expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
-      expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
-    it 'changes from diffs.html', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs.html')
-
-      expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
-      expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
-    it 'changes from notes', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
-
-      expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
-      expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
-
-    it 'includes search parameters and hash string', ->
-      @class._location = stubLocation({
-        pathname: '/foo/bar/merge_requests/1/diffs'
-        search:   '?view=parallel'
-        hash:     '#L15-35'
-      })
-
-      expect(@subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35')
-
-    it 'replaces the current history state', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
-      new_state = @subject('commits')
-
-      expect(@spies.history).toHaveBeenCalledWith(
-        {turbolinks: true, url: new_state},
-        document.title,
-        new_state
-      )
-
-    it 'treats "show" like "notes"', ->
-      @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
-
-      expect(@subject('show')).toBe('/foo/bar/merge_requests/1')
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..17b32914ec395fe1150c493fa8d619903485fbcd
--- /dev/null
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -0,0 +1,74 @@
+
+/*= require merge_request_widget */
+
+(function() {
+  describe('MergeRequestWidget', function() {
+    beforeEach(function() {
+      window.notifyPermissions = function() {};
+      window.notify = function() {};
+      this.opts = {
+        ci_status_url: "http://sampledomain.local/ci/getstatus",
+        ci_status: "",
+        ci_message: {
+          normal: "Build {{status}} for \"{{title}}\"",
+          preparing: "{{status}} build for \"{{title}}\""
+        },
+        ci_title: {
+          preparing: "{{status}} build",
+          normal: "Build {{status}}"
+        },
+        gitlab_icon: "gitlab_logo.png",
+        builds_path: "http://sampledomain.local/sampleBuildsPath"
+      };
+      this["class"] = new MergeRequestWidget(this.opts);
+      return this.ciStatusData = {
+        "title": "Sample MR title",
+        "sha": "12a34bc5",
+        "status": "success",
+        "coverage": 98
+      };
+    });
+    return describe('getCIStatus', function() {
+      beforeEach(function() {
+        return spyOn(jQuery, 'getJSON').and.callFake((function(_this) {
+          return function(req, cb) {
+            return cb(_this.ciStatusData);
+          };
+        })(this));
+      });
+      it('should call showCIStatus even if a notification should not be displayed', function() {
+        var spy;
+        spy = spyOn(this["class"], 'showCIStatus').and.stub();
+        this["class"].getCIStatus(false);
+        return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
+      });
+      it('should call showCIStatus when a notification should be displayed', function() {
+        var spy;
+        spy = spyOn(this["class"], 'showCIStatus').and.stub();
+        this["class"].getCIStatus(true);
+        return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
+      });
+      it('should call showCICoverage when the coverage rate is set', function() {
+        var spy;
+        spy = spyOn(this["class"], 'showCICoverage').and.stub();
+        this["class"].getCIStatus(false);
+        return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage);
+      });
+      it('should not call showCICoverage when the coverage rate is not set', function() {
+        var spy;
+        this.ciStatusData.coverage = null;
+        spy = spyOn(this["class"], 'showCICoverage').and.stub();
+        this["class"].getCIStatus(false);
+        return expect(spy).not.toHaveBeenCalled();
+      });
+      return it('should not display a notification on the first check after the widget has been created', function() {
+        var spy;
+        spy = spyOn(window, 'notify');
+        this["class"] = new MergeRequestWidget(this.opts);
+        this["class"].getCIStatus(true);
+        return expect(spy).not.toHaveBeenCalled();
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee
deleted file mode 100644
index 92b7eeb1116d0b11acd3f16ff0a94808fd8a3501..0000000000000000000000000000000000000000
--- a/spec/javascripts/merge_request_widget_spec.js.coffee
+++ /dev/null
@@ -1,55 +0,0 @@
-#= require merge_request_widget
-
-describe 'MergeRequestWidget', ->
-
-  beforeEach ->
-    window.notifyPermissions = () ->
-    window.notify = () ->
-    @opts = {
-      ci_status_url:"http://sampledomain.local/ci/getstatus",
-      ci_status:"",
-      ci_message: {
-        normal: "Build {{status}} for \"{{title}}\"",
-        preparing: "{{status}} build for \"{{title}}\""
-      },
-      ci_title: {
-        preparing: "{{status}} build",
-        normal: "Build {{status}}"
-      },
-      gitlab_icon:"gitlab_logo.png",
-      builds_path:"http://sampledomain.local/sampleBuildsPath"
-    }
-    @class = new MergeRequestWidget(@opts)
-    @ciStatusData = {"title":"Sample MR title","sha":"12a34bc5","status":"success","coverage":98}
-
-  describe 'getCIStatus', ->
-    beforeEach ->
-      spyOn(jQuery, 'getJSON').and.callFake (req, cb) =>
-        cb(@ciStatusData)
-
-    it 'should call showCIStatus even if a notification should not be displayed', ->
-      spy = spyOn(@class, 'showCIStatus').and.stub()
-      @class.getCIStatus(false)
-      expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
-
-    it 'should call showCIStatus when a notification should be displayed', ->
-      spy = spyOn(@class, 'showCIStatus').and.stub()
-      @class.getCIStatus(true)
-      expect(spy).toHaveBeenCalledWith(@ciStatusData.status)
-
-    it 'should call showCICoverage when the coverage rate is set', ->
-      spy = spyOn(@class, 'showCICoverage').and.stub()
-      @class.getCIStatus(false)
-      expect(spy).toHaveBeenCalledWith(@ciStatusData.coverage)
-
-    it 'should not call showCICoverage when the coverage rate is not set', ->
-      @ciStatusData.coverage = null
-      spy = spyOn(@class, 'showCICoverage').and.stub()
-      @class.getCIStatus(false)
-      expect(spy).not.toHaveBeenCalled()
-
-    it 'should not display a notification on the first check after the widget has been created', ->
-      spy = spyOn(window, 'notify')
-      @class = new MergeRequestWidget(@opts)
-      @class.getCIStatus(true)
-      expect(spy).not.toHaveBeenCalled()
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..25d3f5b6c04d73fa0bc4499d1e166b0e1173784f
--- /dev/null
+++ b/spec/javascripts/new_branch_spec.js
@@ -0,0 +1,170 @@
+
+/*= require jquery-ui/autocomplete */
+
+
+/*= require new_branch_form */
+
+(function() {
+  describe('Branch', function() {
+    return describe('create a new branch', function() {
+      var expectToHaveError, fillNameWith;
+      fixture.preload('new_branch.html');
+      fillNameWith = function(value) {
+        return $('.js-branch-name').val(value).trigger('blur');
+      };
+      expectToHaveError = function(error) {
+        return expect($('.js-branch-name-error span').text()).toEqual(error);
+      };
+      beforeEach(function() {
+        fixture.load('new_branch.html');
+        $('form').on('submit', function(e) {
+          return e.preventDefault();
+        });
+        return this.form = new NewBranchForm($('.js-create-branch-form'), []);
+      });
+      it("can't start with a dot", function() {
+        fillNameWith('.foo');
+        return expectToHaveError("can't start with '.'");
+      });
+      it("can't start with a slash", function() {
+        fillNameWith('/foo');
+        return expectToHaveError("can't start with '/'");
+      });
+      it("can't have two consecutive dots", function() {
+        fillNameWith('foo..bar');
+        return expectToHaveError("can't contain '..'");
+      });
+      it("can't have spaces anywhere", function() {
+        fillNameWith(' foo');
+        expectToHaveError("can't contain spaces");
+        fillNameWith('foo bar');
+        expectToHaveError("can't contain spaces");
+        fillNameWith('foo ');
+        return expectToHaveError("can't contain spaces");
+      });
+      it("can't have ~ anywhere", function() {
+        fillNameWith('~foo');
+        expectToHaveError("can't contain '~'");
+        fillNameWith('foo~bar');
+        expectToHaveError("can't contain '~'");
+        fillNameWith('foo~');
+        return expectToHaveError("can't contain '~'");
+      });
+      it("can't have tilde anwhere", function() {
+        fillNameWith('~foo');
+        expectToHaveError("can't contain '~'");
+        fillNameWith('foo~bar');
+        expectToHaveError("can't contain '~'");
+        fillNameWith('foo~');
+        return expectToHaveError("can't contain '~'");
+      });
+      it("can't have caret anywhere", function() {
+        fillNameWith('^foo');
+        expectToHaveError("can't contain '^'");
+        fillNameWith('foo^bar');
+        expectToHaveError("can't contain '^'");
+        fillNameWith('foo^');
+        return expectToHaveError("can't contain '^'");
+      });
+      it("can't have : anywhere", function() {
+        fillNameWith(':foo');
+        expectToHaveError("can't contain ':'");
+        fillNameWith('foo:bar');
+        expectToHaveError("can't contain ':'");
+        fillNameWith(':foo');
+        return expectToHaveError("can't contain ':'");
+      });
+      it("can't have question mark anywhere", function() {
+        fillNameWith('?foo');
+        expectToHaveError("can't contain '?'");
+        fillNameWith('foo?bar');
+        expectToHaveError("can't contain '?'");
+        fillNameWith('foo?');
+        return expectToHaveError("can't contain '?'");
+      });
+      it("can't have asterisk anywhere", function() {
+        fillNameWith('*foo');
+        expectToHaveError("can't contain '*'");
+        fillNameWith('foo*bar');
+        expectToHaveError("can't contain '*'");
+        fillNameWith('foo*');
+        return expectToHaveError("can't contain '*'");
+      });
+      it("can't have open bracket anywhere", function() {
+        fillNameWith('[foo');
+        expectToHaveError("can't contain '['");
+        fillNameWith('foo[bar');
+        expectToHaveError("can't contain '['");
+        fillNameWith('foo[');
+        return expectToHaveError("can't contain '['");
+      });
+      it("can't have a backslash anywhere", function() {
+        fillNameWith('\\foo');
+        expectToHaveError("can't contain '\\'");
+        fillNameWith('foo\\bar');
+        expectToHaveError("can't contain '\\'");
+        fillNameWith('foo\\');
+        return expectToHaveError("can't contain '\\'");
+      });
+      it("can't contain a sequence @{ anywhere", function() {
+        fillNameWith('@{foo');
+        expectToHaveError("can't contain '@{'");
+        fillNameWith('foo@{bar');
+        expectToHaveError("can't contain '@{'");
+        fillNameWith('foo@{');
+        return expectToHaveError("can't contain '@{'");
+      });
+      it("can't have consecutive slashes", function() {
+        fillNameWith('foo//bar');
+        return expectToHaveError("can't contain consecutive slashes");
+      });
+      it("can't end with a slash", function() {
+        fillNameWith('foo/');
+        return expectToHaveError("can't end in '/'");
+      });
+      it("can't end with a dot", function() {
+        fillNameWith('foo.');
+        return expectToHaveError("can't end in '.'");
+      });
+      it("can't end with .lock", function() {
+        fillNameWith('foo.lock');
+        return expectToHaveError("can't end in '.lock'");
+      });
+      it("can't be the single character @", function() {
+        fillNameWith('@');
+        return expectToHaveError("can't be '@'");
+      });
+      it("concatenates all error messages", function() {
+        fillNameWith('/foo bar?~.');
+        return expectToHaveError("can't start with '/', can't contain spaces, '?', '~', can't end in '.'");
+      });
+      it("doesn't duplicate error messages", function() {
+        fillNameWith('?foo?bar?zoo?');
+        return expectToHaveError("can't contain '?'");
+      });
+      it("removes the error message when is a valid name", function() {
+        fillNameWith('foo?bar');
+        expect($('.js-branch-name-error span').length).toEqual(1);
+        fillNameWith('foobar');
+        return expect($('.js-branch-name-error span').length).toEqual(0);
+      });
+      it("can have dashes anywhere", function() {
+        fillNameWith('-foo-bar-zoo-');
+        return expect($('.js-branch-name-error span').length).toEqual(0);
+      });
+      it("can have underscores anywhere", function() {
+        fillNameWith('_foo_bar_zoo_');
+        return expect($('.js-branch-name-error span').length).toEqual(0);
+      });
+      it("can have numbers anywhere", function() {
+        fillNameWith('1foo2bar3zoo4');
+        return expect($('.js-branch-name-error span').length).toEqual(0);
+      });
+      return it("can be only letters", function() {
+        fillNameWith('foo');
+        return expect($('.js-branch-name-error span').length).toEqual(0);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
deleted file mode 100644
index ce7737938174c3b3ffb3ed6fb60550ad0759e444..0000000000000000000000000000000000000000
--- a/spec/javascripts/new_branch_spec.js.coffee
+++ /dev/null
@@ -1,160 +0,0 @@
-#= require jquery-ui/autocomplete
-#= require new_branch_form
-
-describe 'Branch', ->
-  describe 'create a new branch', ->
-    fixture.preload('new_branch.html')
-
-    fillNameWith = (value) ->
-      $('.js-branch-name').val(value).trigger('blur')
-
-    expectToHaveError = (error) ->
-      expect($('.js-branch-name-error span').text()).toEqual(error)
-
-    beforeEach ->
-      fixture.load('new_branch.html')
-      $('form').on 'submit', (e) -> e.preventDefault()
-
-      @form = new NewBranchForm($('.js-create-branch-form'), [])
-
-    it "can't start with a dot", ->
-      fillNameWith '.foo'
-      expectToHaveError "can't start with '.'"
-
-    it "can't start with a slash", ->
-      fillNameWith '/foo'
-      expectToHaveError "can't start with '/'"
-
-    it "can't have two consecutive dots", ->
-      fillNameWith 'foo..bar'
-      expectToHaveError "can't contain '..'"
-
-    it "can't have spaces anywhere", ->
-      fillNameWith ' foo'
-      expectToHaveError "can't contain spaces"
-      fillNameWith 'foo bar'
-      expectToHaveError "can't contain spaces"
-      fillNameWith 'foo '
-      expectToHaveError "can't contain spaces"
-
-    it "can't have ~ anywhere", ->
-      fillNameWith '~foo'
-      expectToHaveError "can't contain '~'"
-      fillNameWith 'foo~bar'
-      expectToHaveError "can't contain '~'"
-      fillNameWith 'foo~'
-      expectToHaveError "can't contain '~'"
-
-    it "can't have tilde anwhere", ->
-      fillNameWith '~foo'
-      expectToHaveError "can't contain '~'"
-      fillNameWith 'foo~bar'
-      expectToHaveError "can't contain '~'"
-      fillNameWith 'foo~'
-      expectToHaveError "can't contain '~'"
-
-    it "can't have caret anywhere", ->
-      fillNameWith '^foo'
-      expectToHaveError "can't contain '^'"
-      fillNameWith 'foo^bar'
-      expectToHaveError "can't contain '^'"
-      fillNameWith 'foo^'
-      expectToHaveError "can't contain '^'"
-
-    it "can't have : anywhere", ->
-      fillNameWith ':foo'
-      expectToHaveError "can't contain ':'"
-      fillNameWith 'foo:bar'
-      expectToHaveError "can't contain ':'"
-      fillNameWith ':foo'
-      expectToHaveError "can't contain ':'"
-
-    it "can't have question mark anywhere", ->
-      fillNameWith '?foo'
-      expectToHaveError "can't contain '?'"
-      fillNameWith 'foo?bar'
-      expectToHaveError "can't contain '?'"
-      fillNameWith 'foo?'
-      expectToHaveError "can't contain '?'"
-
-    it "can't have asterisk anywhere", ->
-      fillNameWith '*foo'
-      expectToHaveError "can't contain '*'"
-      fillNameWith 'foo*bar'
-      expectToHaveError "can't contain '*'"
-      fillNameWith 'foo*'
-      expectToHaveError "can't contain '*'"
-
-    it "can't have open bracket anywhere", ->
-      fillNameWith '[foo'
-      expectToHaveError "can't contain '['"
-      fillNameWith 'foo[bar'
-      expectToHaveError "can't contain '['"
-      fillNameWith 'foo['
-      expectToHaveError "can't contain '['"
-
-    it "can't have a backslash anywhere", ->
-      fillNameWith '\\foo'
-      expectToHaveError "can't contain '\\'"
-      fillNameWith 'foo\\bar'
-      expectToHaveError "can't contain '\\'"
-      fillNameWith 'foo\\'
-      expectToHaveError "can't contain '\\'"
-
-    it "can't contain a sequence @{ anywhere", ->
-      fillNameWith '@{foo'
-      expectToHaveError "can't contain '@{'"
-      fillNameWith 'foo@{bar'
-      expectToHaveError "can't contain '@{'"
-      fillNameWith 'foo@{'
-      expectToHaveError "can't contain '@{'"
-
-    it "can't have consecutive slashes", ->
-      fillNameWith 'foo//bar'
-      expectToHaveError "can't contain consecutive slashes"
-
-    it "can't end with a slash", ->
-      fillNameWith 'foo/'
-      expectToHaveError "can't end in '/'"
-
-    it "can't end with a dot", ->
-      fillNameWith 'foo.'
-      expectToHaveError "can't end in '.'"
-
-    it "can't end with .lock", ->
-      fillNameWith 'foo.lock'
-      expectToHaveError "can't end in '.lock'"
-
-    it "can't be the single character @", ->
-      fillNameWith '@'
-      expectToHaveError "can't be '@'"
-
-    it "concatenates all error messages", ->
-      fillNameWith '/foo bar?~.'
-      expectToHaveError "can't start with '/', can't contain spaces, '?', '~', can't end in '.'"
-
-    it "doesn't duplicate error messages", ->
-      fillNameWith '?foo?bar?zoo?'
-      expectToHaveError "can't contain '?'"
-
-    it "removes the error message when is a valid name", ->
-      fillNameWith 'foo?bar'
-      expect($('.js-branch-name-error span').length).toEqual(1)
-      fillNameWith 'foobar'
-      expect($('.js-branch-name-error span').length).toEqual(0)
-
-    it "can have dashes anywhere", ->
-      fillNameWith '-foo-bar-zoo-'
-      expect($('.js-branch-name-error span').length).toEqual(0)
-
-    it "can have underscores anywhere", ->
-      fillNameWith '_foo_bar_zoo_'
-      expect($('.js-branch-name-error span').length).toEqual(0)
-
-    it "can have numbers anywhere", ->
-      fillNameWith '1foo2bar3zoo4'
-      expect($('.js-branch-name-error span').length).toEqual(0)
-
-    it "can be only letters", ->
-      fillNameWith 'foo'
-      expect($('.js-branch-name-error span').length).toEqual(0)
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..14dc6bfdfdeb865eedb1fb2ebbea00681b5387ef
--- /dev/null
+++ b/spec/javascripts/notes_spec.js
@@ -0,0 +1,41 @@
+
+/*= require notes */
+
+
+/*= require gl_form */
+
+(function() {
+  window.gon || (window.gon = {});
+
+  window.disableButtonIfEmptyField = function() {
+    return null;
+  };
+
+  describe('Notes', function() {
+    return describe('task lists', function() {
+      fixture.preload('issue_note.html');
+      beforeEach(function() {
+        fixture.load('issue_note.html');
+        $('form').on('submit', function(e) {
+          return e.preventDefault();
+        });
+        return this.notes = new Notes();
+      });
+      it('modifies the Markdown field', function() {
+        $('input[type=checkbox]').attr('checked', true).trigger('change');
+        return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
+      });
+      return it('submits the form on tasklist:changed', function() {
+        var submitted;
+        submitted = false;
+        $('form').on('submit', function(e) {
+          submitted = true;
+          return e.preventDefault();
+        });
+        $('.js-task-list-field').trigger('tasklist:changed');
+        return expect(submitted).toBe(true);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
deleted file mode 100644
index 3a3c8d63e82f7106b25bc4c3687aa02f30561b11..0000000000000000000000000000000000000000
--- a/spec/javascripts/notes_spec.js.coffee
+++ /dev/null
@@ -1,26 +0,0 @@
-#= require notes
-#= require gl_form
-
-window.gon or= {}
-window.disableButtonIfEmptyField = -> null
-
-describe 'Notes', ->
-  describe 'task lists', ->
-    fixture.preload('issue_note.html')
-
-    beforeEach ->
-      fixture.load('issue_note.html')
-      $('form').on 'submit', (e) -> e.preventDefault()
-
-      @notes = new Notes()
-
-    it 'modifies the Markdown field', ->
-      $('input[type=checkbox]').attr('checked', true).trigger('change')
-      expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
-
-    it 'submits the form on tasklist:changed', ->
-      submitted = false
-      $('form').on 'submit', (e) -> submitted = true; e.preventDefault()
-
-      $('.js-task-list-field').trigger('tasklist:changed')
-      expect(submitted).toBe(true)
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..ffe49828492f2f649e60f67544e54631f7217e64
--- /dev/null
+++ b/spec/javascripts/project_title_spec.js
@@ -0,0 +1,60 @@
+
+/*= require bootstrap */
+
+
+/*= require select2 */
+
+
+/*= require lib/utils/type_utility */
+
+
+/*= require gl_dropdown */
+
+
+/*= require api */
+
+
+/*= require project_select */
+
+
+/*= require project */
+
+(function() {
+  window.gon || (window.gon = {});
+
+  window.gon.api_version = 'v3';
+
+  describe('Project Title', function() {
+    fixture.preload('project_title.html');
+    fixture.preload('projects.json');
+    beforeEach(function() {
+      fixture.load('project_title.html');
+      return this.project = new Project();
+    });
+    return describe('project list', function() {
+      beforeEach((function(_this) {
+        return function() {
+          _this.projects_data = fixture.load('projects.json')[0];
+          return spyOn(jQuery, 'ajax').and.callFake(function(req) {
+            var d;
+            expect(req.url).toBe('/api/v3/projects.json?simple=true');
+            d = $.Deferred();
+            d.resolve(_this.projects_data);
+            return d.promise();
+          });
+        };
+      })(this));
+      it('to show on toggle click', (function(_this) {
+        return function() {
+          $('.js-projects-dropdown-toggle').click();
+          return expect($('.header-content').hasClass('open')).toBe(true);
+        };
+      })(this));
+      return it('hide dropdown', function() {
+        $(".dropdown-menu-close-icon").click();
+        return expect($('.header-content').hasClass('open')).toBe(false);
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
deleted file mode 100644
index 0244119fa0e794068055ca08804db9ff6e9cabb7..0000000000000000000000000000000000000000
--- a/spec/javascripts/project_title_spec.js.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-#= require bootstrap
-#= require select2
-#= require lib/utils/type_utility
-#= require gl_dropdown
-#= require api
-#= require project_select
-#= require project
-
-window.gon or= {}
-window.gon.api_version = 'v3'
-
-describe 'Project Title', ->
-  fixture.preload('project_title.html')
-  fixture.preload('projects.json')
-
-  beforeEach ->
-    fixture.load('project_title.html')
-    @project = new Project()
-
-  describe 'project list', ->
-    beforeEach =>
-      @projects_data = fixture.load('projects.json')[0]
-
-      spyOn(jQuery, 'ajax').and.callFake (req) =>
-        expect(req.url).toBe('/api/v3/projects.json?simple=true')
-        d = $.Deferred()
-        d.resolve @projects_data
-        d.promise()
-
-    it 'to show on toggle click', =>
-      $('.js-projects-dropdown-toggle').click()
-      expect($('.header-content').hasClass('open')).toBe(true)
-
-    it 'hide dropdown', ->
-      $(".dropdown-menu-close-icon").click()
-
-      expect($('.header-content').hasClass('open')).toBe(false)
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..38b3b2653ecaa4e38e84900af291f61b806de949
--- /dev/null
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -0,0 +1,70 @@
+
+/*= require right_sidebar */
+
+
+/*= require jquery */
+
+
+/*= require jquery.cookie */
+
+(function() {
+  var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
+
+  this.sidebar = null;
+
+  $aside = null;
+
+  $toggle = null;
+
+  $icon = null;
+
+  $page = null;
+
+  $labelsIcon = null;
+
+  assertSidebarState = function(state) {
+    var shouldBeCollapsed, shouldBeExpanded;
+    shouldBeExpanded = state === 'expanded';
+    shouldBeCollapsed = state === 'collapsed';
+    expect($aside.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded);
+    expect($page.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded);
+    expect($icon.hasClass('fa-angle-double-right')).toBe(shouldBeExpanded);
+    expect($aside.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed);
+    expect($page.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed);
+    return expect($icon.hasClass('fa-angle-double-left')).toBe(shouldBeCollapsed);
+  };
+
+  describe('RightSidebar', function() {
+    fixture.preload('right_sidebar.html');
+    beforeEach(function() {
+      fixture.load('right_sidebar.html');
+      this.sidebar = new Sidebar;
+      $aside = $('.right-sidebar');
+      $page = $('.page-with-sidebar');
+      $icon = $aside.find('i');
+      $toggle = $aside.find('.js-sidebar-toggle');
+      return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
+    });
+    it('should expand the sidebar when arrow is clicked', function() {
+      $toggle.click();
+      return assertSidebarState('expanded');
+    });
+    it('should collapse the sidebar when arrow is clicked', function() {
+      $toggle.click();
+      assertSidebarState('expanded');
+      $toggle.click();
+      return assertSidebarState('collapsed');
+    });
+    it('should float over the page and when sidebar icons clicked', function() {
+      $labelsIcon.click();
+      return assertSidebarState('expanded');
+    });
+    return it('should collapse when the icon arrow clicked while it is floating on page', function() {
+      $labelsIcon.click();
+      assertSidebarState('expanded');
+      $toggle.click();
+      return assertSidebarState('collapsed');
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/right_sidebar_spec.js.coffee b/spec/javascripts/right_sidebar_spec.js.coffee
deleted file mode 100644
index 2075cacdb674148299f8c8ef911358b610840d02..0000000000000000000000000000000000000000
--- a/spec/javascripts/right_sidebar_spec.js.coffee
+++ /dev/null
@@ -1,69 +0,0 @@
-#= require right_sidebar
-#= require jquery
-#= require jquery.cookie
-
-@sidebar    = null
-$aside      = null
-$toggle     = null
-$icon       = null
-$page       = null
-$labelsIcon = null
-
-
-assertSidebarState = (state) ->
-
-  shouldBeExpanded  = state is 'expanded'
-  shouldBeCollapsed = state is 'collapsed'
-
-  expect($aside.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
-  expect($page.hasClass('right-sidebar-expanded')).toBe shouldBeExpanded
-  expect($icon.hasClass('fa-angle-double-right')).toBe shouldBeExpanded
-
-  expect($aside.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
-  expect($page.hasClass('right-sidebar-collapsed')).toBe shouldBeCollapsed
-  expect($icon.hasClass('fa-angle-double-left')).toBe shouldBeCollapsed
-
-
-describe 'RightSidebar', ->
-
-  fixture.preload 'right_sidebar.html'
-
-  beforeEach ->
-    fixture.load 'right_sidebar.html'
-
-    @sidebar    = new Sidebar
-    $aside      = $ '.right-sidebar'
-    $page       = $ '.page-with-sidebar'
-    $icon       = $aside.find 'i'
-    $toggle     = $aside.find '.js-sidebar-toggle'
-    $labelsIcon = $aside.find '.sidebar-collapsed-icon'
-
-
-  it 'should expand the sidebar when arrow is clicked', ->
-
-    $toggle.click()
-    assertSidebarState 'expanded'
-
-
-  it 'should collapse the sidebar when arrow is clicked', ->
-
-    $toggle.click()
-    assertSidebarState 'expanded'
-
-    $toggle.click()
-    assertSidebarState 'collapsed'
-
-
-  it 'should float over the page and when sidebar icons clicked', ->
-
-    $labelsIcon.click()
-    assertSidebarState 'expanded'
-
-
-  it 'should collapse when the icon arrow clicked while it is floating on page', ->
-
-    $labelsIcon.click()
-    assertSidebarState 'expanded'
-
-    $toggle.click()
-    assertSidebarState 'collapsed'
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..68d64483d6777ce04521116b00e07785abdf80d1
--- /dev/null
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -0,0 +1,159 @@
+
+/*= require gl_dropdown */
+
+
+/*= require search_autocomplete */
+
+
+/*= require jquery */
+
+
+/*= require lib/utils/common_utils */
+
+
+/*= require lib/utils/type_utility */
+
+
+/*= require fuzzaldrin-plus */
+
+(function() {
+  var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
+
+  widget = null;
+
+  userId = 1;
+
+  window.gon || (window.gon = {});
+
+  window.gon.current_user_id = userId;
+
+  dashboardIssuesPath = '/dashboard/issues';
+
+  dashboardMRsPath = '/dashboard/merge_requests';
+
+  projectIssuesPath = '/gitlab-org/gitlab-ce/issues';
+
+  projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests';
+
+  groupIssuesPath = '/groups/gitlab-org/issues';
+
+  groupMRsPath = '/groups/gitlab-org/merge_requests';
+
+  projectName = 'GitLab Community Edition';
+
+  groupName = 'Gitlab Org';
+
+  addBodyAttributes = function(section) {
+    var $body;
+    if (section == null) {
+      section = 'dashboard';
+    }
+    $body = $('body');
+    $body.removeAttr('data-page');
+    $body.removeAttr('data-project');
+    $body.removeAttr('data-group');
+    switch (section) {
+      case 'dashboard':
+        return $body.data('page', 'root:index');
+      case 'group':
+        $body.data('page', 'groups:show');
+        return $body.data('group', 'gitlab-org');
+      case 'project':
+        $body.data('page', 'projects:show');
+        return $body.data('project', 'gitlab-ce');
+    }
+  };
+
+  mockDashboardOptions = function() {
+    window.gl || (window.gl = {});
+    return window.gl.dashboardOptions = {
+      issuesPath: dashboardIssuesPath,
+      mrPath: dashboardMRsPath
+    };
+  };
+
+  mockProjectOptions = function() {
+    window.gl || (window.gl = {});
+    return window.gl.projectOptions = {
+      'gitlab-ce': {
+        issuesPath: projectIssuesPath,
+        mrPath: projectMRsPath,
+        projectName: projectName
+      }
+    };
+  };
+
+  mockGroupOptions = function() {
+    window.gl || (window.gl = {});
+    return window.gl.groupOptions = {
+      'gitlab-org': {
+        issuesPath: groupIssuesPath,
+        mrPath: groupMRsPath,
+        projectName: groupName
+      }
+    };
+  };
+
+  assertLinks = function(list, issuesPath, mrsPath) {
+    var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
+    issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId;
+    issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId;
+    mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
+    mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
+    a1 = "a[href='" + issuesAssignedToMeLink + "']";
+    a2 = "a[href='" + issuesIHaveCreatedLink + "']";
+    a3 = "a[href='" + mrsAssignedToMeLink + "']";
+    a4 = "a[href='" + mrsIHaveCreatedLink + "']";
+    expect(list.find(a1).length).toBe(1);
+    expect(list.find(a1).text()).toBe(' Issues assigned to me ');
+    expect(list.find(a2).length).toBe(1);
+    expect(list.find(a2).text()).toBe(" Issues I've created ");
+    expect(list.find(a3).length).toBe(1);
+    expect(list.find(a3).text()).toBe(' Merge requests assigned to me ');
+    expect(list.find(a4).length).toBe(1);
+    return expect(list.find(a4).text()).toBe(" Merge requests I've created ");
+  };
+
+  describe('Search autocomplete dropdown', function() {
+    fixture.preload('search_autocomplete.html');
+    beforeEach(function() {
+      fixture.load('search_autocomplete.html');
+      return widget = new SearchAutocomplete;
+    });
+    it('should show Dashboard specific dropdown menu', function() {
+      var list;
+      addBodyAttributes();
+      mockDashboardOptions();
+      widget.searchInput.focus();
+      list = widget.wrap.find('.dropdown-menu').find('ul');
+      return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
+    });
+    it('should show Group specific dropdown menu', function() {
+      var list;
+      addBodyAttributes('group');
+      mockGroupOptions();
+      widget.searchInput.focus();
+      list = widget.wrap.find('.dropdown-menu').find('ul');
+      return assertLinks(list, groupIssuesPath, groupMRsPath);
+    });
+    it('should show Project specific dropdown menu', function() {
+      var list;
+      addBodyAttributes('project');
+      mockProjectOptions();
+      widget.searchInput.focus();
+      list = widget.wrap.find('.dropdown-menu').find('ul');
+      return assertLinks(list, projectIssuesPath, projectMRsPath);
+    });
+    return it('should not show category related menu if there is text in the input', function() {
+      var link, list;
+      addBodyAttributes('project');
+      mockProjectOptions();
+      widget.searchInput.val('help');
+      widget.searchInput.focus();
+      list = widget.wrap.find('.dropdown-menu').find('ul');
+      link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
+      return expect(list.find(link).length).toBe(0);
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/search_autocomplete_spec.js.coffee b/spec/javascripts/search_autocomplete_spec.js.coffee
deleted file mode 100644
index 1c1faca3333334f19a0b01ec35c2faeb95aaf6f7..0000000000000000000000000000000000000000
--- a/spec/javascripts/search_autocomplete_spec.js.coffee
+++ /dev/null
@@ -1,149 +0,0 @@
-#= require gl_dropdown
-#= require search_autocomplete
-#= require jquery
-#= require lib/utils/common_utils
-#= require lib/utils/type_utility
-#= require fuzzaldrin-plus
-
-
-widget       = null
-userId       = 1
-window.gon or= {}
-window.gon.current_user_id = userId
-
-dashboardIssuesPath = '/dashboard/issues'
-dashboardMRsPath    = '/dashboard/merge_requests'
-projectIssuesPath   = '/gitlab-org/gitlab-ce/issues'
-projectMRsPath      = '/gitlab-org/gitlab-ce/merge_requests'
-groupIssuesPath     = '/groups/gitlab-org/issues'
-groupMRsPath        = '/groups/gitlab-org/merge_requests'
-projectName         = 'GitLab Community Edition'
-groupName           = 'Gitlab Org'
-
-
-# Add required attributes to body before starting the test.
-# section would be dashboard|group|project
-addBodyAttributes = (section = 'dashboard') ->
-
-  $body = $ 'body'
-
-  $body.removeAttr 'data-page'
-  $body.removeAttr 'data-project'
-  $body.removeAttr 'data-group'
-
-  switch section
-    when 'dashboard'
-      $body.data 'page', 'root:index'
-    when 'group'
-      $body.data 'page', 'groups:show'
-      $body.data 'group', 'gitlab-org'
-    when 'project'
-      $body.data 'page', 'projects:show'
-      $body.data 'project', 'gitlab-ce'
-
-
-# Mock `gl` object in window for dashboard specific page. App code will need it.
-mockDashboardOptions = ->
-
-  window.gl or= {}
-  window.gl.dashboardOptions =
-    issuesPath: dashboardIssuesPath
-    mrPath    : dashboardMRsPath
-
-
-# Mock `gl` object in window for project specific page. App code will need it.
-mockProjectOptions = ->
-
-  window.gl or= {}
-  window.gl.projectOptions =
-    'gitlab-ce'   :
-      issuesPath  : projectIssuesPath
-      mrPath      : projectMRsPath
-      projectName : projectName
-
-
-mockGroupOptions = ->
-
-  window.gl or= {}
-  window.gl.groupOptions =
-    'gitlab-org'  :
-      issuesPath  : groupIssuesPath
-      mrPath      : groupMRsPath
-      projectName : groupName
-
-
-assertLinks = (list, issuesPath, mrsPath) ->
-
-  issuesAssignedToMeLink = "#{issuesPath}/?assignee_id=#{userId}"
-  issuesIHaveCreatedLink = "#{issuesPath}/?author_id=#{userId}"
-  mrsAssignedToMeLink    = "#{mrsPath}/?assignee_id=#{userId}"
-  mrsIHaveCreatedLink    = "#{mrsPath}/?author_id=#{userId}"
-
-  a1 = "a[href='#{issuesAssignedToMeLink}']"
-  a2 = "a[href='#{issuesIHaveCreatedLink}']"
-  a3 = "a[href='#{mrsAssignedToMeLink}']"
-  a4 = "a[href='#{mrsIHaveCreatedLink}']"
-
-  expect(list.find(a1).length).toBe 1
-  expect(list.find(a1).text()).toBe ' Issues assigned to me '
-
-  expect(list.find(a2).length).toBe 1
-  expect(list.find(a2).text()).toBe " Issues I've created "
-
-  expect(list.find(a3).length).toBe 1
-  expect(list.find(a3).text()).toBe ' Merge requests assigned to me '
-
-  expect(list.find(a4).length).toBe 1
-  expect(list.find(a4).text()).toBe " Merge requests I've created "
-
-
-describe 'Search autocomplete dropdown', ->
-
-  fixture.preload 'search_autocomplete.html'
-
-  beforeEach ->
-
-    fixture.load 'search_autocomplete.html'
-    widget = new SearchAutocomplete
-
-
-  it 'should show Dashboard specific dropdown menu', ->
-
-    addBodyAttributes()
-    mockDashboardOptions()
-    widget.searchInput.focus()
-
-    list = widget.wrap.find('.dropdown-menu').find 'ul'
-    assertLinks list, dashboardIssuesPath, dashboardMRsPath
-
-
-  it 'should show Group specific dropdown menu', ->
-
-    addBodyAttributes 'group'
-    mockGroupOptions()
-    widget.searchInput.focus()
-
-    list = widget.wrap.find('.dropdown-menu').find 'ul'
-    assertLinks list, groupIssuesPath, groupMRsPath
-
-
-  it 'should show Project specific dropdown menu', ->
-
-    addBodyAttributes 'project'
-    mockProjectOptions()
-    widget.searchInput.focus()
-
-    list = widget.wrap.find('.dropdown-menu').find 'ul'
-    assertLinks list, projectIssuesPath, projectMRsPath
-
-
-  it 'should not show category related menu if there is text in the input', ->
-
-    addBodyAttributes 'project'
-    mockProjectOptions()
-    widget.searchInput.val 'help'
-    widget.searchInput.focus()
-
-    list = widget.wrap.find('.dropdown-menu').find 'ul'
-    link = "a[href='#{projectIssuesPath}/?assignee_id=#{userId}']"
-    expect(list.find(link).length).toBe 0
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b6b55fe545c385fc2e71a862dd4bbc677732412
--- /dev/null
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -0,0 +1,74 @@
+
+/*= require shortcuts_issuable */
+
+(function() {
+  describe('ShortcutsIssuable', function() {
+    fixture.preload('issuable.html');
+    beforeEach(function() {
+      fixture.load('issuable.html');
+      return this.shortcut = new ShortcutsIssuable();
+    });
+    return describe('#replyWithSelectedText', function() {
+      var stubSelection;
+      stubSelection = function(text) {
+        return window.getSelection = function() {
+          return text;
+        };
+      };
+      beforeEach(function() {
+        return this.selector = 'form.js-main-target-form textarea#note_note';
+      });
+      describe('with empty selection', function() {
+        return it('does nothing', function() {
+          stubSelection('');
+          this.shortcut.replyWithSelectedText();
+          return expect($(this.selector).val()).toBe('');
+        });
+      });
+      describe('with any selection', function() {
+        beforeEach(function() {
+          return stubSelection('Selected text.');
+        });
+        it('leaves existing input intact', function() {
+          $(this.selector).val('This text was already here.');
+          expect($(this.selector).val()).toBe('This text was already here.');
+          this.shortcut.replyWithSelectedText();
+          return expect($(this.selector).val()).toBe("This text was already here.\n> Selected text.\n\n");
+        });
+        it('triggers `input`', function() {
+          var triggered;
+          triggered = false;
+          $(this.selector).on('input', function() {
+            return triggered = true;
+          });
+          this.shortcut.replyWithSelectedText();
+          return expect(triggered).toBe(true);
+        });
+        return it('triggers `focus`', function() {
+          var focused;
+          focused = false;
+          $(this.selector).on('focus', function() {
+            return focused = true;
+          });
+          this.shortcut.replyWithSelectedText();
+          return expect(focused).toBe(true);
+        });
+      });
+      describe('with a one-line selection', function() {
+        return it('quotes the selection', function() {
+          stubSelection('This text has been selected.');
+          this.shortcut.replyWithSelectedText();
+          return expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
+        });
+      });
+      return describe('with a multi-line selection', function() {
+        return it('quotes the selected lines as a group', function() {
+          stubSelection("Selected line one.\n\nSelected line two.\nSelected line three.\n");
+          this.shortcut.replyWithSelectedText();
+          return expect($(this.selector).val()).toBe("> Selected line one.\n> Selected line two.\n> Selected line three.\n\n");
+        });
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
deleted file mode 100644
index a01ad7140dd961128544a168c03182309a0d289f..0000000000000000000000000000000000000000
--- a/spec/javascripts/shortcuts_issuable_spec.js.coffee
+++ /dev/null
@@ -1,82 +0,0 @@
-#= require shortcuts_issuable
-
-describe 'ShortcutsIssuable', ->
-  fixture.preload('issuable.html')
-
-  beforeEach ->
-    fixture.load('issuable.html')
-    @shortcut = new ShortcutsIssuable()
-
-  describe '#replyWithSelectedText', ->
-    # Stub window.getSelection to return the provided String.
-    stubSelection = (text) ->
-      window.getSelection = -> text
-
-    beforeEach ->
-      @selector = 'form.js-main-target-form textarea#note_note'
-
-    describe 'with empty selection', ->
-      it 'does nothing', ->
-        stubSelection('')
-        @shortcut.replyWithSelectedText()
-        expect($(@selector).val()).toBe('')
-
-    describe 'with any selection', ->
-      beforeEach ->
-        stubSelection('Selected text.')
-
-      it 'leaves existing input intact', ->
-        $(@selector).val('This text was already here.')
-        expect($(@selector).val()).toBe('This text was already here.')
-
-        @shortcut.replyWithSelectedText()
-        expect($(@selector).val()).
-          toBe("This text was already here.\n> Selected text.\n\n")
-
-      it 'triggers `input`', ->
-        triggered = false
-        $(@selector).on 'input', -> triggered = true
-        @shortcut.replyWithSelectedText()
-
-        expect(triggered).toBe(true)
-
-      it 'triggers `focus`', ->
-        focused = false
-        $(@selector).on 'focus', -> focused = true
-        @shortcut.replyWithSelectedText()
-
-        expect(focused).toBe(true)
-
-    describe 'with a one-line selection', ->
-      it 'quotes the selection', ->
-        stubSelection('This text has been selected.')
-
-        @shortcut.replyWithSelectedText()
-
-        expect($(@selector).val()).
-          toBe("> This text has been selected.\n\n")
-
-    describe 'with a multi-line selection', ->
-      it 'quotes the selected lines as a group', ->
-        stubSelection(
-          """
-          Selected line one.
-
-          Selected line two.
-          Selected line three.
-
-          """
-        )
-
-        @shortcut.replyWithSelectedText()
-
-        expect($(@selector).val()).
-          toBe(
-            """
-            > Selected line one.
-            > Selected line two.
-            > Selected line three.
-
-
-            """
-          )
diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
deleted file mode 100644
index 90b02a6aec59b150f5347b28ce1683693a36f08a..0000000000000000000000000000000000000000
--- a/spec/javascripts/spec_helper.coffee
+++ /dev/null
@@ -1,47 +0,0 @@
-# PhantomJS (Teaspoons default driver) doesn't have support for
-# Function.prototype.bind, which has caused confusion.  Use this polyfill to
-# avoid the confusion.
-
-#= require support/bind-poly
-
-# You can require your own javascript files here. By default this will include
-# everything in application, however you may get better load performance if you
-# require the specific files that are being used in the spec that tests them.
-
-#= require jquery
-#= require jquery.turbolinks
-#= require bootstrap
-#= require underscore
-
-# Teaspoon includes some support files, but you can use anything from your own
-# support path too.
-
-# require support/jasmine-jquery-1.7.0
-# require support/jasmine-jquery-2.0.0
-#= require support/jasmine-jquery-2.1.0
-# require support/sinon
-# require support/your-support-file
-
-# Deferring execution
-
-# If you're using CommonJS, RequireJS or some other asynchronous library you can
-# defer execution. Call Teaspoon.execute() after everything has been loaded.
-# Simple example of a timeout:
-
-# Teaspoon.defer = true
-# setTimeout(Teaspoon.execute, 1000)
-
-# Matching files
-
-# By default Teaspoon will look for files that match
-# _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
-# and it'll be included in the default suite automatically. If you want to
-# customize suites, check out the configuration in teaspoon_env.rb
-
-# Manifest
-
-# If you'd rather require your spec files manually (to control order for
-# instance) you can disable the suite matcher in the configuration and use this
-# file as a manifest.
-
-# For more information: http://github.com/modeset/teaspoon
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..7d91ed0f85582d114558f4b077a54fd710188a07
--- /dev/null
+++ b/spec/javascripts/spec_helper.js
@@ -0,0 +1,22 @@
+
+/*= require support/bind-poly */
+
+
+/*= require jquery */
+
+
+/*= require jquery.turbolinks */
+
+
+/*= require bootstrap */
+
+
+/*= require underscore */
+
+
+/*= require support/jasmine-jquery-2.1.0 */
+
+(function() {
+
+
+}).call(this);
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e5dd1e59bf65d5d387710e1eca155aac17b5d45
--- /dev/null
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -0,0 +1,44 @@
+
+/*= require syntax_highlight */
+
+(function() {
+  describe('Syntax Highlighter', function() {
+    var stubUserColorScheme;
+    stubUserColorScheme = function(value) {
+      if (window.gon == null) {
+        window.gon = {};
+      }
+      return window.gon.user_color_scheme = value;
+    };
+    describe('on a js-syntax-highlight element', function() {
+      beforeEach(function() {
+        return fixture.set('<div class="js-syntax-highlight"></div>');
+      });
+      return it('applies syntax highlighting', function() {
+        stubUserColorScheme('monokai');
+        $('.js-syntax-highlight').syntaxHighlight();
+        return expect($('.js-syntax-highlight')).toHaveClass('monokai');
+      });
+    });
+    return describe('on a parent element', function() {
+      beforeEach(function() {
+        return fixture.set("<div class=\"parent\">\n  <div class=\"js-syntax-highlight\"></div>\n  <div class=\"foo\"></div>\n  <div class=\"js-syntax-highlight\"></div>\n</div>");
+      });
+      it('applies highlighting to all applicable children', function() {
+        stubUserColorScheme('monokai');
+        $('.parent').syntaxHighlight();
+        expect($('.parent, .foo')).not.toHaveClass('monokai');
+        return expect($('.monokai').length).toBe(2);
+      });
+      return it('prevents an infinite loop when no matches exist', function() {
+        var highlight;
+        fixture.set('<div></div>');
+        highlight = function() {
+          return $('div').syntaxHighlight();
+        };
+        return expect(highlight).not.toThrow();
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/syntax_highlight_spec.js.coffee b/spec/javascripts/syntax_highlight_spec.js.coffee
deleted file mode 100644
index 6a73b6bf32c1ccdb38e31f6c6018a2c5895d355c..0000000000000000000000000000000000000000
--- a/spec/javascripts/syntax_highlight_spec.js.coffee
+++ /dev/null
@@ -1,42 +0,0 @@
-#= require syntax_highlight
-
-describe 'Syntax Highlighter', ->
-  stubUserColorScheme = (value) ->
-    window.gon ?= {}
-    window.gon.user_color_scheme = value
-
-  describe 'on a js-syntax-highlight element', ->
-    beforeEach ->
-      fixture.set('<div class="js-syntax-highlight"></div>')
-
-    it 'applies syntax highlighting', ->
-      stubUserColorScheme('monokai')
-
-      $('.js-syntax-highlight').syntaxHighlight()
-
-      expect($('.js-syntax-highlight')).toHaveClass('monokai')
-
-  describe 'on a parent element', ->
-    beforeEach ->
-      fixture.set """
-        <div class="parent">
-          <div class="js-syntax-highlight"></div>
-          <div class="foo"></div>
-          <div class="js-syntax-highlight"></div>
-        </div>
-      """
-
-    it 'applies highlighting to all applicable children', ->
-      stubUserColorScheme('monokai')
-
-      $('.parent').syntaxHighlight()
-
-      expect($('.parent, .foo')).not.toHaveClass('monokai')
-      expect($('.monokai').length).toBe(2)
-
-    it 'prevents an infinite loop when no matches exist', ->
-      fixture.set('<div></div>')
-
-      highlight = -> $('div').syntaxHighlight()
-
-      expect(highlight).not.toThrow()
diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee
deleted file mode 100644
index 8ffeda11704636d8869f0263e98640b77695feca..0000000000000000000000000000000000000000
--- a/spec/javascripts/u2f/authenticate_spec.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-#= require u2f/authenticate
-#= require u2f/util
-#= require u2f/error
-#= require u2f
-#= require ./mock_u2f_device
-
-describe 'U2FAuthenticate', ->
-  fixture.load('u2f/authenticate')
-
-  beforeEach ->
-    @u2fDevice = new MockU2FDevice
-    @container = $("#js-authenticate-u2f")
-    @component = new U2FAuthenticate(@container, {sign_requests: []}, "token")
-    @component.start()
-
-  it 'allows authenticating via a U2F device', ->
-    setupButton = @container.find("#js-login-u2f-device")
-    setupMessage = @container.find("p")
-    expect(setupMessage.text()).toContain('Insert your security key')
-    expect(setupButton.text()).toBe('Login Via U2F Device')
-    setupButton.trigger('click')
-
-    inProgressMessage = @container.find("p")
-    expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
-
-    @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
-    authenticatedMessage = @container.find("p")
-    deviceResponse = @container.find('#js-device-response')
-    expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
-    expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
-
-  describe "errors", ->
-    it "displays an error message", ->
-      setupButton = @container.find("#js-login-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
-      errorMessage = @container.find("p")
-      expect(errorMessage.text()).toContain("There was a problem communicating with your device")
-
-    it "allows retrying authentication after an error", ->
-      setupButton = @container.find("#js-login-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToAuthenticateRequest({errorCode: "error!"})
-      retryButton = @container.find("#js-u2f-try-again")
-      retryButton.trigger('click')
-
-      setupButton = @container.find("#js-login-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToAuthenticateRequest({deviceData: "this is data from the device"})
-      authenticatedMessage = @container.find("p")
-      expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server")
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e008ce956adb5ad65a247c329eb0534e336485fe
--- /dev/null
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -0,0 +1,75 @@
+
+/*= require u2f/authenticate */
+
+
+/*= require u2f/util */
+
+
+/*= require u2f/error */
+
+
+/*= require u2f */
+
+
+/*= require ./mock_u2f_device */
+
+(function() {
+  describe('U2FAuthenticate', function() {
+    fixture.load('u2f/authenticate');
+    beforeEach(function() {
+      this.u2fDevice = new MockU2FDevice;
+      this.container = $("#js-authenticate-u2f");
+      this.component = new U2FAuthenticate(this.container, {
+        sign_requests: []
+      }, "token");
+      return this.component.start();
+    });
+    it('allows authenticating via a U2F device', function() {
+      var authenticatedMessage, deviceResponse, inProgressMessage, setupButton, setupMessage;
+      setupButton = this.container.find("#js-login-u2f-device");
+      setupMessage = this.container.find("p");
+      expect(setupMessage.text()).toContain('Insert your security key');
+      expect(setupButton.text()).toBe('Login Via U2F Device');
+      setupButton.trigger('click');
+      inProgressMessage = this.container.find("p");
+      expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
+      this.u2fDevice.respondToAuthenticateRequest({
+        deviceData: "this is data from the device"
+      });
+      authenticatedMessage = this.container.find("p");
+      deviceResponse = this.container.find('#js-device-response');
+      expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server");
+      return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+    });
+    return describe("errors", function() {
+      it("displays an error message", function() {
+        var errorMessage, setupButton;
+        setupButton = this.container.find("#js-login-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToAuthenticateRequest({
+          errorCode: "error!"
+        });
+        errorMessage = this.container.find("p");
+        return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
+      });
+      return it("allows retrying authentication after an error", function() {
+        var authenticatedMessage, retryButton, setupButton;
+        setupButton = this.container.find("#js-login-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToAuthenticateRequest({
+          errorCode: "error!"
+        });
+        retryButton = this.container.find("#js-u2f-try-again");
+        retryButton.trigger('click');
+        setupButton = this.container.find("#js-login-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToAuthenticateRequest({
+          deviceData: "this is data from the device"
+        });
+        authenticatedMessage = this.container.find("p");
+        return expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server");
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
new file mode 100644
index 0000000000000000000000000000000000000000..ca91a716ba3989102fb2fad0727122e17fbe6074
--- /dev/null
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -0,0 +1,33 @@
+(function() {
+  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+  this.MockU2FDevice = (function() {
+    function MockU2FDevice() {
+      this.respondToAuthenticateRequest = bind(this.respondToAuthenticateRequest, this);
+      this.respondToRegisterRequest = bind(this.respondToRegisterRequest, this);
+      window.u2f || (window.u2f = {});
+      window.u2f.register = (function(_this) {
+        return function(appId, registerRequests, signRequests, callback) {
+          return _this.registerCallback = callback;
+        };
+      })(this);
+      window.u2f.sign = (function(_this) {
+        return function(appId, challenges, signRequests, callback) {
+          return _this.authenticateCallback = callback;
+        };
+      })(this);
+    }
+
+    MockU2FDevice.prototype.respondToRegisterRequest = function(params) {
+      return this.registerCallback(params);
+    };
+
+    MockU2FDevice.prototype.respondToAuthenticateRequest = function(params) {
+      return this.authenticateCallback(params);
+    };
+
+    return MockU2FDevice;
+
+  })();
+
+}).call(this);
diff --git a/spec/javascripts/u2f/mock_u2f_device.js.coffee b/spec/javascripts/u2f/mock_u2f_device.js.coffee
deleted file mode 100644
index 97ed0e83a0e3287cd3539070779348b081e4a66c..0000000000000000000000000000000000000000
--- a/spec/javascripts/u2f/mock_u2f_device.js.coffee
+++ /dev/null
@@ -1,15 +0,0 @@
-class @MockU2FDevice
-  constructor: () ->
-    window.u2f ||= {}
-
-    window.u2f.register = (appId, registerRequests, signRequests, callback) =>
-      @registerCallback = callback
-
-    window.u2f.sign = (appId, challenges, signRequests, callback) =>
-      @authenticateCallback = callback
-
-  respondToRegisterRequest: (params) =>
-    @registerCallback(params)
-
-  respondToAuthenticateRequest: (params) =>
-    @authenticateCallback(params)
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..21c5266c60e72f1c0ddd9265e04161cf5127a4ac
--- /dev/null
+++ b/spec/javascripts/u2f/register_spec.js
@@ -0,0 +1,81 @@
+
+/*= require u2f/register */
+
+
+/*= require u2f/util */
+
+
+/*= require u2f/error */
+
+
+/*= require u2f */
+
+
+/*= require ./mock_u2f_device */
+
+(function() {
+  describe('U2FRegister', function() {
+    fixture.load('u2f/register');
+    beforeEach(function() {
+      this.u2fDevice = new MockU2FDevice;
+      this.container = $("#js-register-u2f");
+      this.component = new U2FRegister(this.container, $("#js-register-u2f-templates"), {}, "token");
+      return this.component.start();
+    });
+    it('allows registering a U2F device', function() {
+      var deviceResponse, inProgressMessage, registeredMessage, setupButton;
+      setupButton = this.container.find("#js-setup-u2f-device");
+      expect(setupButton.text()).toBe('Setup New U2F Device');
+      setupButton.trigger('click');
+      inProgressMessage = this.container.children("p");
+      expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
+      this.u2fDevice.respondToRegisterRequest({
+        deviceData: "this is data from the device"
+      });
+      registeredMessage = this.container.find('p');
+      deviceResponse = this.container.find('#js-device-response');
+      expect(registeredMessage.text()).toContain("Your device was successfully set up!");
+      return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+    });
+    return describe("errors", function() {
+      it("doesn't allow the same device to be registered twice (for the same user", function() {
+        var errorMessage, setupButton;
+        setupButton = this.container.find("#js-setup-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToRegisterRequest({
+          errorCode: 4
+        });
+        errorMessage = this.container.find("p");
+        return expect(errorMessage.text()).toContain("already been registered with us");
+      });
+      it("displays an error message for other errors", function() {
+        var errorMessage, setupButton;
+        setupButton = this.container.find("#js-setup-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToRegisterRequest({
+          errorCode: "error!"
+        });
+        errorMessage = this.container.find("p");
+        return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
+      });
+      return it("allows retrying registration after an error", function() {
+        var registeredMessage, retryButton, setupButton;
+        setupButton = this.container.find("#js-setup-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToRegisterRequest({
+          errorCode: "error!"
+        });
+        retryButton = this.container.find("#U2FTryAgain");
+        retryButton.trigger('click');
+        setupButton = this.container.find("#js-setup-u2f-device");
+        setupButton.trigger('click');
+        this.u2fDevice.respondToRegisterRequest({
+          deviceData: "this is data from the device"
+        });
+        registeredMessage = this.container.find("p");
+        return expect(registeredMessage.text()).toContain("Your device was successfully set up!");
+      });
+    });
+  });
+
+}).call(this);
diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee
deleted file mode 100644
index 87dc769792bd0461e4de084b4fecd9fcf1b447b6..0000000000000000000000000000000000000000
--- a/spec/javascripts/u2f/register_spec.js.coffee
+++ /dev/null
@@ -1,56 +0,0 @@
-#= require u2f/register
-#= require u2f/util
-#= require u2f/error
-#= require u2f
-#= require ./mock_u2f_device
-
-describe 'U2FRegister', ->
-  fixture.load('u2f/register')
-
-  beforeEach ->
-    @u2fDevice = new MockU2FDevice
-    @container = $("#js-register-u2f")
-    @component = new U2FRegister(@container, $("#js-register-u2f-templates"), {}, "token")
-    @component.start()
-
-  it 'allows registering a U2F device', ->
-    setupButton = @container.find("#js-setup-u2f-device")
-    expect(setupButton.text()).toBe('Setup New U2F Device')
-    setupButton.trigger('click')
-
-    inProgressMessage = @container.children("p")
-    expect(inProgressMessage.text()).toContain("Trying to communicate with your device")
-
-    @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
-    registeredMessage = @container.find('p')
-    deviceResponse = @container.find('#js-device-response')
-    expect(registeredMessage.text()).toContain("Your device was successfully set up!")
-    expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}')
-
-  describe "errors", ->
-    it "doesn't allow the same device to be registered twice (for the same user", ->
-      setupButton = @container.find("#js-setup-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToRegisterRequest({errorCode: 4})
-      errorMessage = @container.find("p")
-      expect(errorMessage.text()).toContain("already been registered with us")
-
-    it "displays an error message for other errors", ->
-      setupButton = @container.find("#js-setup-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
-      errorMessage = @container.find("p")
-      expect(errorMessage.text()).toContain("There was a problem communicating with your device")
-
-    it "allows retrying registration after an error", ->
-      setupButton = @container.find("#js-setup-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToRegisterRequest({errorCode: "error!"})
-      retryButton = @container.find("#U2FTryAgain")
-      retryButton.trigger('click')
-
-      setupButton = @container.find("#js-setup-u2f-device")
-      setupButton.trigger('click')
-      @u2fDevice.respondToRegisterRequest({deviceData: "this is data from the device"})
-      registeredMessage = @container.find("p")
-      expect(registeredMessage.text()).toContain("Your device was successfully set up!")
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..3d680ec8ea3f49da8e083f057db5d37bfdcbb339
--- /dev/null
+++ b/spec/javascripts/zen_mode_spec.js
@@ -0,0 +1,73 @@
+
+/*= require zen_mode */
+
+(function() {
+  var enterZen, escapeKeydown, exitZen;
+
+  describe('ZenMode', function() {
+    fixture.preload('zen_mode.html');
+    beforeEach(function() {
+      fixture.load('zen_mode.html');
+      spyOn(Dropzone, 'forElement').and.callFake(function() {
+        return {
+          enable: function() {
+            return true;
+          }
+        };
+      });
+      this.zen = new ZenMode();
+      return this.zen.scroll_position = 456;
+    });
+    describe('on enter', function() {
+      it('pauses Mousetrap', function() {
+        spyOn(Mousetrap, 'pause');
+        enterZen();
+        return expect(Mousetrap.pause).toHaveBeenCalled();
+      });
+      return it('removes textarea styling', function() {
+        $('textarea').attr('style', 'height: 400px');
+        enterZen();
+        return expect('textarea').not.toHaveAttr('style');
+      });
+    });
+    describe('in use', function() {
+      beforeEach(function() {
+        return enterZen();
+      });
+      return it('exits on Escape', function() {
+        escapeKeydown();
+        return expect($('.zen-backdrop')).not.toHaveClass('fullscreen');
+      });
+    });
+    return describe('on exit', function() {
+      beforeEach(function() {
+        return enterZen();
+      });
+      it('unpauses Mousetrap', function() {
+        spyOn(Mousetrap, 'unpause');
+        exitZen();
+        return expect(Mousetrap.unpause).toHaveBeenCalled();
+      });
+      return it('restores the scroll position', function() {
+        spyOn(this.zen, 'scrollTo');
+        exitZen();
+        return expect(this.zen.scrollTo).toHaveBeenCalled();
+      });
+    });
+  });
+
+  enterZen = function() {
+    return $('a.js-zen-enter').click();
+  };
+
+  exitZen = function() {
+    return $('a.js-zen-leave').click();
+  };
+
+  escapeKeydown = function() {
+    return $('textarea').trigger($.Event('keydown', {
+      keyCode: 27
+    }));
+  };
+
+}).call(this);
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
deleted file mode 100644
index b790fce01ede562c5723a1a6e30286e2301798c7..0000000000000000000000000000000000000000
--- a/spec/javascripts/zen_mode_spec.js.coffee
+++ /dev/null
@@ -1,51 +0,0 @@
-#= require zen_mode
-
-describe 'ZenMode', ->
-  fixture.preload('zen_mode.html')
-
-  beforeEach ->
-    fixture.load('zen_mode.html')
-
-    # Stub Dropzone.forElement(...).enable()
-    spyOn(Dropzone, 'forElement').and.callFake ->
-      enable: -> true
-
-    @zen = new ZenMode()
-
-    # Set this manually because we can't actually scroll the window
-    @zen.scroll_position = 456
-
-  describe 'on enter', ->
-    it 'pauses Mousetrap', ->
-      spyOn(Mousetrap, 'pause')
-      enterZen()
-      expect(Mousetrap.pause).toHaveBeenCalled()
-
-    it 'removes textarea styling', ->
-      $('textarea').attr('style', 'height: 400px')
-      enterZen()
-      expect('textarea').not.toHaveAttr('style')
-
-  describe 'in use', ->
-    beforeEach -> enterZen()
-
-    it 'exits on Escape', ->
-      escapeKeydown()
-      expect($('.zen-backdrop')).not.toHaveClass('fullscreen')
-
-  describe 'on exit', ->
-    beforeEach -> enterZen()
-
-    it 'unpauses Mousetrap', ->
-      spyOn(Mousetrap, 'unpause')
-      exitZen()
-      expect(Mousetrap.unpause).toHaveBeenCalled()
-
-    it 'restores the scroll position', ->
-      spyOn(@zen, 'scrollTo')
-      exitZen()
-      expect(@zen.scrollTo).toHaveBeenCalled()
-
-enterZen      = -> $('a.js-zen-enter').click() # Ohmmmmmmm
-exitZen       = -> $('a.js-zen-leave').click()
-escapeKeydown = -> $('textarea').trigger($.Event('keydown', {keyCode: 27}))
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index b9e4a4eaf0ecd54ce4ed5306f2f6956e8be37160..224baca803019cb850e18f44cafd86f59076f421 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -1,5 +1,3 @@
-# encoding: UTF-8
-
 require 'spec_helper'
 
 describe Banzai::Filter::RelativeLinkFilter, lib: true do
@@ -19,6 +17,10 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
     %(<img src="#{path}" />)
   end
 
+  def video(path)
+    %(<video src="#{path}"></video>)
+  end
+
   def link(path)
     %(<a href="#{path}">#{path}</a>)
   end
@@ -39,6 +41,12 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
       doc = filter(image('files/images/logo-black.png'))
       expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
     end
+
+    it 'does not modify any relative URL in video' do
+      doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
+
+      expect(doc.at_css('video')['src']).to eq 'files/videos/intro.mp4'
+    end
   end
 
   shared_examples :relative_to_requested do
@@ -70,12 +78,24 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
   end
 
   context 'with a valid repository' do
+    it 'rebuilds absolute URL for a file in the repo' do
+      doc = filter(link('/doc/api/README.md'))
+      expect(doc.at_css('a')['href']).
+        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+    end
+
     it 'rebuilds relative URL for a file in the repo' do
       doc = filter(link('doc/api/README.md'))
       expect(doc.at_css('a')['href']).
         to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
     end
 
+    it 'rebuilds relative URL for a file in the repo with leading ./' do
+      doc = filter(link('./doc/api/README.md'))
+      expect(doc.at_css('a')['href']).
+        to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+    end
+
     it 'rebuilds relative URL for a file in the repo up one directory' do
       relative_link = link('../api/README.md')
       doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
@@ -113,11 +133,26 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
     end
 
     it 'rebuilds relative URL for an image in the repo' do
+      doc = filter(image('files/images/logo-black.png'))
+
+      expect(doc.at_css('img')['src']).
+        to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+    end
+
+    it 'rebuilds relative URL for link to an image in the repo' do
       doc = filter(link('files/images/logo-black.png'))
+
       expect(doc.at_css('a')['href']).
         to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
     end
 
+    it 'rebuilds relative URL for a video in the repo' do
+      doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
+
+      expect(doc.at_css('video')['src']).
+        to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
+    end
+
     it 'does not modify relative URL with an anchor only' do
       doc = filter(link('#section-1'))
       expect(doc.at_css('a')['href']).to eq '#section-1'
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 6a5d003e87f048190f15202478b7bcb2ec9f7161..356dd01a03a81014ecaac3a92792f215c58bddfe 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -1,5 +1,3 @@
-# encoding: UTF-8
-
 require 'spec_helper'
 
 describe Banzai::Filter::TableOfContentsFilter, lib: true do
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 273d2ed709adf387a16134d1de0787f2915848f3..8b76c1d73c993b5824821e14435f71483a2cbf12 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -1,5 +1,3 @@
-# encoding: UTF-8
-
 require 'spec_helper'
 
 describe Banzai::Filter::UploadLinkFilter, lib: true do
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc4349f80ba5aa18f2f60c44b53afd3b37062d1b
--- /dev/null
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Banzai::Filter::VideoLinkFilter, lib: true do
+  def filter(doc, contexts = {})
+    contexts.reverse_merge!({
+      project: project
+    })
+
+    described_class.call(doc, contexts)
+  end
+
+  def link_to_image(path)
+    %(<img src="#{path}" />)
+  end
+
+  let(:project) { create(:project) }
+
+  context 'when the element src has a video extension' do
+    UploaderHelper::VIDEO_EXT.each do |ext|
+      it "replaces the image tag 'path/video.#{ext}' with a video tag" do
+        container = filter(link_to_image("/path/video.#{ext}")).children.first
+
+        expect(container.name).to eq 'div'
+        expect(container['class']).to eq 'video-container'
+
+        video, paragraph = container.children
+
+        expect(video.name).to eq 'video'
+        expect(video['src']).to eq "/path/video.#{ext}"
+
+        expect(paragraph.name).to eq 'p'
+
+        link = paragraph.children.first
+
+        expect(link.name).to eq 'a'
+        expect(link['href']).to eq "/path/video.#{ext}"
+        expect(link['target']).to eq '_blank'
+      end
+    end
+  end
+
+  context 'when the element src is an image' do
+    it 'leaves the document unchanged' do
+      element = filter(link_to_image('/path/my_image.jpg')).children.first
+
+      expect(element.name).to eq 'img'
+      expect(element['src']).to eq '/path/my_image.jpg'
+    end
+  end
+
+end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 514c752546d83f722225f4b5491180239d24748c..85cfe728b6a6a60806a7ecbd6b374da09205574d 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -16,17 +16,17 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
       end
 
       it 'returns the nodes when the user can read the issue' do
-        expect(Ability.abilities).to receive(:allowed?).
-          with(user, :read_issue, issue).
-          and_return(true)
+        expect(Ability).to receive(:issues_readable_by_user).
+          with([issue], user).
+          and_return([issue])
 
         expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
       end
 
       it 'returns an empty Array when the user can not read the issue' do
-        expect(Ability.abilities).to receive(:allowed?).
-          with(user, :read_issue, issue).
-          and_return(false)
+        expect(Ability).to receive(:issues_readable_by_user).
+          with([issue], user).
+          and_return([])
 
         expect(subject.nodes_visible_to_user(user, [link])).to eq([])
       end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index ad6587b4c25e28ba387d496b1940120655b605ae..61490555ff5a4039954e684d1148e5ccf90a825f 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -162,7 +162,7 @@ module Ci
 
           shared_examples 'raises an error' do
             it do
-              expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: only parameter should be an array of strings or regexps')
+              expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:only config should be an array of strings or regexps')
             end
           end
 
@@ -318,7 +318,7 @@ module Ci
 
           shared_examples 'raises an error' do
             it do
-              expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: except parameter should be an array of strings or regexps')
+              expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:except config should be an array of strings or regexps')
             end
           end
 
@@ -559,7 +559,7 @@ module Ci
             it 'raises error' do
               expect { subject }
                 .to raise_error(GitlabCiYamlProcessor::ValidationError,
-                                /job: variables should be a map/)
+                                 /jobs:rspec:variables config should be a hash of key value pairs/)
             end
           end
 
@@ -774,7 +774,7 @@ module Ci
         let(:environment) { 1 }
 
         it 'raises error' do
-          expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
         end
       end
 
@@ -782,7 +782,7 @@ module Ci
         let(:environment) { 'production staging' }
 
         it 'raises error' do
-          expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}")
+          expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
         end
       end
     end
@@ -973,7 +973,7 @@ EOT
         config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings")
       end
 
       it "returns errors if before_script parameter is invalid" do
@@ -987,7 +987,7 @@ EOT
         config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings")
       end
 
       it "returns errors if after_script parameter is invalid" do
@@ -1001,7 +1001,7 @@ EOT
         config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: after_script should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings")
       end
 
       it "returns errors if image parameter is invalid" do
@@ -1015,21 +1015,21 @@ EOT
         config = YAML.dump({ '' => { script: "test" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:job name can't be blank")
       end
 
       it "returns errors if job name is non-string" do
         config = YAML.dump({ 10 => { script: "test" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:10 name should be a symbol")
       end
 
       it "returns errors if job image parameter is invalid" do
         config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string")
       end
 
       it "returns errors if services parameter is not an array" do
@@ -1050,49 +1050,56 @@ EOT
         config = YAML.dump({ rspec: { script: "test", services: "test" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
       end
 
       it "returns errors if job services parameter is not an array of strings" do
         config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
       end
 
-      it "returns errors if there are unknown parameters" do
+      it "returns error if job configuration is invalid" do
         config = YAML.dump({ extra: "bundle update" })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra config should be a hash")
       end
 
       it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do
         config = YAML.dump({ extra: { services: "test" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings")
       end
 
       it "returns errors if there are no jobs defined" do
         config = YAML.dump({ before_script: ["bundle update"] })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
+      end
+
+      it "returns errors if there are no visible jobs defined" do
+        config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } })
+        expect do
+          GitlabCiYamlProcessor.new(config, path)
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
       end
 
       it "returns errors if job allow_failure parameter is not an boolean" do
         config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value")
       end
 
       it "returns errors if job stage is not a string" do
         config = YAML.dump({ rspec: { script: "test", type: 1 } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be a string")
       end
 
       it "returns errors if job stage is not a pre-defined stage" do
@@ -1141,49 +1148,49 @@ EOT
         config = YAML.dump({ rspec: { script: "test", when: 1 } })
         expect do
           GitlabCiYamlProcessor.new(config, path)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
       end
 
       it "returns errors if job artifacts:name is not an a string" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string")
       end
 
       it "returns errors if job artifacts:when is not an a predefined value" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:when parameter should be on_success, on_failure or always")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always")
       end
 
       it "returns errors if job artifacts:expire_in is not an a string" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: 1 } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
       end
 
       it "returns errors if job artifacts:expire_in is not an a valid duration" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
       end
 
       it "returns errors if job artifacts:untracked is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value")
       end
 
       it "returns errors if job artifacts:paths is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings")
       end
 
       it "returns errors if cache:untracked is not an array of strings" do
@@ -1211,28 +1218,28 @@ EOT
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol")
       end
 
       it "returns errors if job cache:untracked is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value")
       end
 
       it "returns errors if job cache:paths is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings")
       end
 
       it "returns errors if job dependencies is not an array of strings" do
         config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
         expect do
           GitlabCiYamlProcessor.new(config)
-        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
+        end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
       end
     end
 
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
index 88a71528867c2d2f72296546c4d65837fcf504d7..b08396da4d20cf85ca363a254e851c0a8aba76f1 100644
--- a/spec/lib/gitlab/akismet_helper_spec.rb
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -1,7 +1,7 @@
 require 'spec_helper'
 
 describe Gitlab::AkismetHelper, type: :helper do
-  let(:project) { create(:project) }
+  let(:project) { create(:project, :public) }
   let(:user) { create(:user) }
 
   before do
@@ -11,13 +11,13 @@ describe Gitlab::AkismetHelper, type: :helper do
   end
 
   describe '#check_for_spam?' do
-    it 'returns true for non-member' do
-      expect(helper.check_for_spam?(project, user)).to eq(true)
+    it 'returns true for public project' do
+      expect(helper.check_for_spam?(project)).to eq(true)
     end
 
-    it 'returns false for member' do
-      project.team << [user, :guest]
-      expect(helper.check_for_spam?(project, user)).to eq(false)
+    it 'returns false for private project' do
+      project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+      expect(helper.check_for_spam?(project)).to eq(false)
     end
   end
 
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index 2034445a197320280ae1c625988b25c6188e104b..f3b522a02f52d173e5fa06f0a8e09775c5dca69c 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
                                     sha: sha,
                                     ref: branch)
 
-    create(:ci_build, pipeline: pipeline)
+    create(:ci_build, pipeline: pipeline, stage: 'notify')
   end
 
   def status_node(data, status)
diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c09a0a9c793532cb18da06c4add0f5cdbabb79fa
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Artifacts do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validation' do
+    context 'when entry config value is correct' do
+      let(:config) { { paths: %w[public/] } }
+
+      describe '#value' do
+        it 'returns artifacs configuration' do
+          expect(entry.value).to eq config
+        end
+      end
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(entry).to be_valid
+        end
+      end
+    end
+
+    context 'when entry value is not correct' do
+      describe '#errors' do
+        context 'when value of attribute is invalid' do
+          let(:config) { { name: 10 } }
+
+          it 'reports error' do
+            expect(entry.errors)
+              .to include 'artifacts name should be a string'
+          end
+        end
+
+        context 'when there is an unknown key present' do
+          let(:config) { { test: 100 } }
+
+          it 'reports error' do
+            expect(entry.errors)
+              .to include 'artifacts config contains unknown keys: test'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/attributable_spec.rb b/spec/lib/gitlab/ci/config/node/attributable_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..24d9daafd8801a3ba781aebc09003080b0ce21db
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/attributable_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Attributable do
+  let(:node) { Class.new }
+  let(:instance) { node.new }
+
+  before do
+    node.include(described_class)
+
+    node.class_eval do
+      attributes :name, :test
+    end
+  end
+
+  context 'config is a hash' do
+    before do
+      allow(instance)
+        .to receive(:config)
+        .and_return({ name: 'some name', test: 'some test' })
+    end
+
+    it 'returns the value of config' do
+      expect(instance.name).to eq 'some name'
+      expect(instance.test).to eq 'some test'
+    end
+
+    it 'returns no method error for unknown attributes' do
+      expect { instance.unknown }.to raise_error(NoMethodError)
+    end
+  end
+
+  context 'config is not a hash' do
+    before do
+      allow(instance)
+        .to receive(:config)
+        .and_return('some test')
+    end
+
+    it 'returns nil' do
+      expect(instance.test).to be_nil
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/commands_spec.rb b/spec/lib/gitlab/ci/config/node/commands_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e373c40706f0c8ca91a6d15f3a488edecfc5de77
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/commands_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Commands do
+  let(:entry) { described_class.new(config) }
+
+  context 'when entry config value is an array' do
+    let(:config) { ['ls', 'pwd'] }
+
+    describe '#value' do
+      it 'returns array of strings' do
+        expect(entry.value).to eq config
+      end
+    end
+
+    describe '#errors' do
+      it 'does not append errors' do
+        expect(entry.errors).to be_empty
+      end
+    end
+  end
+
+  context 'when entry config value is a string' do
+    let(:config) { 'ls' }
+
+    describe '#value' do
+      it 'returns array with single element' do
+        expect(entry.value).to eq ['ls']
+      end
+    end
+
+    describe '#valid?' do
+      it 'is valid' do
+        expect(entry).to be_valid
+      end
+    end
+  end
+
+  context 'when entry value is not valid' do
+    let(:config) { 1 }
+
+    describe '#errors' do
+      it 'saves errors' do
+        expect(entry.errors)
+          .to include 'commands config should be a ' \
+                      'string or an array of strings'
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index 91ddef7bfbf05aba58db11eb034a68ebf86eee58..d26185ba585c32a1606369cf60e3dee8228e23cc 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Config::Node::Factory do
   describe '#create!' do
-    let(:factory) { described_class.new(entry_class) }
-    let(:entry_class) { Gitlab::Ci::Config::Node::Script }
+    let(:factory) { described_class.new(node) }
+    let(:node) { Gitlab::Ci::Config::Node::Script }
 
-    context 'when setting up a value' do
+    context 'when setting a concrete value' do
       it 'creates entry with valid value' do
         entry = factory
-          .with(value: ['ls', 'pwd'])
+          .value(['ls', 'pwd'])
           .create!
 
         expect(entry.value).to eq ['ls', 'pwd']
@@ -17,7 +17,7 @@ describe Gitlab::Ci::Config::Node::Factory do
       context 'when setting description' do
         it 'creates entry with description' do
           entry = factory
-            .with(value: ['ls', 'pwd'])
+            .value(['ls', 'pwd'])
             .with(description: 'test description')
             .create!
 
@@ -29,7 +29,8 @@ describe Gitlab::Ci::Config::Node::Factory do
       context 'when setting key' do
         it 'creates entry with custom key' do
           entry = factory
-            .with(value: ['ls', 'pwd'], key: 'test key')
+            .value(['ls', 'pwd'])
+            .with(key: 'test key')
             .create!
 
           expect(entry.key).to eq 'test key'
@@ -37,19 +38,20 @@ describe Gitlab::Ci::Config::Node::Factory do
       end
 
       context 'when setting a parent' do
-        let(:parent) { Object.new }
+        let(:object) { Object.new }
 
         it 'creates entry with valid parent' do
           entry = factory
-            .with(value: 'ls', parent: parent)
+            .value('ls')
+            .with(parent: object)
             .create!
 
-          expect(entry.parent).to eq parent
+          expect(entry.parent).to eq object
         end
       end
     end
 
-    context 'when not setting up a value' do
+    context 'when not setting a value' do
       it 'raises error' do
         expect { factory.create! }.to raise_error(
           Gitlab::Ci::Config::Node::Factory::InvalidFactory
@@ -60,11 +62,25 @@ describe Gitlab::Ci::Config::Node::Factory do
     context 'when creating entry with nil value' do
       it 'creates an undefined entry' do
         entry = factory
-          .with(value: nil)
+          .value(nil)
           .create!
 
         expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
       end
     end
+
+    context 'when passing metadata' do
+      let(:node) { spy('node') }
+
+      it 'passes metadata as a parameter' do
+        factory
+          .value('some value')
+          .metadata(some: 'hash')
+          .create!
+
+        expect(node).to have_received(:new)
+          .with('some value', { some: 'hash' })
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index c87c9e97bc82b49b711e714a101b5422d1be18e0..2f87d270b36a4773fbbba4d47e2f6d2dd5236d19 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -22,38 +22,40 @@ describe Gitlab::Ci::Config::Node::Global do
           variables: { VAR: 'value' },
           after_script: ['make clean'],
           stages: ['build', 'pages'],
-          cache: { key: 'k', untracked: true, paths: ['public/'] } }
+          cache: { key: 'k', untracked: true, paths: ['public/'] },
+          rspec: { script: %w[rspec ls] },
+          spinach: { script: 'spinach' } }
       end
 
       describe '#process!' do
         before { global.process! }
 
         it 'creates nodes hash' do
-          expect(global.nodes).to be_an Array
+          expect(global.descendants).to be_an Array
         end
 
         it 'creates node object for each entry' do
-          expect(global.nodes.count).to eq 8
+          expect(global.descendants.count).to eq 8
         end
 
         it 'creates node object using valid class' do
-          expect(global.nodes.first)
+          expect(global.descendants.first)
             .to be_an_instance_of Gitlab::Ci::Config::Node::Script
-          expect(global.nodes.second)
+          expect(global.descendants.second)
             .to be_an_instance_of Gitlab::Ci::Config::Node::Image
         end
 
         it 'sets correct description for nodes' do
-          expect(global.nodes.first.description)
+          expect(global.descendants.first.description)
             .to eq 'Script that will be executed before each job.'
-          expect(global.nodes.second.description)
+          expect(global.descendants.second.description)
             .to eq 'Docker image that will be used to execute jobs.'
         end
-      end
 
-      describe '#leaf?' do
-        it 'is not leaf' do
-          expect(global).not_to be_leaf
+        describe '#leaf?' do
+          it 'is not leaf' do
+            expect(global).not_to be_leaf
+          end
         end
       end
 
@@ -63,6 +65,12 @@ describe Gitlab::Ci::Config::Node::Global do
             expect(global.before_script).to be nil
           end
         end
+
+        describe '#leaf?' do
+          it 'is leaf' do
+            expect(global).to be_leaf
+          end
+        end
       end
 
       context 'when processed' do
@@ -106,7 +114,10 @@ describe Gitlab::Ci::Config::Node::Global do
           end
 
           context 'when deprecated types key defined' do
-            let(:hash) { { types: ['test', 'deploy'] } }
+            let(:hash) do
+              { types: ['test', 'deploy'],
+                rspec: { script: 'rspec' } }
+            end
 
             it 'returns array of types as stages' do
               expect(global.stages).to eq %w[test deploy]
@@ -120,20 +131,33 @@ describe Gitlab::Ci::Config::Node::Global do
               .to eq(key: 'k', untracked: true, paths: ['public/'])
           end
         end
+
+        describe '#jobs' do
+          it 'returns jobs configuration' do
+            expect(global.jobs).to eq(
+              rspec: { name: :rspec,
+                       script: %w[rspec ls],
+                       stage: 'test' },
+              spinach: { name: :spinach,
+                         script: %w[spinach],
+                         stage: 'test' }
+            )
+          end
+        end
       end
     end
 
     context 'when most of entires not defined' do
-      let(:hash) { { cache: { key: 'a' }, rspec: {} } }
+      let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
       before { global.process! }
 
       describe '#nodes' do
         it 'instantizes all nodes' do
-          expect(global.nodes.count).to eq 8
+          expect(global.descendants.count).to eq 8
         end
 
         it 'contains undefined nodes' do
-          expect(global.nodes.first)
+          expect(global.descendants.first)
             .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
         end
       end
@@ -164,7 +188,7 @@ describe Gitlab::Ci::Config::Node::Global do
     # details.
     #
     context 'when entires specified but not defined' do
-      let(:hash) { { variables: nil } }
+      let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
       before { global.process! }
 
       describe '#variables' do
@@ -196,10 +220,8 @@ describe Gitlab::Ci::Config::Node::Global do
     end
 
     describe '#before_script' do
-      it 'raises error' do
-        expect { global.before_script }.to raise_error(
-          Gitlab::Ci::Config::Node::Entry::InvalidError
-        )
+      it 'returns nil' do
+        expect(global.before_script).to be_nil
       end
     end
   end
@@ -220,9 +242,9 @@ describe Gitlab::Ci::Config::Node::Global do
     end
   end
 
-  describe '#defined?' do
+  describe '#specified?' do
     it 'is concrete entry that is defined' do
-      expect(global.defined?).to be true
+      expect(global.specified?).to be true
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cc44e2cc05448e3c3b9e4b1da069e609c1fb8ec7
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::HiddenJob do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validations' do
+    context 'when entry config value is correct' do
+      let(:config) { { image: 'ruby:2.2' } }
+
+      describe '#value' do
+        it 'returns key value' do
+          expect(entry.value).to eq(image: 'ruby:2.2')
+        end
+      end
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(entry).to be_valid
+        end
+      end
+    end
+
+    context 'when entry value is not correct' do
+      context 'incorrect config value type' do
+        let(:config) { ['incorrect'] }
+
+        describe '#errors' do
+          it 'saves errors' do
+            expect(entry.errors)
+              .to include 'hidden job config should be a hash'
+          end
+        end
+      end
+
+      context 'when config is empty' do
+        let(:config) { {} }
+
+        describe '#valid' do
+          it 'is invalid' do
+            expect(entry).not_to be_valid
+          end
+        end
+      end
+    end
+  end
+
+  describe '#leaf?' do
+    it 'is a leaf' do
+      expect(entry).to be_leaf
+    end
+  end
+
+  describe '#relevant?' do
+    it 'is not a relevant entry' do
+      expect(entry).not_to be_relevant
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1484fb60dd81eedc8e4a2416f44c119118381151
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Job do
+  let(:entry) { described_class.new(config, name: :rspec) }
+
+  before { entry.process! }
+
+  describe 'validations' do
+    context 'when entry config value is correct' do
+      let(:config) { { script: 'rspec' } }
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(entry).to be_valid
+        end
+      end
+
+      context 'when job name is empty' do
+        let(:entry) { described_class.new(config, name: ''.to_sym) }
+
+        it 'reports error' do
+          expect(entry.errors)
+            .to include "job name can't be blank"
+        end
+      end
+    end
+
+    context 'when entry value is not correct' do
+      context 'incorrect config value type' do
+        let(:config) { ['incorrect'] }
+
+        describe '#errors' do
+          it 'reports error about a config type' do
+            expect(entry.errors)
+              .to include 'job config should be a hash'
+          end
+        end
+      end
+
+      context 'when config is empty' do
+        let(:config) { {} }
+
+        describe '#valid' do
+          it 'is invalid' do
+            expect(entry).not_to be_valid
+          end
+        end
+      end
+
+      context 'when unknown keys detected' do
+        let(:config) { { unknown: true } }
+
+        describe '#valid' do
+          it 'is not valid' do
+            expect(entry).not_to be_valid
+          end
+        end
+      end
+    end
+  end
+
+  describe '#value' do
+    context 'when entry is correct' do
+      let(:config) do
+        { before_script: %w[ls pwd],
+          script: 'rspec',
+          after_script: %w[cleanup] }
+      end
+
+      it 'returns correct value' do
+        expect(entry.value)
+          .to eq(name: :rspec,
+                 before_script: %w[ls pwd],
+                 script: %w[rspec],
+                 stage: 'test',
+                 after_script: %w[cleanup])
+      end
+    end
+  end
+
+  describe '#relevant?' do
+    it 'is a relevant entry' do
+      expect(entry).to be_relevant
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b8d9c70479cc1a29e95deb05113565840c3876ba
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Jobs do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validations' do
+    before { entry.process! }
+
+    context 'when entry config value is correct' do
+      let(:config) { { rspec: { script: 'rspec' } } }
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(entry).to be_valid
+        end
+      end
+    end
+
+    context 'when entry value is not correct' do
+      describe '#errors' do
+        context 'incorrect config value type' do
+          let(:config) { ['incorrect'] }
+
+          it 'returns error about incorrect type' do
+            expect(entry.errors)
+              .to include 'jobs config should be a hash'
+          end
+        end
+
+        context 'when job is unspecified' do
+          let(:config) { { rspec: nil } }
+
+          it 'reports error' do
+            expect(entry.errors).to include "rspec config can't be blank"
+          end
+        end
+
+        context 'when no visible jobs present' do
+          let(:config) { { '.hidden'.to_sym => { script: [] } } }
+
+          it 'returns error about no visible jobs defined' do
+            expect(entry.errors)
+              .to include 'jobs config should contain at least one visible job'
+          end
+        end
+      end
+    end
+  end
+
+  context 'when valid job entries processed' do
+    before { entry.process! }
+
+    let(:config) do
+      { rspec: { script: 'rspec' },
+        spinach: { script: 'spinach' },
+        '.hidden'.to_sym => {} }
+    end
+
+    describe '#value' do
+      it 'returns key value' do
+        expect(entry.value).to eq(
+          rspec: { name: :rspec,
+                   script: %w[rspec],
+                   stage: 'test' },
+          spinach: { name: :spinach,
+                     script: %w[spinach],
+                     stage: 'test' })
+      end
+    end
+
+    describe '#descendants' do
+      it 'creates valid descendant nodes' do
+        expect(entry.descendants.count).to eq 3
+        expect(entry.descendants.first(2))
+          .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
+        expect(entry.descendants.last)
+          .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
+      end
+    end
+
+    describe '#value' do
+      it 'returns value of visible jobs only' do
+        expect(entry.value.keys).to eq [:rspec, :spinach]
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ab5478dcfa01d2380c379391877f1d44030fa64
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/null_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Null do
+  let(:null) { described_class.new(nil) }
+
+  describe '#leaf?' do
+    it 'is leaf node' do
+      expect(null).to be_leaf
+    end
+  end
+
+  describe '#valid?' do
+    it 'is always valid' do
+      expect(null).to be_valid
+    end
+  end
+
+  describe '#errors' do
+    it 'is does not contain errors' do
+      expect(null.errors).to be_empty
+    end
+  end
+
+  describe '#value' do
+    it 'returns nil' do
+      expect(null.value).to eq nil
+    end
+  end
+
+  describe '#relevant?' do
+    it 'is not relevant' do
+      expect(null.relevant?).to eq false
+    end
+  end
+
+  describe '#specified?' do
+    it 'is not defined' do
+      expect(null.specified?).to eq false
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb9ec70762abe49e6cb78e10de2190f1f5a2dc91
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Stage do
+  let(:stage) { described_class.new(config) }
+
+  describe 'validations' do
+    context 'when stage config value is correct' do
+      let(:config) { 'build' }
+
+      describe '#value' do
+        it 'returns a stage key' do
+          expect(stage.value).to eq config
+        end
+      end
+
+      describe '#valid?' do
+        it 'is valid' do
+          expect(stage).to be_valid
+        end
+      end
+    end
+
+    context 'when value has a wrong type' do
+      let(:config) { { test: true } }
+
+      it 'reports errors about wrong type' do
+        expect(stage.errors)
+          .to include 'stage config should be a string'
+      end
+    end
+  end
+
+  describe '.default' do
+    it 'returns default stage' do
+      expect(described_class.default).to eq 'test'
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/trigger_spec.rb b/spec/lib/gitlab/ci/config/node/trigger_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a4a3e36754ebf92c4bf04cbce670d7a8c0abdafb
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/trigger_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Trigger do
+  let(:entry) { described_class.new(config) }
+
+  describe 'validations' do
+    context 'when entry config value is valid' do
+      context 'when config is a branch or tag name' do
+        let(:config) { %w[master feature/branch] }
+
+        describe '#valid?' do
+          it 'is valid' do
+            expect(entry).to be_valid
+          end
+        end
+
+        describe '#value' do
+          it 'returns key value' do
+            expect(entry.value).to eq config
+          end
+        end
+      end
+
+      context 'when config is a regexp' do
+        let(:config) { ['/^issue-.*$/'] }
+
+        describe '#valid?' do
+          it 'is valid' do
+            expect(entry).to be_valid
+          end
+        end
+      end
+
+      context 'when config is a special keyword' do
+        let(:config) { %w[tags triggers branches] }
+
+        describe '#valid?' do
+          it 'is valid' do
+            expect(entry).to be_valid
+          end
+        end
+      end
+    end
+
+    context 'when entry value is not valid' do
+      let(:config) { [1] }
+
+      describe '#errors' do
+        it 'saves errors' do
+          expect(entry.errors)
+            .to include 'trigger config should be an array of strings or regexps'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 0c6608d906d6e2a59862757a5cf24e1d6cd3eb7f..2d43e1c1a9d6477f421d9710b0f666efacdba62d 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -2,39 +2,31 @@ require 'spec_helper'
 
 describe Gitlab::Ci::Config::Node::Undefined do
   let(:undefined) { described_class.new(entry) }
-  let(:entry) { Class.new }
-
-  describe '#leaf?' do
-    it 'is leaf node' do
-      expect(undefined).to be_leaf
-    end
-  end
+  let(:entry) { spy('Entry') }
 
   describe '#valid?' do
-    it 'is always valid' do
-      expect(undefined).to be_valid
+    it 'delegates method to entry' do
+      expect(undefined.valid).to eq entry
     end
   end
 
   describe '#errors' do
-    it 'is does not contain errors' do
-      expect(undefined.errors).to be_empty
+    it 'delegates method to entry' do
+      expect(undefined.errors).to eq entry
     end
   end
 
   describe '#value' do
-    before do
-      allow(entry).to receive(:default).and_return('some value')
-    end
-
-    it 'returns default value for entry' do
-      expect(undefined.value).to eq 'some value'
+    it 'delegates method to entry' do
+      expect(undefined.value).to eq entry
     end
   end
 
-  describe '#undefined?' do
-    it 'is not a defined entry' do
-      expect(undefined.defined?).to be false
+  describe '#specified?' do
+    it 'is always false' do
+      allow(entry).to receive(:specified?).and_return(true)
+
+      expect(undefined.specified?).to be false
     end
   end
 end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index 5f76b70c6f51d56b4af09afed00ced3e4eef5f0f..2aa5ae44f54ab2a32f5fbe8bd5ca5af65d667000 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -11,11 +11,51 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
   let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
   subject { described_class.new(diff_file) }
 
-  let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
-
   describe '#parallelize' do
     it 'should return an array of arrays containing the parsed diff' do
-      expect(subject.parallelize).to match_array(parallel_diff_result_array)
+      diff_lines = diff_file.highlighted_diff_lines
+      expected = [
+        # Unchanged lines
+        { left: diff_lines[0], right: diff_lines[0] },
+        { left: diff_lines[1], right: diff_lines[1] },
+        { left: diff_lines[2], right: diff_lines[2] },
+        { left: diff_lines[3], right: diff_lines[3] },
+        { left: diff_lines[4], right: diff_lines[5] },
+        { left: diff_lines[6], right: diff_lines[6] },
+        { left: diff_lines[7], right: diff_lines[7] },
+        { left: diff_lines[8], right: diff_lines[8] },
+
+        # Changed lines
+        { left: diff_lines[9], right: diff_lines[11] },
+        { left: diff_lines[10], right: diff_lines[12] },
+
+        # Added lines
+        { left: nil, right: diff_lines[13] },
+        { left: nil, right: diff_lines[14] },
+        { left: nil, right: diff_lines[15] },
+        { left: nil, right: diff_lines[16] },
+        { left: nil, right: diff_lines[17] },
+        { left: nil, right: diff_lines[18] },
+
+        # Unchanged lines
+        { left: diff_lines[19], right: diff_lines[19] },
+        { left: diff_lines[20], right: diff_lines[20] },
+        { left: diff_lines[21], right: diff_lines[21] },
+        { left: diff_lines[22], right: diff_lines[22] },
+        { left: diff_lines[23], right: diff_lines[23] },
+        { left: diff_lines[24], right: diff_lines[24] },
+        { left: diff_lines[25], right: diff_lines[25] },
+
+        # Added line
+        { left: nil, right: diff_lines[26] },
+
+        # Unchanged lines
+        { left: diff_lines[27], right: diff_lines[27] },
+        { left: diff_lines[28], right: diff_lines[28] },
+        { left: diff_lines[29], right: diff_lines[29] }
+      ]
+
+      expect(subject.parallelize).to eq(expected)
     end
   end
 end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index cf28628cb962fb1b66d61f2b0ffaf8623caff262..10537bea00830e3d641998277be1be7080d49d5f 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -338,4 +338,28 @@ describe Gitlab::Diff::Position, lib: true do
       end
     end
   end
+
+  describe "#to_json" do
+    let(:hash) do
+      {
+        old_path: "files/ruby/popen.rb",
+        new_path: "files/ruby/popen.rb",
+        old_line: nil,
+        new_line: 14,
+        base_sha: nil,
+        head_sha: nil,
+        start_sha: nil
+      }
+    end
+
+    let(:diff_position) { described_class.new(hash) }
+
+    it "returns the position as JSON" do
+      expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys)
+    end
+
+    it "works when nested under another hash" do
+      expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..93094cda776f03f3e7b31a629c8b59f20192fc5d
--- /dev/null
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::DowntimeCheck::Message do
+  describe '#to_s' do
+    it 'returns an ANSI formatted String for an offline migration' do
+      message = described_class.new('foo.rb', true, 'hello')
+
+      expect(message.to_s).to eq("[\e[32moffline\e[0m]: foo.rb: hello")
+    end
+
+    it 'returns an ANSI formatted String for an online migration' do
+      message = described_class.new('foo.rb')
+
+      expect(message.to_s).to eq("[\e[31monline\e[0m]: foo.rb")
+    end
+  end
+end
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..42d895e548e3aa97b948034d83a8148f46c77f4e
--- /dev/null
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -0,0 +1,113 @@
+require 'spec_helper'
+
+describe Gitlab::DowntimeCheck do
+  subject { described_class.new }
+  let(:path) { 'foo.rb' }
+
+  describe '#check' do
+    before do
+      expect(subject).to receive(:require).with(path)
+    end
+
+    context 'when a migration does not specify if downtime is required' do
+      it 'raises RuntimeError' do
+        expect(subject).to receive(:class_for_migration_file).
+          with(path).
+          and_return(Class.new)
+
+        expect { subject.check([path]) }.
+          to raise_error(RuntimeError, /it requires downtime/)
+      end
+    end
+
+    context 'when a migration requires downtime' do
+      context 'when no reason is specified' do
+        it 'raises RuntimeError' do
+          stub_const('TestMigration::DOWNTIME', true)
+
+          expect(subject).to receive(:class_for_migration_file).
+            with(path).
+            and_return(TestMigration)
+
+          expect { subject.check([path]) }.
+            to raise_error(RuntimeError, /no reason was given/)
+        end
+      end
+
+      context 'when a reason is specified' do
+        it 'returns an Array of messages' do
+          stub_const('TestMigration::DOWNTIME', true)
+          stub_const('TestMigration::DOWNTIME_REASON', 'foo')
+
+          expect(subject).to receive(:class_for_migration_file).
+            with(path).
+            and_return(TestMigration)
+
+          messages = subject.check([path])
+
+          expect(messages).to be_an_instance_of(Array)
+          expect(messages[0]).to be_an_instance_of(Gitlab::DowntimeCheck::Message)
+
+          message = messages[0]
+
+          expect(message.path).to eq(path)
+          expect(message.offline).to eq(true)
+          expect(message.reason).to eq('foo')
+        end
+      end
+    end
+  end
+
+  describe '#check_and_print' do
+    it 'checks the migrations and prints the results to STDOUT' do
+      stub_const('TestMigration::DOWNTIME', true)
+      stub_const('TestMigration::DOWNTIME_REASON', 'foo')
+
+      expect(subject).to receive(:require).with(path)
+
+      expect(subject).to receive(:class_for_migration_file).
+        with(path).
+        and_return(TestMigration)
+
+      expect(subject).to receive(:puts).with(an_instance_of(String))
+
+      subject.check_and_print([path])
+    end
+  end
+
+  describe '#class_for_migration_file' do
+    it 'returns the class for a migration file path' do
+      expect(subject.class_for_migration_file('123_string.rb')).to eq(String)
+    end
+  end
+
+  describe '#online?' do
+    it 'returns true when a migration can be performed online' do
+      stub_const('TestMigration::DOWNTIME', false)
+
+      expect(subject.online?(TestMigration)).to eq(true)
+    end
+
+    it 'returns false when a migration can not be performed online' do
+      stub_const('TestMigration::DOWNTIME', true)
+
+      expect(subject.online?(TestMigration)).to eq(false)
+    end
+  end
+
+  describe '#downtime_reason' do
+    context 'when a reason is defined' do
+      it 'returns the downtime reason' do
+        stub_const('TestMigration::DOWNTIME_REASON', 'hello')
+
+        expect(subject.downtime_reason(TestMigration)).to eq('hello')
+      end
+    end
+
+    context 'when a reason is not defined' do
+      it 'returns nil' do
+        expect(subject.downtime_reason(Class.new)).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 476a21bf996c298a5b5a291a9e477c802ccf0a1c..08b2577ecc45cdad13f6b6ac0bba30acbd9d11b4 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -11,7 +11,6 @@ describe Gitlab::Email::AttachmentUploader, lib: true do
       link = links.first
 
       expect(link).not_to be_nil
-      expect(link[:is_image]).to be_truthy
       expect(link[:alt]).to eq("bricks")
       expect(link[:url]).to include("bricks.png")
     end
diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19298e261e39866313a183cdc879d13a8b0bf614
--- /dev/null
+++ b/spec/lib/gitlab/email/email_shared_blocks.rb
@@ -0,0 +1,41 @@
+require 'gitlab/email/receiver'
+
+shared_context :email_shared_context do
+  let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
+  let(:receiver) { Gitlab::Email::Receiver.new(email_raw) }
+  let(:markdown) { "![image](uploads/image.png)" }
+
+  def setup_attachment
+    allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
+      [
+        {
+          url: "uploads/image.png",
+          alt: "image",
+          markdown: markdown
+        }
+      ]
+    )
+  end
+end
+
+shared_examples :email_shared_examples do
+  context "when the user could not be found" do
+    before do
+      user.destroy
+    end
+
+    it "raises a UserNotFoundError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+    end
+  end
+
+  context "when the user is not authorized to the project" do
+    before do
+      project.update_attribute(:visibility_level, Project::PRIVATE)
+    end
+
+    it "raises a ProjectNotFound" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e115315477822703498e05027098ed46dd17747e
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
+  include_context :email_shared_context
+  it_behaves_like :email_shared_examples
+
+  before do
+    stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+    stub_config_setting(host: 'localhost')
+  end
+
+  let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
+  let(:namespace) { create(:namespace, path: 'gitlabhq') }
+
+  let!(:project)  { create(:project, :public, namespace: namespace) }
+  let!(:user) do
+    create(
+      :user,
+      email: 'jake@adventuretime.ooo',
+      authentication_token: 'auth_token'
+    )
+  end
+
+  context "when everything is fine" do
+    it "creates a new issue" do
+      setup_attachment
+
+      expect { receiver.execute }.to change { project.issues.count }.by(1)
+      issue = project.issues.last
+
+      expect(issue.author).to eq(user)
+      expect(issue.title).to eq('New Issue by email')
+      expect(issue.description).to include('reply by email')
+      expect(issue.description).to include(markdown)
+    end
+
+    context "when the reply is blank" do
+      let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") }
+
+      it "creates a new issue" do
+        expect { receiver.execute }.to change { project.issues.count }.by(1)
+        issue = project.issues.last
+
+        expect(issue.author).to eq(user)
+        expect(issue.title).to eq('New Issue by email')
+        expect(issue.description).to eq('')
+      end
+    end
+  end
+
+  context "something is wrong" do
+    context "when the issue could not be saved" do
+      before do
+        allow_any_instance_of(Issue).to receive(:persisted?).and_return(false)
+      end
+
+      it "raises an InvalidIssueError" do
+        expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError)
+      end
+    end
+
+    context "when we can't find the authentication_token" do
+      let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") }
+
+      it "raises an UserNotFoundError" do
+        expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+      end
+    end
+
+    context "when project is private" do
+      let(:project) { create(:project, :private, namespace: namespace) }
+
+      it "raises a ProjectNotFound if the user is not a member" do
+        expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2119b0dadfb1619434cc6ffe1c60d33593c3a53
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
+  include_context :email_shared_context
+  it_behaves_like :email_shared_examples
+
+  before do
+    stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
+    stub_config_setting(host: 'localhost')
+  end
+
+  let(:email_raw) { fixture_file('emails/valid_reply.eml') }
+  let(:project)   { create(:project, :public) }
+  let(:noteable)  { create(:issue, project: project) }
+  let(:user)      { create(:user) }
+
+  let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+
+  context "when the recipient address doesn't include a mail key" do
+    let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
+
+    it "raises a UnknownIncomingEmail" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+    end
+  end
+
+  context "when no sent notification for the mail key could be found" do
+    let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') }
+
+    it "raises a SentNotificationNotFoundError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError)
+    end
+  end
+
+  context "when the email was auto generated" do
+    let!(:mail_key)  { '636ca428858779856c226bb145ef4fad' }
+    let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
+
+    it "raises an AutoGeneratedEmailError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
+    end
+  end
+
+  context "when the noteable could not be found" do
+    before do
+      noteable.destroy
+    end
+
+    it "raises a NoteableNotFoundError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError)
+    end
+  end
+
+  context "when the note could not be saved" do
+    before do
+      allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
+    end
+
+    it "raises an InvalidNoteError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
+    end
+  end
+
+  context "when the reply is blank" do
+    let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
+
+    it "raises an EmptyEmailError" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
+    end
+  end
+
+  context "when everything is fine" do
+    before do
+      setup_attachment
+    end
+
+    it "creates a comment" do
+      expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+      note = noteable.notes.last
+
+      expect(note.author).to eq(sent_notification.recipient)
+      expect(note.note).to include("I could not disagree more.")
+    end
+
+    it "adds all attachments" do
+      receiver.execute
+
+      note = noteable.notes.last
+
+      expect(note.note).to include(markdown)
+    end
+
+    context 'when sub-addressing is not supported' do
+      before do
+        stub_incoming_email_setting(enabled: true, address: nil)
+      end
+
+      shared_examples 'an email that contains a mail key' do |header|
+        it "fetches the mail key from the #{header} header and creates a comment" do
+          expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+          note = noteable.notes.last
+
+          expect(note.author).to eq(sent_notification.recipient)
+          expect(note.note).to include('I could not disagree more.')
+        end
+      end
+
+      context 'mail key is in the References header' do
+        let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
+
+        it_behaves_like 'an email that contains a mail key', 'References'
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 36267faeb93b66eb8966a9b75dec7aa6149a41a7..2a86b427806480fc0dcd3da01b328af85ba22dc4 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,34 +1,14 @@
-require "spec_helper"
+require 'spec_helper'
+require_relative 'email_shared_blocks'
 
 describe Gitlab::Email::Receiver, lib: true do
-  before do
-    stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
-    stub_config_setting(host: 'localhost')
-  end
-
-  let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
-  let(:email_raw) { fixture_file('emails/valid_reply.eml') }
-
-  let(:project)   { create(:project, :public) }
-  let(:noteable)  { create(:issue, project: project) }
-  let(:user)      { create(:user) }
-  let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) }
-
-  let(:receiver) { described_class.new(email_raw) }
-
-  context "when the recipient address doesn't include a reply key" do
-    let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") }
-
-    it "raises a SentNotificationNotFoundError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
-    end
-  end
+  include_context :email_shared_context
 
-  context "when no sent notificiation for the reply key could be found" do
-    let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') }
+  context "when we cannot find a capable handler" do
+    let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") }
 
-    it "raises a SentNotificationNotFoundError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
+    it "raises a UnknownIncomingEmail" do
+      expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
     end
   end
 
@@ -36,129 +16,7 @@ describe Gitlab::Email::Receiver, lib: true do
     let(:email_raw) { "" }
 
     it "raises an EmptyEmailError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
-    end
-  end
-
-  context "when the email was auto generated" do
-    let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
-    let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
-    it "raises an AutoGeneratedEmailError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
-    end
-  end
-
-  context "when the user could not be found" do
-    before do
-      user.destroy
-    end
-
-    it "raises a UserNotFoundError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError)
-    end
-  end
-
-  context "when the user has been blocked" do
-    before do
-      user.block
-    end
-
-    it "raises a UserBlockedError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError)
-    end
-  end
-
-  context "when the user is not authorized to create a note" do
-    before do
-      project.update_attribute(:visibility_level, Project::PRIVATE)
-    end
-
-    it "raises a UserNotAuthorizedError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError)
-    end
-  end
-
-  context "when the noteable could not be found" do
-    before do
-      noteable.destroy
-    end
-
-    it "raises a NoteableNotFoundError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError)
-    end
-  end
-
-  context "when the reply is blank" do
-    let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
-    it "raises an EmptyEmailError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
-    end
-  end
-
-  context "when the note could not be saved" do
-    before do
-      allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
-    end
-
-    it "raises an InvalidNoteError" do
-      expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError)
-    end
-  end
-
-  context "when everything is fine" do
-    let(:markdown) { "![image](uploads/image.png)" }
-
-    before do
-      allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
-        [
-          {
-            url: "uploads/image.png",
-            is_image: true,
-            alt: "image",
-            markdown: markdown
-          }
-        ]
-      )
-    end
-
-    it "creates a comment" do
-      expect { receiver.execute }.to change { noteable.notes.count }.by(1)
-      note = noteable.notes.last
-
-      expect(note.author).to eq(sent_notification.recipient)
-      expect(note.note).to include("I could not disagree more.")
-    end
-
-    it "adds all attachments" do
-      receiver.execute
-
-      note = noteable.notes.last
-
-      expect(note.note).to include(markdown)
-    end
-
-    context 'when sub-addressing is not supported' do
-      before do
-        stub_incoming_email_setting(enabled: true, address: nil)
-      end
-
-      shared_examples 'an email that contains a reply key' do |header|
-        it "fetches the reply key from the #{header} header and creates a comment" do
-          expect { receiver.execute }.to change { noteable.notes.count }.by(1)
-          note = noteable.notes.last
-
-          expect(note.author).to eq(sent_notification.recipient)
-          expect(note.note).to include('I could not disagree more.')
-        end
-      end
-
-      context 'reply key is in the References header' do
-        let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
-
-        it_behaves_like 'an email that contains a reply key', 'References'
-      end
+      expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
     end
   end
 end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index db33c7a22bbc9cabcfd67322dcd2c655996a7b6d..8447305a316df18f55d809a5b2a627236350684f 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -44,12 +44,12 @@ describe Gitlab::GitAccess, lib: true do
   end
 
   describe 'download_access_check' do
+    subject { access.check('git-upload-pack') }
+
     describe 'master permissions' do
       before { project.team << [user, :master] }
 
       context 'pull code' do
-        subject { access.download_access_check }
-
         it { expect(subject.allowed?).to be_truthy }
       end
     end
@@ -58,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do
       before { project.team << [user, :guest] }
 
       context 'pull code' do
-        subject { access.download_access_check }
-
         it { expect(subject.allowed?).to be_falsey }
       end
     end
@@ -71,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do
       end
 
       context 'pull code' do
-        subject { access.download_access_check }
-
         it { expect(subject.allowed?).to be_falsey }
       end
     end
 
     describe 'without acccess to project' do
       context 'pull code' do
-        subject { access.download_access_check }
-
         it { expect(subject.allowed?).to be_falsey }
       end
     end
@@ -90,10 +84,31 @@ describe Gitlab::GitAccess, lib: true do
       let(:actor) { key }
 
       context 'pull code' do
-        before { key.projects << project }
-        subject { access.download_access_check }
+        context 'when project is authorized' do
+          before { key.projects << project }
 
-        it { expect(subject.allowed?).to be_truthy }
+          it { expect(subject).to be_allowed }
+        end
+
+        context 'when unauthorized' do
+          context 'from public project' do
+            let(:project) { create(:project, :public) }
+
+            it { expect(subject).to be_allowed }
+          end
+
+          context 'from internal project' do
+            let(:project) { create(:project, :internal) }
+
+            it { expect(subject).not_to be_allowed }
+          end
+
+          context 'from private project' do
+            let(:project) { create(:project, :internal) }
+
+            it { expect(subject).not_to be_allowed }
+          end
+        end
       end
     end
   end
@@ -136,7 +151,13 @@ describe Gitlab::GitAccess, lib: true do
     def self.run_permission_checks(permissions_matrix)
       permissions_matrix.keys.each do |role|
         describe "#{role} access" do
-          before { project.team << [user, role] }
+          before do
+            if role == :admin
+              user.update_attribute(:admin, true)
+            else
+              project.team << [user, role]
+            end
+          end
 
           permissions_matrix[role].each do |action, allowed|
             context action do
@@ -150,6 +171,17 @@ describe Gitlab::GitAccess, lib: true do
     end
 
     permissions_matrix = {
+      admin: {
+        push_new_branch: true,
+        push_master: true,
+        push_protected_branch: true,
+        push_remove_protected_branch: false,
+        push_tag: true,
+        push_new_tag: true,
+        push_all: true,
+        merge_into_protected_branch: true
+      },
+
       master: {
         push_new_branch: true,
         push_master: true,
@@ -202,19 +234,20 @@ describe Gitlab::GitAccess, lib: true do
         run_permission_checks(permissions_matrix)
       end
 
-      context "when 'developers can push' is turned on for the #{protected_branch_type} protected branch" do
-        before { create(:protected_branch, name: protected_branch_name, developers_can_push: true, project: project) }
+      context "when developers are allowed to push into the #{protected_branch_type} protected branch" do
+        before { create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) }
 
         run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
       end
 
-      context "when 'developers can merge' is turned on for the #{protected_branch_type} protected branch" do
-        before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, project: project) }
+      context "developers are allowed to merge into the #{protected_branch_type} protected branch" do
+        before { create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) }
 
         context "when a merge request exists for the given source/target branch" do
           context "when the merge request is in progress" do
             before do
-              create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
+              create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature',
+                                     state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
             end
 
             run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true }))
@@ -227,18 +260,61 @@ describe Gitlab::GitAccess, lib: true do
 
             run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
           end
-        end
 
-        context "when a merge request does not exist for the given source/target branch" do
-          run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+          context "when a merge request does not exist for the given source/target branch" do
+            run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+          end
         end
       end
 
-      context "when 'developers can merge' and 'developers can push' are turned on for the #{protected_branch_type} protected branch" do
-        before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, developers_can_push: true, project: project) }
+      context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do
+        before { create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) }
 
         run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
       end
+
+      context "when no one is allowed to push to the #{protected_branch_name} protected branch" do
+        before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) }
+
+        run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
+                                                            master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
+                                                            admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
+      end
+    end
+  end
+
+  describe 'deploy key permissions' do
+    let(:key) { create(:deploy_key) }
+    let(:actor) { key }
+
+    context 'push code' do
+      subject { access.check('git-receive-pack') }
+
+      context 'when project is authorized' do
+        before { key.projects << project }
+
+        it { expect(subject).not_to be_allowed }
+      end
+
+      context 'when unauthorized' do
+        context 'to public project' do
+          let(:project) { create(:project, :public) }
+
+          it { expect(subject).not_to be_allowed }
+        end
+
+        context 'to internal project' do
+          let(:project) { create(:project, :internal) }
+
+          it { expect(subject).not_to be_allowed }
+        end
+
+        context 'to private project' do
+          let(:project) { create(:project, :internal) }
+
+          it { expect(subject).not_to be_allowed }
+        end
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 364532e94e30956eb37967dee45f6b85cdacc545..fc021416d92472f38def5cbe5c21fc39ab0958e7 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -17,6 +17,18 @@ describe Gitlab::Highlight, lib: true do
       expect(lines[21]).to eq(%Q{<span id="LC22" class="line">    <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
       expect(lines[26]).to eq(%Q{<span id="LC27" class="line">    <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
     end
+
+    describe 'with CRLF' do
+      let(:branch) { 'crlf-diff' }
+      let(:blob) { repository.blob_at_branch(branch, path) }
+      let(:lines) do
+        Gitlab::Highlight.highlight_lines(project.repository, 'crlf-diff', 'files/whitespace')
+      end
+
+      it 'strips extra LFs' do
+        expect(lines[0]).to eq("<span id=\"LC1\" class=\"line\">test  </span>")
+      end
+    end
   end
 
   describe 'custom highlighting from .gitattributes' do
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ae178414cc240f529a55e96e557b4c64956f61d
--- /dev/null
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AvatarRestorer, lib: true do
+  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+  let(:project) { create(:empty_project) }
+
+  before do
+    allow_any_instance_of(described_class).to receive(:avatar_export_file)
+                                                .and_return(Rails.root + "spec/fixtures/dk.png")
+  end
+
+  after do
+    project.remove_avatar!
+  end
+
+  it 'restores a project avatar' do
+    expect(described_class.new(project: project, shared: shared).restore).to be true
+  end
+
+  it 'saves the avatar into the project' do
+    described_class.new(project: project, shared: shared).restore
+
+    expect(project.reload.avatar.file.exists?).to be true
+  end
+end
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d6ee94442cbc7a6ef3a3fc5f6091fee15a886ddd
--- /dev/null
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AvatarSaver, lib: true do
+  let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+  let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+  let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+  let(:project) { create(:empty_project) }
+
+  before do
+    FileUtils.mkdir_p("#{shared.export_path}/avatar/")
+    allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+  end
+
+  after do
+    FileUtils.rm_rf("#{shared.export_path}/avatar")
+  end
+
+  it 'saves a project avatar' do
+    described_class.new(project: project_with_avatar, shared: shared).save
+
+    expect(File).to exist("#{shared.export_path}/avatar/dk.png")
+  end
+
+  it 'is fine not to have an avatar' do
+    expect(described_class.new(project: project, shared: shared).save).to be true
+  end
+end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 4113d829c3c60f04a244cb47f29b1439a14585dd..b5550ca196322a88e4271e13e24941e68fdb7b51 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -18,7 +18,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Aliquam enim illo et possimus.",
-      "milestone_id": 18,
       "state": "opened",
       "iid": 10,
       "updated_by_id": null,
@@ -27,6 +26,52 @@
       "due_date": null,
       "moved_to_id": null,
       "test_ee_field": "test",
+      "milestone": {
+        "id": 1,
+        "title": "v0.0",
+        "project_id": 8,
+        "description": "test milestone",
+        "due_date": null,
+        "created_at": "2016-06-14T15:02:04.415Z",
+        "updated_at": "2016-06-14T15:02:04.415Z",
+        "state": "active",
+        "iid": 1,
+        "events": [
+          {
+            "id": 487,
+            "target_type": "Milestone",
+            "target_id": 1,
+            "title": null,
+            "data": null,
+            "project_id": 46,
+            "created_at": "2016-06-14T15:02:04.418Z",
+            "updated_at": "2016-06-14T15:02:04.418Z",
+            "action": 1,
+            "author_id": 18
+          }
+        ]
+      },
+      "label_links": [
+        {
+          "id": 2,
+          "label_id": 2,
+          "target_id": 3,
+          "target_type": "Issue",
+          "created_at": "2016-07-22T08:57:02.840Z",
+          "updated_at": "2016-07-22T08:57:02.840Z",
+          "label": {
+            "id": 2,
+            "title": "test2",
+            "color": "#428bca",
+            "project_id": 8,
+            "created_at": "2016-07-22T08:55:44.161Z",
+            "updated_at": "2016-07-22T08:55:44.161Z",
+            "template": false,
+            "description": "",
+            "priority": null
+          }
+        }
+      ],
       "notes": [
         {
           "id": 351,
@@ -233,7 +278,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.",
-      "milestone_id": 16,
       "state": "opened",
       "iid": 9,
       "updated_by_id": null,
@@ -447,7 +491,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Ea recusandae neque autem tempora.",
-      "milestone_id": 16,
       "state": "closed",
       "iid": 8,
       "updated_by_id": null,
@@ -661,7 +704,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Maiores architecto quos in dolorem.",
-      "milestone_id": 17,
       "state": "opened",
       "iid": 7,
       "updated_by_id": null,
@@ -875,7 +917,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Ut aut ut et tenetur velit aut id modi.",
-      "milestone_id": 16,
       "state": "opened",
       "iid": 6,
       "updated_by_id": null,
@@ -1089,7 +1130,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Dicta nisi nihil non ipsa velit.",
-      "milestone_id": 20,
       "state": "closed",
       "iid": 5,
       "updated_by_id": null,
@@ -1303,7 +1343,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Ut et explicabo vel voluptatem consequuntur ut sed.",
-      "milestone_id": 19,
       "state": "closed",
       "iid": 4,
       "updated_by_id": null,
@@ -1517,7 +1556,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Non asperiores velit accusantium voluptate.",
-      "milestone_id": 18,
       "state": "closed",
       "iid": 3,
       "updated_by_id": null,
@@ -1731,7 +1769,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Molestiae corporis magnam et fugit aliquid nulla quia.",
-      "milestone_id": 17,
       "state": "closed",
       "iid": 2,
       "updated_by_id": null,
@@ -1945,7 +1982,6 @@
       "position": 0,
       "branch_name": null,
       "description": "Quod ad architecto qui est sed quia.",
-      "milestone_id": 20,
       "state": "closed",
       "iid": 1,
       "updated_by_id": null,
@@ -2259,117 +2295,6 @@
           "author_id": 25
         }
       ]
-    },
-    {
-      "id": 18,
-      "title": "v2.0",
-      "project_id": 5,
-      "description": "Error dolorem rerum aut nulla.",
-      "due_date": null,
-      "created_at": "2016-06-14T15:02:04.576Z",
-      "updated_at": "2016-06-14T15:02:04.576Z",
-      "state": "active",
-      "iid": 3,
-      "events": [
-        {
-          "id": 242,
-          "target_type": "Milestone",
-          "target_id": 18,
-          "title": null,
-          "data": null,
-          "project_id": 36,
-          "created_at": "2016-06-14T15:02:04.579Z",
-          "updated_at": "2016-06-14T15:02:04.579Z",
-          "action": 1,
-          "author_id": 1
-        },
-        {
-          "id": 58,
-          "target_type": "Milestone",
-          "target_id": 18,
-          "title": null,
-          "data": null,
-          "project_id": 5,
-          "created_at": "2016-06-14T15:02:04.579Z",
-          "updated_at": "2016-06-14T15:02:04.579Z",
-          "action": 1,
-          "author_id": 22
-        }
-      ]
-    },
-    {
-      "id": 17,
-      "title": "v1.0",
-      "project_id": 5,
-      "description": "Molestiae perspiciatis voluptates doloremque commodi veniam consequatur.",
-      "due_date": null,
-      "created_at": "2016-06-14T15:02:04.569Z",
-      "updated_at": "2016-06-14T15:02:04.569Z",
-      "state": "active",
-      "iid": 2,
-      "events": [
-        {
-          "id": 243,
-          "target_type": "Milestone",
-          "target_id": 17,
-          "title": null,
-          "data": null,
-          "project_id": 36,
-          "created_at": "2016-06-14T15:02:04.570Z",
-          "updated_at": "2016-06-14T15:02:04.570Z",
-          "action": 1,
-          "author_id": 1
-        },
-        {
-          "id": 57,
-          "target_type": "Milestone",
-          "target_id": 17,
-          "title": null,
-          "data": null,
-          "project_id": 5,
-          "created_at": "2016-06-14T15:02:04.570Z",
-          "updated_at": "2016-06-14T15:02:04.570Z",
-          "action": 1,
-          "author_id": 20
-        }
-      ]
-    },
-    {
-      "id": 16,
-      "title": "v0.0",
-      "project_id": 5,
-      "description": "Velit numquam et sed sit.",
-      "due_date": null,
-      "created_at": "2016-06-14T15:02:04.561Z",
-      "updated_at": "2016-06-14T15:02:04.561Z",
-      "state": "closed",
-      "iid": 1,
-      "events": [
-        {
-          "id": 244,
-          "target_type": "Milestone",
-          "target_id": 16,
-          "title": null,
-          "data": null,
-          "project_id": 36,
-          "created_at": "2016-06-14T15:02:04.563Z",
-          "updated_at": "2016-06-14T15:02:04.563Z",
-          "action": 1,
-          "author_id": 26
-        },
-        {
-          "id": 56,
-          "target_type": "Milestone",
-          "target_id": 16,
-          "title": null,
-          "data": null,
-          "project_id": 5,
-          "created_at": "2016-06-14T15:02:04.563Z",
-          "updated_at": "2016-06-14T15:02:04.563Z",
-          "action": 1,
-          "author_id": 26
-        }
-      ]
     }
   ],
   "snippets": [
@@ -2471,7 +2396,6 @@
       "title": "Cannot be automatically merged",
       "created_at": "2016-06-14T15:02:36.568Z",
       "updated_at": "2016-06-14T15:02:56.815Z",
-      "milestone_id": null,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -2765,7 +2689,7 @@
             "committer_email": "dmitriy.zaporozhets@gmail.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "Binary files a/.DS_Store and /dev/null differ\n",
             "new_path": ".DS_Store",
@@ -2909,7 +2833,6 @@
       "title": "Can be automatically merged",
       "created_at": "2016-06-14T15:02:36.418Z",
       "updated_at": "2016-06-14T15:02:57.013Z",
-      "milestone_id": null,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -3138,7 +3061,7 @@
             "committer_email": "dmitriy.zaporozhets@gmail.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,5 @@\n+class Feature\n+  def foo\n+    puts 'bar'\n+  end\n+end\n",
             "new_path": "files/ruby/feature.rb",
@@ -3194,7 +3117,6 @@
       "title": "Qui accusantium et inventore facilis doloribus occaecati officiis.",
       "created_at": "2016-06-14T15:02:25.168Z",
       "updated_at": "2016-06-14T15:02:59.521Z",
-      "milestone_id": 17,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -3423,7 +3345,7 @@
             "committer_email": "james@jameslopez.es"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "--- /dev/null\n+++ b/test\n",
             "new_path": "test",
@@ -3479,7 +3401,6 @@
       "title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.",
       "created_at": "2016-06-14T15:02:24.760Z",
       "updated_at": "2016-06-14T15:02:59.749Z",
-      "milestone_id": 20,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -3960,7 +3881,7 @@
             "committer_email": "dmitriy.zaporozhets@gmail.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "Binary files a/.DS_Store and /dev/null differ\n",
             "new_path": ".DS_Store",
@@ -4170,7 +4091,6 @@
       "title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.",
       "created_at": "2016-06-14T15:02:24.415Z",
       "updated_at": "2016-06-14T15:02:59.958Z",
-      "milestone_id": 17,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -4597,7 +4517,7 @@
             "committer_email": "marmis85@gmail.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n   - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n   - Add edit file button to MergeRequest diff\n   - Public groups (Jason Hollingsworth)\n",
             "new_path": "CHANGELOG",
@@ -4719,7 +4639,6 @@
       "title": "In a rerum harum nihil accusamus aut quia nobis non.",
       "created_at": "2016-06-14T15:02:24.000Z",
       "updated_at": "2016-06-14T15:03:00.225Z",
-      "milestone_id": 19,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -5108,7 +5027,7 @@
             "committer_email": "stanhu@packetzoom.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "--- a/CHANGELOG\n+++ b/CHANGELOG\n@@ -1,4 +1,6 @@\n-v 6.7.0\n+v6.8.0\n+\n+v6.7.0\n   - Add support for Gemnasium as a Project Service (Olivier Gonzalez)\n   - Add edit file button to MergeRequest diff\n   - Public groups (Jason Hollingsworth)\n",
             "new_path": "CHANGELOG",
@@ -5219,7 +5138,6 @@
       "title": "Corporis provident similique perspiciatis dolores eos animi.",
       "created_at": "2016-06-14T15:02:23.767Z",
       "updated_at": "2016-06-14T15:03:00.475Z",
-      "milestone_id": 18,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -5434,7 +5352,7 @@
         "id": 11,
         "state": "empty",
         "st_commits": null,
-        "st_diffs": [
+        "utf8_st_diffs": [
 
         ],
         "merge_request_id": 11,
@@ -5480,7 +5398,6 @@
       "title": "Eligendi reprehenderit doloribus quia et sit id.",
       "created_at": "2016-06-14T15:02:23.014Z",
       "updated_at": "2016-06-14T15:03:00.685Z",
-      "milestone_id": 20,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -5961,7 +5878,7 @@
             "committer_email": "dmitriy.zaporozhets@gmail.com"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "Binary files a/.DS_Store and /dev/null differ\n",
             "new_path": ".DS_Store",
@@ -6171,7 +6088,6 @@
       "title": "Et ipsam voluptas velit sequi illum ut.",
       "created_at": "2016-06-14T15:02:22.825Z",
       "updated_at": "2016-06-14T15:03:00.904Z",
-      "milestone_id": 16,
       "state": "opened",
       "merge_status": "unchecked",
       "target_project_id": 5,
@@ -6400,7 +6316,7 @@
             "committer_email": "james@jameslopez.es"
           }
         ],
-        "st_diffs": [
+        "utf8_st_diffs": [
           {
             "diff": "--- /dev/null\n+++ b/test\n",
             "new_path": "test",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 877be300262ae4289e1fbc6969ab2eb3d0a81e4e..32c0d6462f14917758dbb71bfb356de5ccac0756 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
 
 describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
   describe 'restore project tree' do
+
     let(:user) { create(:user) }
     let(:namespace) { create(:namespace, owner: user) }
     let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
@@ -53,6 +54,24 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
           expect(event.note.noteable.project).not_to be_nil
         end
       end
+
+      it 'has the correct data for merge request st_diffs' do
+        # makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
+
+        expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
+      end
+
+      it 'has labels associated to label links, associated to issues' do
+        restored_project_json
+
+        expect(Label.first.label_links.first.target).not_to be_nil
+      end
+
+      it 'has milestones associated to issues' do
+        restored_project_json
+
+        expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
+      end
     end
   end
 end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 1424de9e60be25b17d483da5100982db6e7e5fef..3a86a4ce07c826aea84bb60cefca3a5c3552a78f 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -31,10 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
         expect(saved_project_json).to include({ "visibility_level" => 20 })
       end
 
-      it 'has events' do
-        expect(saved_project_json['milestones'].first['events']).not_to be_empty
-      end
-
       it 'has milestones' do
         expect(saved_project_json['milestones']).not_to be_empty
       end
@@ -43,8 +39,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
         expect(saved_project_json['merge_requests']).not_to be_empty
       end
 
-      it 'has labels' do
-        expect(saved_project_json['labels']).not_to be_empty
+      it 'has merge request\'s milestones' do
+        expect(saved_project_json['merge_requests'].first['milestone']).not_to be_empty
+      end
+
+      it 'has events' do
+        expect(saved_project_json['merge_requests'].first['milestone']['events']).not_to be_empty
       end
 
       it 'has snippets' do
@@ -102,25 +102,38 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
       it 'has ci pipeline notes' do
         expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
       end
+
+      it 'has labels with no associations' do
+        expect(saved_project_json['labels']).not_to be_empty
+      end
+
+      it 'has labels associated to records' do
+        expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
+      end
+
+      it 'does not complain about non UTF-8 characters in MR diffs' do
+        ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n    LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n    KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n    YXR'")
+
+        expect(project_tree_saver.save).to be true
+      end
     end
   end
 
   def setup_project
     issue = create(:issue, assignee: user)
-    merge_request = create(:merge_request)
-    label = create(:label)
     snippet = create(:project_snippet)
     release = create(:release)
 
     project = create(:project,
                      :public,
                      issues: [issue],
-                     merge_requests: [merge_request],
-                     labels: [label],
                      snippets: [snippet],
                      releases: [release]
                     )
-
+    label = create(:label, project: project)
+    create(:label_link, label: label, target: issue)
+    milestone = create(:milestone, project: project)
+    merge_request = create(:merge_request, source_project: project, milestone: milestone)
     commit_status = create(:commit_status, project: project)
 
     ci_pipeline = create(:ci_pipeline,
@@ -130,7 +143,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
                        statuses: [commit_status])
 
     create(:ci_build, pipeline: ci_pipeline, project: project)
-    milestone = create(:milestone, project: project)
+    create(:milestone, project: project)
     create(:note, noteable: issue, project: project)
     create(:note, noteable: merge_request, project: project)
     create(:note, noteable: snippet, project: project)
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index afb3e26f8fbbf15db3556a8fc41febebf3df970d..1dcf2c0668b75f3e748a868c83022be5b65fc7af 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do
     end
   end
 
-  context 'self.key_from_fallback_reply_message_id' do
+  context 'self.key_from_fallback_message_id' do
     it 'returns reply key' do
-      expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key')
+      expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key')
     end
   end
 end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 8809b7e3f1201aae071954568a94d0576bcd837e..d88bcae41fb854e6eb278c630b3944f3631064aa 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do
     allow(@dummy).to receive(:name).and_return('Dummy')
   end
 
+  describe '.series' do
+    it 'returns a String' do
+      expect(described_class.series).to be_an_instance_of(String)
+    end
+  end
+
   describe '.configure' do
     it 'yields self' do
       described_class.configure do |c|
@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do
         allow(described_class).to receive(:transaction).
           and_return(transaction)
 
-        expect(transaction).to receive(:measure_method).
-          with('Dummy.foo')
+        expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
 
         @dummy.foo
       end
@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do
         allow(described_class).to receive(:transaction).
           and_return(transaction)
 
-        expect(transaction).to receive(:measure_method).
-          with('Dummy#bar')
+        expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
 
         @dummy.new.bar
       end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index cf0e282c2fb17391368e6f0ebcbd3999ce8e277a..9e2ea89a712a0733b77b6e39a266efcad695637f 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do
   end
 
   describe '.cpu_time' do
-    it 'returns a Float' do
-      expect(described_class.cpu_time).to be_an_instance_of(Float)
+    it 'returns a Fixnum' do
+      expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
     end
   end
 
   describe '.real_time' do
-    it 'returns a Float' do
-      expect(described_class.real_time).to be_an_instance_of(Float)
+    it 'returns a Fixnum' do
+      expect(described_class.real_time).to be_an_instance_of(Fixnum)
     end
   end
 
   describe '.monotonic_time' do
-    it 'returns a Float' do
-      expect(described_class.monotonic_time).to be_an_instance_of(Float)
+    it 'returns a Fixnum' do
+      expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
     end
   end
 end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 3b1c67a21478d41ee6d1ebcd071b658b1561b09b..f1a191d94100889466524e3a8a81e9e052a6e590 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do
     end
   end
 
-  describe '#measure_method' do
-    it 'adds a new method if it does not exist already' do
-      transaction.measure_method('Foo#bar') { 'foo' }
+  describe '#method_call_for' do
+    it 'returns a MethodCall' do
+      method = transaction.method_call_for('Foo#bar')
 
-      expect(transaction.methods['Foo#bar']).
-        to be_an_instance_of(Gitlab::Metrics::MethodCall)
-    end
-
-    it 'adds timings to an existing method call' do
-      transaction.measure_method('Foo#bar') { 'foo' }
-      transaction.measure_method('Foo#bar') { 'foo' }
-
-      expect(transaction.methods['Foo#bar'].call_count).to eq(2)
+      expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
     end
   end
 
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 96f7eabbca650d1e1824bab65fb867d8a971d95b..84f9475a0f85fbe66d58c8adce93d0eb7edd04f7 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -147,4 +147,10 @@ describe Gitlab::Metrics do
       end
     end
   end
+
+  describe '#series_prefix' do
+    it 'returns a String' do
+      expect(described_class.series_prefix).to be_an_instance_of(String)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index aa9ec243498bd9d706ec135fb7aa6afee2f26fa1..5bb095366fa1d7cc2650d4f3b90ad1e498a6e2ff 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::UserAccess, lib: true do
 
     describe 'push to protected branch if allowed for developers' do
       before do
-        @branch = create :protected_branch, project: project, developers_can_push: true
+        @branch = create :protected_branch, :developers_can_push, project: project
       end
 
       it 'returns true if user is a master' do
@@ -65,7 +65,7 @@ describe Gitlab::UserAccess, lib: true do
 
     describe 'merge to protected branch if allowed for developers' do
       before do
-        @branch = create :protected_branch, project: project, developers_can_merge: true
+        @branch = create :protected_branch, :developers_can_merge, project: project
       end
 
       it 'returns true if user is a master' do
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index 63b5292b098e91034e6ace12cdea9b7fdd1bf911..f227926f39c83bbddcc8fb111bf958685fa35195 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,33 +1,34 @@
-require_relative '../../lib/repository_cache'
+require 'spec_helper'
 
 describe RepositoryCache, lib: true do
+  let(:project) { create(:project) }
   let(:backend) { double('backend').as_null_object }
-  let(:cache) { RepositoryCache.new('example', backend) }
+  let(:cache) { RepositoryCache.new('example', project.id, backend) }
 
   describe '#cache_key' do
     it 'includes the namespace' do
-      expect(cache.cache_key(:foo)).to eq 'foo:example'
+      expect(cache.cache_key(:foo)).to eq "foo:example:#{project.id}"
     end
   end
 
   describe '#expire' do
     it 'expires the given key from the cache' do
       cache.expire(:foo)
-      expect(backend).to have_received(:delete).with('foo:example')
+      expect(backend).to have_received(:delete).with("foo:example:#{project.id}")
     end
   end
 
   describe '#fetch' do
     it 'fetches the given key from the cache' do
       cache.fetch(:bar)
-      expect(backend).to have_received(:fetch).with('bar:example')
+      expect(backend).to have_received(:fetch).with("bar:example:#{project.id}")
     end
 
     it 'accepts a block' do
       p = -> {}
 
       cache.fetch(:baz, &p)
-      expect(backend).to have_received(:fetch).with('baz:example', &p)
+      expect(backend).to have_received(:fetch).with("baz:example:#{project.id}", &p)
     end
   end
 end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 0a9b10bebeabb49d55df432210186fd49fdae6f9..3685b2b17b55f4cd6cedbd25d1b6b07917a6a751 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -12,7 +12,7 @@ describe Notify do
   context 'for a project' do
     describe 'items that are assignable, the email' do
       let(:current_user) { create(:user, email: "current@email.com") }
-      let(:assignee) { create(:user, email: 'assignee@example.com') }
+      let(:assignee) { create(:user, email: 'assignee@example.com', name: 'John Doe') }
       let(:previous_assignee) { create(:user, name: 'Previous Assignee') }
 
       shared_examples 'an assignee email' do
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 1acb5846fcf9028026ddb5104637bfb58fc791e7..853f6943cef87546ed3c7099b83501b49b8774a8 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,6 +1,62 @@
 require 'spec_helper'
 
 describe Ability, lib: true do
+  describe '.can_edit_note?' do
+    let(:project) { create(:empty_project) }
+    let!(:note) { create(:note_on_issue, project: project) }
+
+    context 'using an anonymous user' do
+      it 'returns false' do
+        expect(described_class.can_edit_note?(nil, note)).to be_falsy
+      end
+    end
+
+    context 'using a system note' do
+      it 'returns false' do
+        system_note = create(:note, system: true)
+        user = create(:user)
+
+        expect(described_class.can_edit_note?(user, system_note)).to be_falsy
+      end
+    end
+
+    context 'using users with different access levels' do
+      let(:user) { create(:user) }
+
+      it 'returns true for the author' do
+        expect(described_class.can_edit_note?(note.author, note)).to be_truthy
+      end
+
+      it 'returns false for a guest user' do
+        project.team << [user, :guest]
+
+        expect(described_class.can_edit_note?(user, note)).to be_falsy
+      end
+
+      it 'returns false for a developer' do
+        project.team << [user, :developer]
+
+        expect(described_class.can_edit_note?(user, note)).to be_falsy
+      end
+
+      it 'returns true for a master' do
+        project.team << [user, :master]
+
+        expect(described_class.can_edit_note?(user, note)).to be_truthy
+      end
+
+      it 'returns true for a group owner' do
+        group = create(:group)
+        project.project_group_links.create(
+          group: group,
+          group_access: Gitlab::Access::MASTER)
+        group.add_owner(user)
+
+        expect(described_class.can_edit_note?(user, note)).to be_truthy
+      end
+    end
+  end
+
   describe '.users_that_can_read_project' do
     context 'using a public project' do
       it 'returns all the users' do
@@ -114,4 +170,52 @@ describe Ability, lib: true do
       end
     end
   end
+
+  describe '.issues_readable_by_user' do
+    context 'with an admin user' do
+      it 'returns all given issues' do
+        user = build(:user, admin: true)
+        issue = build(:issue)
+
+        expect(described_class.issues_readable_by_user([issue], user)).
+          to eq([issue])
+      end
+    end
+
+    context 'with a regular user' do
+      it 'returns the issues readable by the user' do
+        user = build(:user)
+        issue = build(:issue)
+
+        expect(issue).to receive(:readable_by?).with(user).and_return(true)
+
+        expect(described_class.issues_readable_by_user([issue], user)).
+          to eq([issue])
+      end
+
+      it 'returns an empty Array when no issues are readable' do
+        user = build(:user)
+        issue = build(:issue)
+
+        expect(issue).to receive(:readable_by?).with(user).and_return(false)
+
+        expect(described_class.issues_readable_by_user([issue], user)).to eq([])
+      end
+    end
+
+    context 'without a regular user' do
+      it 'returns issues that are publicly visible' do
+        hidden_issue = build(:issue)
+        visible_issue = build(:issue)
+
+        expect(hidden_issue).to receive(:publicly_visible?).and_return(false)
+        expect(visible_issue).to receive(:publicly_visible?).and_return(true)
+
+        issues = described_class.
+          issues_readable_by_user([hidden_issue, visible_issue])
+
+        expect(issues).to eq([visible_issue])
+      end
+    end
+  end
 end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 2ea1320267c34927b0aa4d808da01bb92d3b49c1..fb040ba82bc729a2956037fb5272723f4257768b 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -54,23 +54,60 @@ describe ApplicationSetting, models: true do
 
   context 'restricted signup domains' do
     it 'set single domain' do
-      setting.restricted_signup_domains_raw = 'example.com'
-      expect(setting.restricted_signup_domains).to eq(['example.com'])
+      setting.domain_whitelist_raw = 'example.com'
+      expect(setting.domain_whitelist).to eq(['example.com'])
     end
 
     it 'set multiple domains with spaces' do
-      setting.restricted_signup_domains_raw = 'example.com *.example.com'
-      expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
+      setting.domain_whitelist_raw = 'example.com *.example.com'
+      expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
     end
 
     it 'set multiple domains with newlines and a space' do
-      setting.restricted_signup_domains_raw = "example.com\n *.example.com"
-      expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
+      setting.domain_whitelist_raw = "example.com\n *.example.com"
+      expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
     end
 
     it 'set multiple domains with commas' do
-      setting.restricted_signup_domains_raw = "example.com, *.example.com"
-      expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
+      setting.domain_whitelist_raw = "example.com, *.example.com"
+      expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+    end
+  end
+
+  context 'blacklisted signup domains' do
+    it 'set single domain' do
+      setting.domain_blacklist_raw = 'example.com'
+      expect(setting.domain_blacklist).to contain_exactly('example.com')
+    end
+
+    it 'set multiple domains with spaces' do
+      setting.domain_blacklist_raw = 'example.com *.example.com'
+      expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+    end
+
+    it 'set multiple domains with newlines and a space' do
+      setting.domain_blacklist_raw = "example.com\n *.example.com"
+      expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+    end
+
+    it 'set multiple domains with commas' do
+      setting.domain_blacklist_raw = "example.com, *.example.com"
+      expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+    end
+
+    it 'set multiple domains with semicolon' do
+      setting.domain_blacklist_raw = "example.com; *.example.com"
+      expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+    end
+
+    it 'set multiple domains with mixture of everything' do
+      setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com   yes.com"
+      expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+    end
+
+    it 'set multiple domain with file' do
+      setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
+      expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
     end
   end
 end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 78e95c8fac51cd4075e9f82e09b38788b904a385..1e5d6a34f83d7a4d631ca65da3a736278ec7a832 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -33,6 +33,22 @@ describe Blob do
     end
   end
 
+  describe '#video?' do
+    it 'is falsey with image extension' do
+      git_blob = Gitlab::Git::Blob.new(name: 'image.png')
+
+      expect(described_class.decorate(git_blob)).not_to be_video
+    end
+
+    UploaderHelper::VIDEO_EXT.each do |ext|
+      it "is truthy when extension is .#{ext}" do
+        git_blob = Gitlab::Git::Blob.new(name: "video.#{ext}")
+
+        expect(described_class.decorate(git_blob)).to be_video
+      end
+    end
+  end
+
   describe '#to_partial_path' do
     def stubbed_blob(overrides = {})
       overrides.reverse_merge!(
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 481416319dd9cd01ff8607045578839223679c09..dc88697199b3eb405d2600e107715835369dead8 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -5,7 +5,9 @@ describe Ci::Build, models: true do
 
   let(:pipeline) do
     create(:ci_pipeline, project: project,
-                         sha: project.commit.id)
+                         sha: project.commit.id,
+                         ref: project.default_branch,
+                         status: 'success')
   end
 
   let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -191,77 +193,185 @@ describe Ci::Build, models: true do
   end
 
   describe '#variables' do
+    let(:container_registry_enabled) { false }
+    let(:predefined_variables) do
+      [
+        { key: 'CI', value: 'true', public: true },
+        { key: 'GITLAB_CI', value: 'true', public: true },
+        { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+        { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+        { key: 'CI_BUILD_REF', value: build.sha, public: true },
+        { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
+        { key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
+        { key: 'CI_BUILD_NAME', value: 'test', public: true },
+        { key: 'CI_BUILD_STAGE', value: 'test', public: true },
+        { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
+        { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
+        { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+        { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
+        { key: 'CI_PROJECT_NAME', value: project.path, public: true },
+        { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
+        { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
+        { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
+        { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
+      ]
+    end
+
+    before do
+      stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
+    end
+
+    subject { build.variables }
+
     context 'returns variables' do
-      subject { build.variables }
+      before do
+        build.yaml_variables = []
+      end
 
-      let(:predefined_variables) do
-        [
-          { key: :CI_BUILD_NAME, value: 'test', public: true },
-          { key: :CI_BUILD_STAGE, value: 'stage', public: true },
-        ]
+      it { is_expected.to eq(predefined_variables) }
+    end
+
+    context 'when build is for tag' do
+      let(:tag_variable) do
+        { key: 'CI_BUILD_TAG', value: 'master', public: true }
       end
 
-      let(:yaml_variables) do
-        [
-          { key: :DB_NAME, value: 'postgres', public: true }
-        ]
+      before do
+        build.update_attributes(tag: true)
+      end
+
+      it { is_expected.to include(tag_variable) }
+    end
+
+    context 'when secure variable is defined' do
+      let(:secure_variable) do
+        { key: 'SECRET_KEY', value: 'secret_value', public: false }
       end
 
       before do
-        build.update_attributes(stage: 'stage', yaml_variables: yaml_variables)
+        build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
       end
 
-      it { is_expected.to eq(predefined_variables + yaml_variables) }
+      it { is_expected.to include(secure_variable) }
+    end
 
-      context 'for tag' do
-        let(:tag_variable) do
-          [
-            { key: :CI_BUILD_TAG, value: 'master', public: true }
-          ]
-        end
+    context 'when build is for triggers' do
+      let(:trigger) { create(:ci_trigger, project: project) }
+      let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
+      let(:user_trigger_variable) do
+        { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
+      end
+      let(:predefined_trigger_variable) do
+        { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
+      end
 
-        before do
-          build.update_attributes(tag: true)
-        end
+      before do
+        build.trigger_request = trigger_request
+      end
 
-        it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
+      it { is_expected.to include(user_trigger_variable) }
+      it { is_expected.to include(predefined_trigger_variable) }
+    end
+
+    context 'when yaml_variables are undefined' do
+      before do
+        build.yaml_variables = nil
       end
 
-      context 'and secure variables' do
-        let(:secure_variables) do
-          [
-            { key: 'SECRET_KEY', value: 'secret_value', public: false }
-          ]
+      context 'use from gitlab-ci.yml' do
+        before do
+          stub_ci_pipeline_yaml_file(config)
         end
 
-        before do
-          build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+        context 'if config is not found' do
+          let(:config) { nil }
+
+          it { is_expected.to eq(predefined_variables) }
         end
 
-        it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
+        context 'if config does not have a questioned job' do
+          let(:config) do
+            YAML.dump({
+              test_other: {
+                script: 'Hello World'
+              }
+            })
+          end
+
+          it { is_expected.to eq(predefined_variables) }
+        end
 
-        context 'and trigger variables' do
-          let(:trigger) { create(:ci_trigger, project: project) }
-          let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
-          let(:trigger_variables) do
-            [
-              { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
-            ]
+        context 'if config has variables' do
+          let(:config) do
+            YAML.dump({
+              test: {
+                script: 'Hello World',
+                variables: {
+                  KEY: 'value'
+                }
+              }
+            })
           end
-          let(:predefined_trigger_variable) do
-            [
-              { key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
-            ]
+          let(:variables) do
+            [{ key: :KEY, value: 'value', public: true }]
           end
 
-          before do
-            build.trigger_request = trigger_request
-          end
+          it { is_expected.to eq(predefined_variables + variables) }
+        end
+      end
+    end
 
-          it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
+    context 'when container registry is enabled' do
+      let(:container_registry_enabled) { true }
+      let(:ci_registry) do
+        { key: 'CI_REGISTRY',  value: 'registry.example.com',  public: true }
+      end
+      let(:ci_registry_image) do
+        { key: 'CI_REGISTRY_IMAGE',  value: project.container_registry_repository_url, public: true }
+      end
+
+      context 'and is disabled for project' do
+        before do
+          project.update(container_registry_enabled: false)
         end
+
+        it { is_expected.to include(ci_registry) }
+        it { is_expected.not_to include(ci_registry_image) }
+      end
+
+      context 'and is enabled for project' do
+        before do
+          project.update(container_registry_enabled: true)
+        end
+
+        it { is_expected.to include(ci_registry) }
+        it { is_expected.to include(ci_registry_image) }
       end
     end
+
+    context 'when runner is assigned to build' do
+      let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
+
+      before do
+        build.update(runner: runner)
+      end
+
+      it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
+      it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
+      it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
+    end
+
+    context 'returns variables in valid order' do
+      before do
+        allow(build).to receive(:predefined_variables) { ['predefined'] }
+        allow(project).to receive(:predefined_variables) { ['project'] }
+        allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
+        allow(build).to receive(:yaml_variables) { ['yaml'] }
+        allow(project).to receive(:secret_variables) { ['secret'] }
+      end
+
+      it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
+    end
   end
 
   describe '#has_tags?' do
@@ -612,7 +722,7 @@ describe Ci::Build, models: true do
 
       describe '#erasable?' do
         subject { build.erasable? }
-        it { is_expected.to eq true }
+        it { is_expected.to be_truthy }
       end
 
       describe '#erased?' do
@@ -620,7 +730,7 @@ describe Ci::Build, models: true do
         subject { build.erased? }
 
         context 'build has not been erased' do
-          it { is_expected.to be false }
+          it { is_expected.to be_falsey }
         end
 
         context 'build has been erased' do
@@ -628,12 +738,13 @@ describe Ci::Build, models: true do
             build.erase
           end
 
-          it { is_expected.to be true }
+          it { is_expected.to be_truthy }
         end
       end
 
       context 'metadata and build trace are not available' do
         let!(:build) { create(:ci_build, :success, :artifacts) }
+
         before do
           build.remove_artifacts_metadata!
         end
@@ -653,6 +764,138 @@ describe Ci::Build, models: true do
     end
   end
 
+  describe '#retryable?' do
+    context 'when build is running' do
+      before do
+        build.run!
+      end
+
+      it { expect(build).not_to be_retryable }
+    end
+
+    context 'when build is finished' do
+      before do
+        build.success!
+      end
+
+      it { expect(build).to be_retryable }
+    end
+  end
+
+  describe '#manual?' do
+    before do
+      build.update(when: value)
+    end
+
+    subject { build.manual? }
+
+    context 'when is set to manual' do
+      let(:value) { 'manual' }
+
+      it { is_expected.to be_truthy }
+    end
+
+    context 'when set to something else' do
+      let(:value) { 'something else' }
+
+      it { is_expected.to be_falsey }
+    end
+  end
+
+  describe '#other_actions' do
+    let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+    let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
+
+    subject { build.other_actions }
+
+    it 'returns other actions' do
+      is_expected.to contain_exactly(other_build)
+    end
+
+    context 'when build is retried' do
+      let!(:new_build) { Ci::Build.retry(build) }
+
+      it 'does not return any of them' do
+        is_expected.not_to include(build, new_build)
+      end
+    end
+
+    context 'when other build is retried' do
+      let!(:retried_build) { Ci::Build.retry(other_build) }
+
+      it 'returns a retried build' do
+        is_expected.to contain_exactly(retried_build)
+      end
+    end
+  end
+
+  describe '#play' do
+    let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+
+    subject { build.play }
+
+    it 'enques a build' do
+      is_expected.to be_pending
+      is_expected.to eq(build)
+    end
+
+    context 'for success build' do
+      before { build.queue }
+
+      it 'creates a new build' do
+        is_expected.to be_pending
+        is_expected.not_to eq(build)
+      end
+    end
+  end
+
+  describe '#when' do
+    subject { build.when }
+
+    context 'if is undefined' do
+      before do
+        build.when = nil
+      end
+
+      context 'use from gitlab-ci.yml' do
+        before do
+          stub_ci_pipeline_yaml_file(config)
+        end
+
+        context 'if config is not found' do
+          let(:config) { nil }
+
+          it { is_expected.to eq('on_success') }
+        end
+
+        context 'if config does not have a questioned job' do
+          let(:config) do
+            YAML.dump({
+                        test_other: {
+                          script: 'Hello World'
+                        }
+                      })
+          end
+
+          it { is_expected.to eq('on_success') }
+        end
+
+        context 'if config has when' do
+          let(:config) do
+            YAML.dump({
+                        test: {
+                          script: 'Hello World',
+                          when: 'always'
+                        }
+                      })
+          end
+
+          it { is_expected.to eq('always') }
+        end
+      end
+    end
+  end
+
   describe '#retryable?' do
     context 'when build is running' do
       before { build.run! }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 10db79bd15fd49d06de9474bb1962b520ea81cab..0d4c86955ceb37c67ac07e29ada97e5d7e887c79 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -260,6 +260,70 @@ describe Ci::Pipeline, models: true do
           expect(pipeline.reload.status).to eq('canceled')
         end
       end
+
+      context 'when listing manual actions' do
+        let(:yaml) do
+          {
+            stages: ["build", "test", "staging", "production", "cleanup"],
+            build: {
+              stage: "build",
+              script: "BUILD",
+            },
+            test: {
+              stage: "test",
+              script: "TEST",
+            },
+            staging: {
+              stage: "staging",
+              script: "PUBLISH",
+            },
+            production: {
+              stage: "production",
+              script: "PUBLISH",
+              when: "manual",
+            },
+            cleanup: {
+              stage: "cleanup",
+              script: "TIDY UP",
+              when: "always",
+            },
+            clear_cache: {
+              stage: "cleanup",
+              script: "CLEAR CACHE",
+              when: "manual",
+            }
+          }
+        end
+
+        it 'returns only for skipped builds' do
+          # currently all builds are created
+          expect(create_builds).to be_truthy
+          expect(manual_actions).to be_empty
+
+          # succeed stage build
+          pipeline.builds.running_or_pending.each(&:success)
+          expect(manual_actions).to be_empty
+
+          # succeed stage test
+          pipeline.builds.running_or_pending.each(&:success)
+          expect(manual_actions).to be_empty
+
+          # succeed stage staging and skip stage production
+          pipeline.builds.running_or_pending.each(&:success)
+          expect(manual_actions).to be_many # production and clear cache
+
+          # succeed stage cleanup
+          pipeline.builds.running_or_pending.each(&:success)
+
+          # after processing a pipeline we should have 6 builds, 5 succeeded
+          expect(pipeline.builds.count).to eq(6)
+          expect(pipeline.builds.success.count).to eq(4)
+        end
+
+        def manual_actions
+          pipeline.manual_actions
+        end
+      end
     end
 
     context 'when no builds created' do
@@ -416,4 +480,66 @@ describe Ci::Pipeline, models: true do
       end
     end
   end
+
+  describe '#manual_actions' do
+    subject { pipeline.manual_actions }
+
+    it 'when none defined' do
+      is_expected.to be_empty
+    end
+
+    context 'when action defined' do
+      let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
+
+      it 'returns one action' do
+        is_expected.to contain_exactly(manual)
+      end
+
+      context 'there are multiple of the same name' do
+        let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
+
+        it 'returns latest one' do
+          is_expected.to contain_exactly(manual2)
+        end
+      end
+    end
+  end
+
+  describe '#has_warnings?' do
+    subject { pipeline.has_warnings? }
+
+    context 'build which is allowed to fail fails' do
+      before do
+        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
+        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
+      end
+      
+      it 'returns true' do
+        is_expected.to be_truthy
+      end
+    end
+
+    context 'build which is allowed to fail succeeds' do
+      before do
+        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
+        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
+      end
+      
+      it 'returns false' do
+        is_expected.to be_falsey
+      end
+    end
+
+    context 'build is retried and succeeds' do
+      before do
+        create :ci_build, :success, pipeline: pipeline, name: 'rubocop'
+        create :ci_build, :failed, pipeline: pipeline, name: 'rspec'
+        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
+      end
+
+      it 'returns false' do
+        is_expected.to be_falsey
+      end
+    end
+  end
 end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ba02d5fe97727fa8114064bbf08ca4d24bc24869..d3e6a6648cc647cc9c7aeac11e7edbbdcef6553b 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -13,6 +13,26 @@ describe Commit, models: true do
     it { is_expected.to include_module(StaticModel) }
   end
 
+  describe '#author' do
+    it 'looks up the author in a case-insensitive way' do
+      user = create(:user, email: commit.author_email.upcase)
+      expect(commit.author).to eq(user)
+    end
+
+    it 'caches the author' do
+      user = create(:user, email: commit.author_email)
+      expect(RequestStore).to receive(:active?).twice.and_return(true)
+      expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original
+
+      expect(commit.author).to eq(user)
+      key = "commit_author:#{commit.author_email}"
+      expect(RequestStore.store[key]).to eq(user)
+
+      expect(commit.author).to eq(user)
+      RequestStore.store.clear
+    end
+  end
+
   describe '#to_reference' do
     it 'returns a String reference to the object' do
       expect(commit.to_reference).to eq commit.id
@@ -66,6 +86,27 @@ eos
     end
   end
 
+  describe '#full_title' do
+    it "returns no_commit_message when safe_message is blank" do
+      allow(commit).to receive(:safe_message).and_return('')
+      expect(commit.full_title).to eq("--no commit message")
+    end
+
+    it "returns entire message if there is no newline" do
+      message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.'
+
+      allow(commit).to receive(:safe_message).and_return(message)
+      expect(commit.full_title).to eq(message)
+    end
+
+    it "returns first line of message if there is a newLine" do
+      message = commit.safe_message.split(" ").first
+
+      allow(commit).to receive(:safe_message).and_return(message + "\n" + message)
+      expect(commit.full_title).to eq(message)
+    end
+  end
+
   describe "delegation" do
     subject { commit }
 
@@ -212,6 +253,7 @@ eos
     it 'returns the URI type at the given path' do
       expect(commit.uri_type('files/html')).to be(:tree)
       expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+      expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
       expect(commit.uri_type('files/js/application.js')).to be(:blob)
     end
 
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 5e652660e2c4cd3db1e898f75563e4bda939e5ac..549b0042038333c13f0db2854c7036e8baf6e99d 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -68,7 +68,7 @@ describe Issue, "Mentionable" do
 
   describe '#create_cross_references!' do
     let(:project) { create(:project) }
-    let(:author)  { double('author') }
+    let(:author)  { build(:user) }
     let(:commit)  { project.commit }
     let(:commit2) { project.commit }
 
@@ -88,6 +88,10 @@ describe Issue, "Mentionable" do
     let(:author)  { create(:author) }
     let(:issues)  { create_list(:issue, 2, project: project, author: author) }
 
+    before do
+      project.team << [author, Gitlab::Access::DEVELOPER]
+    end
+
     context 'before changes are persisted' do
       it 'ignores pre-existing references' do
         issue = create_issue(description: issues[0].to_reference)
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index b273018707f491a8d2cf3c53a201ac78f998e164..7df3df4bb9e652016122d0e6ed99b605a15ca28e 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -11,6 +11,7 @@ describe Deployment, models: true do
   it { is_expected.to delegate_method(:name).to(:environment).with_prefix }
   it { is_expected.to delegate_method(:commit).to(:project) }
   it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
+  it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
 
   it { is_expected.to validate_presence_of(:ref) }
   it { is_expected.to validate_presence_of(:sha) }
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index af8e890ca951b2e39b450dd3e4d2391877e136d0..1fa96eb1f158cf5653fe1b96acc48c0e6712d647 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -119,7 +119,7 @@ describe DiffNote, models: true do
 
       context "when the merge request's diff refs don't match that of the diff note" do
         before do
-          allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs)
+          allow(subject.noteable).to receive(:diff_sha_refs).and_return(commit.diff_refs)
         end
 
         it "returns false" do
@@ -168,7 +168,7 @@ describe DiffNote, models: true do
 
         context "when the note is outdated" do
           before do
-            allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs)
+            allow(merge_request).to receive(:diff_sha_refs).and_return(commit.diff_refs)
           end
 
           it "uses the DiffPositionUpdateService" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 7629af6a570a6f101df9576e02cf0d6fbbc44ee8..8a84ac0a7c7cb601cc695a2d8b55a97b8ca75ddb 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -11,4 +11,23 @@ describe Environment, models: true do
   it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
   it { is_expected.to validate_length_of(:name).is_within(0..255) }
+
+  it { is_expected.to validate_length_of(:external_url).is_within(0..255) }
+
+  # To circumvent a not null violation of the name column:
+  # https://github.com/thoughtbot/shoulda-matchers/issues/336
+  it 'validates uniqueness of :external_url' do
+    create(:environment)
+
+    is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id)
+  end
+
+  describe '#nullify_external_url' do
+    it 'replaces a blank url with nil' do
+      env = build(:environment, external_url: "")
+
+      expect(env.save).to be true
+      expect(env.external_url).to be_nil
+    end
+  end
 end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index b87d68283e6cb279c14ce737def53791ea39acf9..3259f79529647c10c17959bb9de5a3a2ab37ad10 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -22,6 +22,26 @@ describe Issue, models: true do
     it { is_expected.to have_db_index(:deleted_at) }
   end
 
+  describe 'visible_to_user' do
+    let(:user) { create(:user) }
+    let(:authorized_user) { create(:user) }
+    let(:project) { create(:project, namespace: authorized_user.namespace) }
+    let!(:public_issue) { create(:issue, project: project) }
+    let!(:confidential_issue) { create(:issue, project: project, confidential: true) }
+
+    it 'returns non confidential issues for nil user' do
+      expect(Issue.visible_to_user(nil).count).to be(1)
+    end
+
+    it 'returns non confidential issues for user not authorized for the issues projects' do
+      expect(Issue.visible_to_user(user).count).to be(1)
+    end
+
+    it 'returns all issues for user authorized for the issues projects' do
+      expect(Issue.visible_to_user(authorized_user).count).to be(2)
+    end
+  end
+
   describe '#to_reference' do
     it 'returns a String reference to the object' do
       expect(subject.to_reference).to eq "##{subject.iid}"
@@ -286,4 +306,257 @@ describe Issue, models: true do
       expect(user2.assigned_open_issues_count).to eq(1)
     end
   end
+
+  describe '#visible_to_user?' do
+    context 'with a user' do
+      let(:user) { build(:user) }
+      let(:issue) { build(:issue) }
+
+      it 'returns true when the issue is readable' do
+        expect(issue).to receive(:readable_by?).with(user).and_return(true)
+
+        expect(issue.visible_to_user?(user)).to eq(true)
+      end
+
+      it 'returns false when the issue is not readable' do
+        expect(issue).to receive(:readable_by?).with(user).and_return(false)
+
+        expect(issue.visible_to_user?(user)).to eq(false)
+      end
+    end
+
+    context 'without a user' do
+      let(:issue) { build(:issue) }
+
+      it 'returns true when the issue is publicly visible' do
+        expect(issue).to receive(:publicly_visible?).and_return(true)
+
+        expect(issue.visible_to_user?).to eq(true)
+      end
+
+      it 'returns false when the issue is not publicly visible' do
+        expect(issue).to receive(:publicly_visible?).and_return(false)
+
+        expect(issue.visible_to_user?).to eq(false)
+      end
+    end
+  end
+
+  describe '#readable_by?' do
+    describe 'with a regular user that is not a team member' do
+      let(:user) { create(:user) }
+
+      context 'using a public project' do
+        let(:project) { create(:empty_project, :public) }
+
+        it 'returns true for a regular issue' do
+          issue = build(:issue, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+
+        it 'returns false for a confidential issue' do
+          issue = build(:issue, project: project, confidential: true)
+
+          expect(issue).not_to be_readable_by(user)
+        end
+      end
+
+      context 'using an internal project' do
+        let(:project) { create(:empty_project, :internal) }
+
+        context 'using an internal user' do
+          it 'returns true for a regular issue' do
+            issue = build(:issue, project: project)
+
+            expect(issue).to be_readable_by(user)
+          end
+
+          it 'returns false for a confidential issue' do
+            issue = build(:issue, :confidential, project: project)
+
+            expect(issue).not_to be_readable_by(user)
+          end
+        end
+
+        context 'using an external user' do
+          before do
+            allow(user).to receive(:external?).and_return(true)
+          end
+
+          it 'returns false for a regular issue' do
+            issue = build(:issue, project: project)
+
+            expect(issue).not_to be_readable_by(user)
+          end
+
+          it 'returns false for a confidential issue' do
+            issue = build(:issue, :confidential, project: project)
+
+            expect(issue).not_to be_readable_by(user)
+          end
+        end
+      end
+
+      context 'using a private project' do
+        let(:project) { create(:empty_project, :private) }
+
+        it 'returns false for a regular issue' do
+          issue = build(:issue, project: project)
+
+          expect(issue).not_to be_readable_by(user)
+        end
+
+        it 'returns false for a confidential issue' do
+          issue = build(:issue, :confidential, project: project)
+
+          expect(issue).not_to be_readable_by(user)
+        end
+
+        context 'when the user is the project owner' do
+          it 'returns true for a regular issue' do
+            issue = build(:issue, project: project)
+
+            expect(issue).not_to be_readable_by(user)
+          end
+
+          it 'returns true for a confidential issue' do
+            issue = build(:issue, :confidential, project: project)
+
+            expect(issue).not_to be_readable_by(user)
+          end
+        end
+      end
+    end
+
+    context 'with a regular user that is a team member' do
+      let(:user) { create(:user) }
+      let(:project) { create(:empty_project, :public) }
+
+      context 'using a public project' do
+        before do
+          project.team << [user, Gitlab::Access::DEVELOPER]
+        end
+
+        it 'returns true for a regular issue' do
+          issue = build(:issue, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+
+        it 'returns true for a confidential issue' do
+          issue = build(:issue, :confidential, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+      end
+
+      context 'using an internal project' do
+        let(:project) { create(:empty_project, :internal) }
+
+        before do
+          project.team << [user, Gitlab::Access::DEVELOPER]
+        end
+
+        it 'returns true for a regular issue' do
+          issue = build(:issue, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+
+        it 'returns true for a confidential issue' do
+          issue = build(:issue, :confidential, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+      end
+
+      context 'using a private project' do
+        let(:project) { create(:empty_project, :private) }
+
+        before do
+          project.team << [user, Gitlab::Access::DEVELOPER]
+        end
+
+        it 'returns true for a regular issue' do
+          issue = build(:issue, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+
+        it 'returns true for a confidential issue' do
+          issue = build(:issue, :confidential, project: project)
+
+          expect(issue).to be_readable_by(user)
+        end
+      end
+    end
+
+    context 'with an admin user' do
+      let(:project) { create(:empty_project) }
+      let(:user) { create(:user, admin: true) }
+
+      it 'returns true for a regular issue' do
+        issue = build(:issue, project: project)
+
+        expect(issue).to be_readable_by(user)
+      end
+
+      it 'returns true for a confidential issue' do
+        issue = build(:issue, :confidential, project: project)
+
+        expect(issue).to be_readable_by(user)
+      end
+    end
+  end
+
+  describe '#publicly_visible?' do
+    context 'using a public project' do
+      let(:project) { create(:empty_project, :public) }
+
+      it 'returns true for a regular issue' do
+        issue = build(:issue, project: project)
+
+        expect(issue).to be_publicly_visible
+      end
+
+      it 'returns false for a confidential issue' do
+        issue = build(:issue, :confidential, project: project)
+
+        expect(issue).not_to be_publicly_visible
+      end
+    end
+
+    context 'using an internal project' do
+      let(:project) { create(:empty_project, :internal) }
+
+      it 'returns false for a regular issue' do
+        issue = build(:issue, project: project)
+
+        expect(issue).not_to be_publicly_visible
+      end
+
+      it 'returns false for a confidential issue' do
+        issue = build(:issue, :confidential, project: project)
+
+        expect(issue).not_to be_publicly_visible
+      end
+    end
+
+    context 'using a private project' do
+      let(:project) { create(:empty_project, :private) }
+
+      it 'returns false for a regular issue' do
+        issue = build(:issue, project: project)
+
+        expect(issue).not_to be_publicly_visible
+      end
+
+      it 'returns false for a confidential issue' do
+        issue = build(:issue, :confidential, project: project)
+
+        expect(issue).not_to be_publicly_visible
+      end
+    end
+  end
 end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 49cf3d8633ad9ea4464311b83818ee2beac5dc3e..6d68e52a822c977d86efcb7edefc1722b5a7623f 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -16,12 +16,13 @@ describe Key, models: true do
   end
 
   describe "Methods" do
+    let(:user) { create(:user) }
     it { is_expected.to respond_to :projects }
     it { is_expected.to respond_to :publishable_key }
 
     describe "#publishable_keys" do
-      it 'strips all personal information' do
-        expect(build(:key).publishable_key).not_to match(/dummy@gitlab/)
+      it 'replaces SSH key comment with simple identifier of username + hostname' do
+        expect(build(:key, user: user).publishable_key).to include("#{user.name} (localhost)")
       end
     end
   end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 40181a8b906e46ab0e0e78e1f9d60a1d52998f58..44cd3c08718fd577d4d4ba0bb9589be42f912743 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -79,6 +79,18 @@ describe Member, models: true do
       @accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
     end
 
+    describe '.access_for_user_ids' do
+      it 'returns the right access levels' do
+        users = [@owner_user.id, @master_user.id]
+        expected = {
+          @owner_user.id => Gitlab::Access::OWNER,
+          @master_user.id => Gitlab::Access::MASTER
+        }
+
+        expect(described_class.access_for_user_ids(users)).to eq(expected)
+      end
+    end
+
     describe '.invite' do
       it { expect(described_class.invite).not_to include @master }
       it { expect(described_class.invite).to include @invited_member }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c8ad7ab3e7f59ed483dcacf3f4954496eab4cce0..21d22c776e984995caf9535bb29a874cae1b362b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -65,11 +65,11 @@ describe MergeRequest, models: true do
   end
 
   describe '#target_branch_sha' do
-    context 'when the target branch does not exist anymore' do
-      let(:project) { create(:project) }
+    let(:project) { create(:project) }
 
-      subject { create(:merge_request, source_project: project, target_project: project) }
+    subject { create(:merge_request, source_project: project, target_project: project) }
 
+    context 'when the target branch does not exist' do
       before do
         project.repository.raw_repository.delete_branch(subject.target_branch)
       end
@@ -78,6 +78,12 @@ describe MergeRequest, models: true do
         expect(subject.target_branch_sha).to be_nil
       end
     end
+
+    it 'returns memoized value' do
+      subject.target_branch_sha = '8ffb3c15a5475e59ae909384297fede4badcb4c7'
+
+      expect(subject.target_branch_sha).to eq '8ffb3c15a5475e59ae909384297fede4badcb4c7'
+    end
   end
 
   describe '#source_branch_sha' do
@@ -103,6 +109,12 @@ describe MergeRequest, models: true do
         expect(subject.source_branch_sha).to be_nil
       end
     end
+
+    it 'returns memoized value' do
+      subject.source_branch_sha = '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+
+      expect(subject.source_branch_sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+    end
   end
 
   describe '#to_reference' do
@@ -674,4 +686,28 @@ describe MergeRequest, models: true do
       subject.reload_diff
     end
   end
+
+  describe "#diff_sha_refs" do
+    context "with diffs" do
+      subject { create(:merge_request, :with_diffs) }
+
+      it "does not touch the repository" do
+        subject # Instantiate the object
+
+        expect_any_instance_of(Repository).not_to receive(:commit)
+
+        subject.diff_sha_refs
+      end
+
+      it "returns expected diff_refs" do
+        expected_diff_refs = Gitlab::Diff::DiffRefs.new(
+          base_sha:  subject.merge_request_diff.base_commit_sha,
+          start_sha: subject.merge_request_diff.start_commit_sha,
+          head_sha:  subject.merge_request_diff.head_commit_sha
+        )
+
+        expect(subject.diff_sha_refs).to eq(expected_diff_refs)
+      end
+    end
+  end
 end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 7d0697dab424475f824838785d38df2bf8e1609c..1243f5420a74a588381c338249326d153de1711f 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -135,22 +135,30 @@ describe Note, models: true do
     let!(:note2) { create(:note_on_issue) }
 
     it "reads the rendered note body from the cache" do
-      expect(Banzai::Renderer).to receive(:render).
-        with(note1.note,
-             pipeline: :note,
-             cache_key: [note1, "note"],
-             project: note1.project,
-             author: note1.author)
-
-      expect(Banzai::Renderer).to receive(:render).
-        with(note2.note,
-             pipeline: :note,
-             cache_key: [note2, "note"],
-             project: note2.project,
-             author: note2.author)
-
-      note1.all_references
-      note2.all_references
+      expect(Banzai::Renderer).to receive(:cache_collection_render).
+        with([{
+          text: note1.note,
+          context: {
+            pipeline: :note,
+            cache_key: [note1, "note"],
+            project: note1.project,
+            author: note1.author
+          }
+        }]).and_call_original
+
+      expect(Banzai::Renderer).to receive(:cache_collection_render).
+        with([{
+          text: note2.note,
+          context: {
+            pipeline: :note,
+            cache_key: [note2, "note"],
+            project: note2.project,
+            author: note2.author
+          }
+        }]).and_call_original
+
+      note1.all_references.users
+      note2.all_references.users
     end
   end
 
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 5f618322aabe07df6128cb6acc05c79d0f93e4f8..62ae5f6cf7474512f195290c1e5fc9783c0d66d7 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -340,18 +340,36 @@ describe HipchatService, models: true do
     end
 
     context "#message_options" do
-      it "should be set to the defaults" do
-        expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
+      it "is set to the defaults" do
+        expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
       end
 
-      it "should set notfiy to true" do
+      it "sets notify to true" do
         allow(hipchat).to receive(:notify).and_return('1')
-        expect(hipchat.send(:message_options)).to eq({ notify: true, color: 'yellow' })
+
+        expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
       end
 
-      it "should set the color" do
+      it "sets the color" do
         allow(hipchat).to receive(:color).and_return('red')
-        expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'red' })
+
+        expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+      end
+
+      context 'with a successful build' do
+        it 'uses the green color' do
+          build_data = { object_kind: 'build', commit: { status: 'success' } }
+
+          expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'green' })
+        end
+      end
+
+      context 'with a failed build' do
+        it 'uses the red color' do
+          build_data = { object_kind: 'build', commit: { status: 'failed' } }
+
+          expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' })
+        end
       end
     end
   end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 155f3e74e0d24989c51bdb357f494927477d481a..df511b1bc4c36e654d5596390ee30c2be2f9f09d 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -124,6 +124,7 @@ describe SlackService, models: true do
        and_return(
          double(:slack_service).as_null_object
        )
+
       slack.execute(push_sample_data)
     end
 
@@ -136,6 +137,76 @@ describe SlackService, models: true do
         )
       slack.execute(push_sample_data)
     end
+
+    context "event channels" do
+      it "uses the right channel for push event" do
+        slack.update_attributes(push_channel: "random")
+
+        expect(Slack::Notifier).to receive(:new).
+         with(webhook_url, channel: "random").
+         and_return(
+           double(:slack_service).as_null_object
+         )
+
+        slack.execute(push_sample_data)
+      end
+
+      it "uses the right channel for merge request event" do
+        slack.update_attributes(merge_request_channel: "random")
+
+        expect(Slack::Notifier).to receive(:new).
+         with(webhook_url, channel: "random").
+         and_return(
+           double(:slack_service).as_null_object
+         )
+
+        slack.execute(@merge_sample_data)
+      end
+
+      it "uses the right channel for issue event" do
+        slack.update_attributes(issue_channel: "random")
+
+        expect(Slack::Notifier).to receive(:new).
+         with(webhook_url, channel: "random").
+         and_return(
+           double(:slack_service).as_null_object
+         )
+
+        slack.execute(@issues_sample_data)
+      end
+
+      it "uses the right channel for wiki event" do
+        slack.update_attributes(wiki_page_channel: "random")
+
+        expect(Slack::Notifier).to receive(:new).
+         with(webhook_url, channel: "random").
+         and_return(
+           double(:slack_service).as_null_object
+         )
+
+        slack.execute(@wiki_page_sample_data)
+      end
+
+      context "note event" do
+        let(:issue_note) do
+          create(:note_on_issue, project: project, note: "issue note")
+        end
+
+        it "uses the right channel" do
+          slack.update_attributes(note_channel: "random")
+
+          note_data = Gitlab::NoteDataBuilder.build(issue_note, user)
+
+          expect(Slack::Notifier).to receive(:new).
+           with(webhook_url, channel: "random").
+           and_return(
+             double(:slack_service).as_null_object
+           )
+
+          slack.execute(note_data)
+        end
+      end
+    end
   end
 
   describe "Note events" do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9dc34276f188edbb0617ccb1434e3c67180e22bb..e365e4e98b2bdf2ad9551e615671a8e3d383681f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -245,6 +245,34 @@ describe Project, models: true do
     end
   end
 
+  describe "#new_issue_address" do
+    let(:project) { create(:empty_project, path: "somewhere") }
+    let(:user) { create(:user) }
+
+    context 'incoming email enabled' do
+      before do
+        stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+      end
+
+      it 'returns the address to create a new issue' do
+        token = user.authentication_token
+        address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab"
+
+        expect(project.new_issue_address(user)).to eq(address)
+      end
+    end
+
+    context 'incoming email disabled' do
+      before do
+        stub_incoming_email_setting(enabled: false)
+      end
+
+      it 'returns nil' do
+        expect(project.new_issue_address(user)).to be_nil
+      end
+    end
+  end
+
   describe 'last_activity methods' do
     let(:project) { create(:project) }
     let(:last_event) { double(created_at: Time.now) }
@@ -372,12 +400,30 @@ describe Project, models: true do
 
       it { expect(@project.to_param).to eq('gitlabhq') }
     end
+
+    context 'with invalid path' do
+      it 'returns previous path to keep project suitable for use in URLs when persisted' do
+        project = create(:empty_project, path: 'gitlab')
+        project.path = 'foo&bar'
+
+        expect(project).not_to be_valid
+        expect(project.to_param).to eq 'gitlab'
+      end
+
+      it 'returns current path when new record' do
+        project = build(:empty_project, path: 'gitlab')
+        project.path = 'foo&bar'
+
+        expect(project).not_to be_valid
+        expect(project.to_param).to eq 'foo&bar'
+      end
+    end
   end
 
   describe '#repository' do
     let(:project) { create(:project) }
 
-    it 'should return valid repo' do
+    it 'returns valid repo' do
       expect(project.repository).to be_kind_of(Repository)
     end
   end
@@ -458,6 +504,57 @@ describe Project, models: true do
     end
   end
 
+  describe '#external_wiki' do
+    let(:project) { create(:project) }
+
+    context 'with an active external wiki' do
+      before do
+        create(:service, project: project, type: 'ExternalWikiService', active: true)
+        project.external_wiki
+      end
+
+      it 'sets :has_external_wiki as true' do
+        expect(project.has_external_wiki).to be(true)
+      end
+
+      it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
+        expect(project.has_external_wiki).to be(true)
+
+        project.services.external_wikis.first.destroy
+
+        expect(project.has_external_wiki).to be(false)
+      end
+    end
+
+    context 'with an inactive external wiki' do
+      before do
+        create(:service, project: project, type: 'ExternalWikiService', active: false)
+      end
+
+      it 'sets :has_external_wiki as false' do
+        expect(project.has_external_wiki).to be(false)
+      end
+    end
+
+    context 'with no external wiki' do
+      before do
+        project.external_wiki
+      end
+
+      it 'sets :has_external_wiki as false' do
+        expect(project.has_external_wiki).to be(false)
+      end
+
+      it 'sets :has_external_wiki as true if an external wiki service is created later' do
+        expect(project.has_external_wiki).to be(false)
+
+        create(:service, project: project, type: 'ExternalWikiService', active: true)
+
+        expect(project.has_external_wiki).to be(true)
+      end
+    end
+  end
+
   describe '#open_branches' do
     let(:project) { create(:project) }
 
@@ -998,46 +1095,6 @@ describe Project, models: true do
     end
   end
 
-  describe "#developers_can_push_to_protected_branch?" do
-    let(:project) { create(:empty_project) }
-
-    context "when the branch matches a protected branch via direct match" do
-      it "returns true if 'Developers can Push' is turned on" do
-        create(:protected_branch, name: "production", project: project, developers_can_push: true)
-
-        expect(project.developers_can_push_to_protected_branch?('production')).to be true
-      end
-
-      it "returns false if 'Developers can Push' is turned off" do
-        create(:protected_branch, name: "production", project: project, developers_can_push: false)
-
-        expect(project.developers_can_push_to_protected_branch?('production')).to be false
-      end
-    end
-
-    context "when the branch matches a protected branch via wilcard match" do
-      it "returns true if 'Developers can Push' is turned on" do
-        create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
-
-        expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true
-      end
-
-      it "returns false if 'Developers can Push' is turned off" do
-        create(:protected_branch, name: "production/*", project: project, developers_can_push: false)
-
-        expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false
-      end
-    end
-
-    context "when the branch does not match a protected branch" do
-      it "returns false" do
-        create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
-
-        expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false
-      end
-    end
-  end
-
   describe '#container_registry_path_with_namespace' do
     let(:project) { create(:empty_project, path: 'PROJECT') }
 
@@ -1114,6 +1171,111 @@ describe Project, models: true do
     end
   end
 
+  describe '#latest_successful_builds_for' do
+    def create_pipeline(status = 'success')
+      create(:ci_pipeline, project: project,
+                           sha: project.commit.sha,
+                           ref: project.default_branch,
+                           status: status)
+    end
+
+    def create_build(new_pipeline = pipeline, name = 'test')
+      create(:ci_build, :success, :artifacts,
+             pipeline: new_pipeline,
+             status: new_pipeline.status,
+             name: name)
+    end
+
+    let(:project) { create(:project) }
+    let(:pipeline) { create_pipeline }
+
+    context 'with many builds' do
+      it 'gives the latest builds from latest pipeline' do
+        pipeline1 = create_pipeline
+        pipeline2 = create_pipeline
+        build1_p2 = create_build(pipeline2, 'test')
+        create_build(pipeline1, 'test')
+        create_build(pipeline1, 'test2')
+        build2_p2 = create_build(pipeline2, 'test2')
+
+        latest_builds = project.latest_successful_builds_for
+
+        expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
+      end
+    end
+
+    context 'with succeeded pipeline' do
+      let!(:build) { create_build }
+
+      context 'standalone pipeline' do
+        it 'returns builds for ref for default_branch' do
+          builds = project.latest_successful_builds_for
+
+          expect(builds).to contain_exactly(build)
+        end
+
+        it 'returns empty relation if the build cannot be found' do
+          builds = project.latest_successful_builds_for('TAIL')
+
+          expect(builds).to be_kind_of(ActiveRecord::Relation)
+          expect(builds).to be_empty
+        end
+      end
+
+      context 'with some pending pipeline' do
+        before do
+          create_build(create_pipeline('pending'))
+        end
+
+        it 'gives the latest build from latest pipeline' do
+          latest_build = project.latest_successful_builds_for
+
+          expect(latest_build).to contain_exactly(build)
+        end
+      end
+    end
+
+    context 'with pending pipeline' do
+      before do
+        pipeline.update(status: 'pending')
+        create_build(pipeline)
+      end
+
+      it 'returns empty relation' do
+        builds = project.latest_successful_builds_for
+
+        expect(builds).to be_kind_of(ActiveRecord::Relation)
+        expect(builds).to be_empty
+      end
+    end
+  end
+
+  describe '#add_import_job' do
+    context 'forked' do
+      let(:forked_project_link) { create(:forked_project_link) }
+      let(:forked_from_project) { forked_project_link.forked_from_project }
+      let(:project) { forked_project_link.forked_to_project }
+
+      it 'schedules a RepositoryForkWorker job' do
+        expect(RepositoryForkWorker).to receive(:perform_async).
+          with(project.id, forked_from_project.repository_storage_path,
+              forked_from_project.path_with_namespace, project.namespace.path)
+
+        project.add_import_job
+      end
+    end
+
+    context 'not forked' do
+      let(:project) { create(:project) }
+
+      it 'schedules a RepositoryImportWorker job' do
+        expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
+
+        project.add_import_job
+      end
+    end
+  end
+
   describe '.where_paths_in' do
     context 'without any paths' do
       it 'returns an empty relation' do
@@ -1146,4 +1308,53 @@ describe Project, models: true do
       end
     end
   end
+
+  describe 'authorized_for_user' do
+    let(:group) { create(:group) }
+    let(:developer) { create(:user) }
+    let(:master) { create(:user) }
+    let(:personal_project) { create(:project, namespace: developer.namespace) }
+    let(:group_project) { create(:project, namespace: group) }
+    let(:members_project) { create(:project) }
+    let(:shared_project) { create(:project) }
+
+    before do
+      group.add_master(master)
+      group.add_developer(developer)
+
+      members_project.team << [developer, :developer]
+      members_project.team << [master, :master]
+
+      create(:project_group_link, project: shared_project, group: group)
+    end
+
+    it 'returns false for no user' do
+      expect(personal_project.authorized_for_user?(nil)).to be(false)
+    end
+
+    it 'returns true for personal projects of the user' do
+      expect(personal_project.authorized_for_user?(developer)).to be(true)
+    end
+
+    it 'returns true for projects of groups the user is a member of' do
+      expect(group_project.authorized_for_user?(developer)).to be(true)
+    end
+
+    it 'returns true for projects for which the user is a member of' do
+      expect(members_project.authorized_for_user?(developer)).to be(true)
+    end
+
+    it 'returns true for projects shared on a group the user is a member of' do
+      expect(shared_project.authorized_for_user?(developer)).to be(true)
+    end
+
+    it 'checks for the correct minimum level access' do
+      expect(group_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
+      expect(group_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
+      expect(members_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
+      expect(members_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
+      expect(shared_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
+      expect(shared_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
+    end
+  end
 end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 9262aeb6ed890a074f2971af20509474711e9b01..5eaf0d3b7a6c5f1b823647f744bc6e84fdfb9066 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -151,8 +151,8 @@ describe ProjectTeam, models: true do
         it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
         it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
         it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
-        it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
-        it { expect(project.team.max_member_access(requester.id)).to be_nil }
+        it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+        it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
       end
 
       context 'when project is shared with group' do
@@ -168,14 +168,14 @@ describe ProjectTeam, models: true do
 
         it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
         it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
-        it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
-        it { expect(project.team.max_member_access(requester.id)).to be_nil }
+        it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+        it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
 
         context 'but share_with_group_lock is true' do
           before { project.namespace.update(share_with_group_lock: true) }
 
-          it { expect(project.team.max_member_access(master.id)).to be_nil }
-          it { expect(project.team.max_member_access(reporter.id)).to be_nil }
+          it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::NO_ACCESS) }
+          it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) }
         end
       end
     end
@@ -194,8 +194,74 @@ describe ProjectTeam, models: true do
       it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
       it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
       it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
-      it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
-      it { expect(project.team.max_member_access(requester.id)).to be_nil }
+      it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+      it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
     end
   end
+
+  shared_examples_for "#max_member_access_for_users" do |enable_request_store|
+    describe "#max_member_access_for_users" do
+      before do
+        RequestStore.begin! if enable_request_store
+      end
+
+      after do
+        if enable_request_store
+          RequestStore.end!
+          RequestStore.clear!
+        end
+      end
+
+      it 'returns correct roles for different users' do
+        master = create(:user)
+        reporter = create(:user)
+        promoted_guest = create(:user)
+        guest = create(:user)
+        project = create(:project)
+
+        project.team << [master, :master]
+        project.team << [reporter, :reporter]
+        project.team << [promoted_guest, :guest]
+        project.team << [guest, :guest]
+
+        group = create(:group)
+        group_developer = create(:user)
+        second_developer = create(:user)
+        project.project_group_links.create(
+          group: group,
+          group_access: Gitlab::Access::DEVELOPER)
+
+        group.add_master(promoted_guest)
+        group.add_developer(group_developer)
+        group.add_developer(second_developer)
+
+        second_group = create(:group)
+        project.project_group_links.create(
+          group: second_group,
+          group_access: Gitlab::Access::MASTER)
+        second_group.add_master(second_developer)
+
+        users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
+
+        expected = {
+          master.id => Gitlab::Access::MASTER,
+          reporter.id => Gitlab::Access::REPORTER,
+          promoted_guest.id => Gitlab::Access::DEVELOPER,
+          guest.id => Gitlab::Access::GUEST,
+          group_developer.id => Gitlab::Access::DEVELOPER,
+          second_developer.id => Gitlab::Access::MASTER
+        }
+
+        expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+      end
+    end
+  end
+
+  describe '#max_member_access_for_users with RequestStore' do
+    it_behaves_like "#max_member_access_for_users", true
+  end
+
+  describe '#max_member_access_for_users without RequestStore' do
+    it_behaves_like "#max_member_access_for_users", false
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e14cec589fe4bbdb92227c22bdea2c37d4f161f5..cce15538b93ed9a2092f6f0bab156fcb1cf82962 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -50,8 +50,9 @@ describe Repository, models: true do
           double_first = double(committed_date: Time.now)
           double_last = double(committed_date: Time.now - 1.second)
 
-          allow(repository).to receive(:commit).with(tag_a.target).and_return(double_first)
-          allow(repository).to receive(:commit).with(tag_b.target).and_return(double_last)
+          allow(tag_a).to receive(:target).and_return(double_first)
+          allow(tag_b).to receive(:target).and_return(double_last)
+          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
         end
 
         it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
@@ -64,8 +65,9 @@ describe Repository, models: true do
           double_first = double(committed_date: Time.now - 1.second)
           double_last = double(committed_date: Time.now)
 
-          allow(repository).to receive(:commit).with(tag_a.target).and_return(double_last)
-          allow(repository).to receive(:commit).with(tag_b.target).and_return(double_first)
+          allow(tag_a).to receive(:target).and_return(double_last)
+          allow(tag_b).to receive(:target).and_return(double_first)
+          allow(repository).to receive(:tags).and_return([tag_a, tag_b])
         end
 
         it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
@@ -130,6 +132,36 @@ describe Repository, models: true do
     end
   end
 
+  describe :commit_file do
+    it 'commits change to a file successfully' do
+      expect do
+        repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+                              'Updates file content',
+                              'master', true)
+      end.to change { repository.commits('master').count }.by(1)
+
+      blob = repository.blob_at('master', 'CHANGELOG')
+
+      expect(blob.data).to eq('Changelog!')
+    end
+  end
+
+  describe :update_file do
+    it 'updates filename successfully' do
+      expect do
+        repository.update_file(user, 'NEWLICENSE', 'Copyright!',
+                                     branch: 'master',
+                                     previous_path: 'LICENSE',
+                                     message: 'Changes filename')
+      end.to change { repository.commits('master').count }.by(1)
+
+      files = repository.ls_files('master')
+
+      expect(files).not_to include('LICENSE')
+      expect(files).to include('NEWLICENSE')
+    end
+  end
+
   describe "search_files" do
     let(:results) { repository.search_files('feature', 'master') }
     subject { results }
@@ -351,9 +383,13 @@ describe Repository, models: true do
   end
 
   describe '#rm_branch' do
+    let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+    let(:blank_sha) { '0000000000000000000000000000000000000000' }
+
     context 'when pre hooks were successful' do
       it 'should run without errors' do
-        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+        expect_any_instance_of(GitHooksService).to receive(:execute).
+          with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature')
 
         expect { repository.rm_branch(user, 'feature') }.not_to raise_error
       end
@@ -388,10 +424,13 @@ describe Repository, models: true do
   end
 
   describe '#commit_with_hooks' do
+    let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+
     context 'when pre hooks were successful' do
       before do
         expect_any_instance_of(GitHooksService).to receive(:execute).
-          and_return(true)
+          with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature').
+          and_yield.and_return(true)
       end
 
       it 'should run without errors' do
@@ -405,6 +444,14 @@ describe Repository, models: true do
 
         repository.commit_with_hooks(user, 'feature') { sample_commit.id }
       end
+
+      context "when the branch wasn't empty" do
+        it 'updates the head' do
+          expect(repository.find_branch('feature').target.id).to eq(old_rev)
+          repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+          expect(repository.find_branch('feature').target.id).to eq(sample_commit.id)
+        end
+      end
     end
 
     context 'when pre hooks failed' do
@@ -416,6 +463,43 @@ describe Repository, models: true do
         end.to raise_error(GitHooksService::PreReceiveError)
       end
     end
+
+    context 'when target branch is different from source branch' do
+      before do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+      end
+
+      it 'expires branch cache' do
+        expect(repository).not_to receive(:expire_exists_cache)
+        expect(repository).not_to receive(:expire_root_ref_cache)
+        expect(repository).not_to receive(:expire_emptiness_caches)
+        expect(repository).to     receive(:expire_branches_cache)
+        expect(repository).to     receive(:expire_has_visible_content_cache)
+        expect(repository).to     receive(:expire_branch_count_cache)
+
+        repository.commit_with_hooks(user, 'new-feature') { sample_commit.id }
+      end
+    end
+
+    context 'when repository is empty' do
+      before do
+        allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+      end
+
+      it 'expires creation and branch cache' do
+        empty_repository = create(:empty_project, :empty_repo).repository
+
+        expect(empty_repository).to receive(:expire_exists_cache)
+        expect(empty_repository).to receive(:expire_root_ref_cache)
+        expect(empty_repository).to receive(:expire_emptiness_caches)
+        expect(empty_repository).to receive(:expire_branches_cache)
+        expect(empty_repository).to receive(:expire_has_visible_content_cache)
+        expect(empty_repository).to receive(:expire_branch_count_cache)
+
+        empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+                                     'Updates file content', 'master', false)
+      end
+    end
   end
 
   describe '#exists?' do
@@ -719,6 +803,30 @@ describe Repository, models: true do
         repository.before_delete
       end
 
+      it 'flushes the tags cache' do
+        expect(repository).to receive(:expire_tags_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the tag count cache' do
+        expect(repository).to receive(:expire_tag_count_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the branches cache' do
+        expect(repository).to receive(:expire_branches_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the branch count cache' do
+        expect(repository).to receive(:expire_branch_count_cache)
+
+        repository.before_delete
+      end
+
       it 'flushes the root ref cache' do
         expect(repository).to receive(:expire_root_ref_cache)
 
@@ -749,6 +857,30 @@ describe Repository, models: true do
         repository.before_delete
       end
 
+      it 'flushes the tags cache' do
+        expect(repository).to receive(:expire_tags_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the tag count cache' do
+        expect(repository).to receive(:expire_tag_count_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the branches cache' do
+        expect(repository).to receive(:expire_branches_cache)
+
+        repository.before_delete
+      end
+
+      it 'flushes the branch count cache' do
+        expect(repository).to receive(:expire_branch_count_cache)
+
+        repository.before_delete
+      end
+
       it 'flushes the root ref cache' do
         expect(repository).to receive(:expire_root_ref_cache)
 
@@ -1083,51 +1215,31 @@ describe Repository, models: true do
     end
   end
 
-  describe '#local_branches' do
-    it 'returns the local branches' do
-      masterrev = repository.find_branch('master').target
-      create_remote_branch('joe', 'remote_branch', masterrev)
-      repository.add_branch(user, 'local_branch', masterrev)
-
-      expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
-      expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
+  describe "#keep_around" do
+    it "does not fail if we attempt to reference bad commit" do
+      expect(repository.kept_around?('abc1234')).to be_falsey
     end
-  end
-
-  describe '.clean_old_archives' do
-    let(:path) { Gitlab.config.gitlab.repository_downloads_path }
-
-    context 'when the downloads directory does not exist' do
-      it 'does not remove any archives' do
-        expect(File).to receive(:directory?).with(path).and_return(false)
 
-        expect(Gitlab::Popen).not_to receive(:popen)
+    it "stores a reference to the specified commit sha so it isn't garbage collected" do
+      repository.keep_around(sample_commit.id)
 
-        described_class.clean_old_archives
-      end
+      expect(repository.kept_around?(sample_commit.id)).to be_truthy
     end
 
-    context 'when the downloads directory exists' do
-      it 'removes old archives' do
-        expect(File).to receive(:directory?).with(path).and_return(true)
-
-        expect(Gitlab::Popen).to receive(:popen)
+    it "attempting to call keep_around on truncated ref does not fail" do
+      repository.keep_around(sample_commit.id)
+      ref = repository.send(:keep_around_ref_name, sample_commit.id)
+      path = File.join(repository.path, ref)
+      # Corrupt the reference
+      File.truncate(path, 0)
 
-        described_class.clean_old_archives
-      end
-    end
-  end
+      expect(repository.kept_around?(sample_commit.id)).to be_falsey
 
-  describe "#keep_around" do
-    it "stores a reference to the specified commit sha so it isn't garbage collected" do
       repository.keep_around(sample_commit.id)
 
-      expect(repository.kept_around?(sample_commit.id)).to be_truthy
-    end
-  end
+      expect(repository.kept_around?(sample_commit.id)).to be_falsey
 
-  def create_remote_branch(remote_name, branch_name, target)
-    rugged = repository.rugged
-    rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
+      File.delete(path)
+    end
   end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fc74488ac0e0a591a5a86b107f055618de2a01c1..9f432501c59ede86d52b523baf3506c5088f33bf 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -89,9 +89,9 @@ describe User, models: true do
     end
 
     describe 'email' do
-      context 'when no signup domains listed' do
+      context 'when no signup domains whitelisted' do
         before do
-          allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return([])
+          allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return([])
         end
 
         it 'accepts any email' do
@@ -100,9 +100,9 @@ describe User, models: true do
         end
       end
 
-      context 'when a signup domain is listed and subdomains are allowed' do
+      context 'when a signup domain is whitelisted and subdomains are allowed' do
         before do
-          allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com'])
+          allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com', '*.example.com'])
         end
 
         it 'accepts info@example.com' do
@@ -121,9 +121,9 @@ describe User, models: true do
         end
       end
 
-      context 'when a signup domain is listed and subdomains are not allowed' do
+      context 'when a signup domain is whitelisted and subdomains are not allowed' do
         before do
-          allow_any_instance_of(ApplicationSetting).to receive(:restricted_signup_domains).and_return(['example.com'])
+          allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['example.com'])
         end
 
         it 'accepts info@example.com' do
@@ -142,6 +142,53 @@ describe User, models: true do
         end
       end
 
+      context 'domain blacklist' do
+        before do
+          allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist_enabled?).and_return(true)
+          allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['example.com'])
+        end
+
+        context 'when a signup domain is blacklisted' do
+          it 'accepts info@test.com' do
+            user = build(:user, email: 'info@test.com')
+            expect(user).to be_valid
+          end
+
+          it 'rejects info@example.com' do
+            user = build(:user, email: 'info@example.com')
+            expect(user).not_to be_valid
+          end
+        end
+
+        context 'when a signup domain is blacklisted but a wildcard subdomain is allowed' do
+          before do
+            allow_any_instance_of(ApplicationSetting).to receive(:domain_blacklist).and_return(['test.example.com'])
+            allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['*.example.com'])
+          end
+
+          it 'should give priority to whitelist and allow info@test.example.com' do
+            user = build(:user, email: 'info@test.example.com')
+            expect(user).to be_valid
+          end
+        end
+
+        context 'with both lists containing a domain' do
+          before do
+            allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['test.com'])
+          end
+
+          it 'accepts info@test.com' do
+            user = build(:user, email: 'info@test.com')
+            expect(user).to be_valid
+          end
+
+          it 'rejects info@example.com' do
+            user = build(:user, email: 'info@example.com')
+            expect(user).not_to be_valid
+          end
+        end
+      end
+
       context 'owns_notification_email' do
         it 'accepts temp_oauth_email emails' do
           user = build(:user, email: "temp-email-for-oauth@example.com")
@@ -596,7 +643,7 @@ describe User, models: true do
       user = create :user
       key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id
 
-      expect(user.all_ssh_keys).to include(key.key)
+      expect(user.all_ssh_keys).to include(a_string_starting_with(key.key))
     end
   end
 
@@ -887,16 +934,25 @@ describe User, models: true do
   end
 
   describe '#authorized_projects' do
-    let!(:user) { create(:user) }
-    let!(:private_project) { create(:project, :private) }
+    context 'with a minimum access level' do
+      it 'includes projects for which the user is an owner' do
+        user = create(:user)
+        project = create(:empty_project, :private, namespace: user.namespace)
 
-    before do
-      private_project.team << [user, Gitlab::Access::MASTER]
-    end
+        expect(user.authorized_projects(Gitlab::Access::REPORTER))
+          .to contain_exactly(project)
+      end
+
+      it 'includes projects for which the user is a master' do
+        user = create(:user)
+        project = create(:empty_project, :private)
 
-    subject { user.authorized_projects }
+        project.team << [user, Gitlab::Access::MASTER]
 
-    it { is_expected.to eq([private_project]) }
+        expect(user.authorized_projects(Gitlab::Access::REPORTER))
+          .to contain_exactly(project)
+      end
+    end
   end
 
   describe '#ci_authorized_runners' do
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index 3d5c19aeff35ba2795a51186727f5582166cde3f..831889afb6c894fc2b783acb38223e02292aad17 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -211,4 +211,27 @@ describe API::Helpers, api: true do
       expect(sudo_identifier).to eq(' 123')
     end
   end
+
+  describe '.to_boolean' do
+    it 'converts a valid string to a boolean' do
+      expect(to_boolean('true')).to be_truthy
+      expect(to_boolean('YeS')).to be_truthy
+      expect(to_boolean('t')).to be_truthy
+      expect(to_boolean('1')).to be_truthy
+      expect(to_boolean('ON')).to be_truthy
+      expect(to_boolean('FaLse')).to be_falsy
+      expect(to_boolean('F')).to be_falsy
+      expect(to_boolean('NO')).to be_falsy
+      expect(to_boolean('n')).to be_falsy
+      expect(to_boolean('0')).to be_falsy
+      expect(to_boolean('oFF')).to be_falsy
+    end
+
+    it 'converts an invalid string to nil' do
+      expect(to_boolean('fals')).to be_nil
+      expect(to_boolean('yeah')).to be_nil
+      expect(to_boolean('')).to be_nil
+      expect(to_boolean(nil)).to be_nil
+    end
+  end
 end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index b11ca26ee68563c35302d14fad5fcb0318c94e20..e8fd697965f3c9bdd86be29940ce5c5d44ecca10 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -32,6 +32,8 @@ describe API::API, api: true  do
       expect(json_response['name']).to eq(branch_name)
       expect(json_response['commit']['id']).to eq(branch_sha)
       expect(json_response['protected']).to eq(false)
+      expect(json_response['developers_can_push']).to eq(false)
+      expect(json_response['developers_can_merge']).to eq(false)
     end
 
     it "should return a 403 error if guest" do
@@ -45,14 +47,95 @@ describe API::API, api: true  do
     end
   end
 
-  describe "PUT /projects/:id/repository/branches/:branch/protect" do
-    it "should protect a single branch" do
+  describe 'PUT /projects/:id/repository/branches/:branch/protect' do
+    it 'protects a single branch' do
       put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
+
       expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq(branch_name)
+      expect(json_response['commit']['id']).to eq(branch_sha)
+      expect(json_response['protected']).to eq(true)
+      expect(json_response['developers_can_push']).to eq(false)
+      expect(json_response['developers_can_merge']).to eq(false)
+    end
+
+    it 'protects a single branch and developers can push' do
+      put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+        developers_can_push: true
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq(branch_name)
+      expect(json_response['commit']['id']).to eq(branch_sha)
+      expect(json_response['protected']).to eq(true)
+      expect(json_response['developers_can_push']).to eq(true)
+      expect(json_response['developers_can_merge']).to eq(false)
+    end
 
+    it 'protects a single branch and developers can merge' do
+      put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+        developers_can_merge: true
+
+      expect(response).to have_http_status(200)
       expect(json_response['name']).to eq(branch_name)
       expect(json_response['commit']['id']).to eq(branch_sha)
       expect(json_response['protected']).to eq(true)
+      expect(json_response['developers_can_push']).to eq(false)
+      expect(json_response['developers_can_merge']).to eq(true)
+    end
+
+    it 'protects a single branch and developers can push and merge' do
+      put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+        developers_can_push: true, developers_can_merge: true
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq(branch_name)
+      expect(json_response['commit']['id']).to eq(branch_sha)
+      expect(json_response['protected']).to eq(true)
+      expect(json_response['developers_can_push']).to eq(true)
+      expect(json_response['developers_can_merge']).to eq(true)
+    end
+
+    it 'protects a single branch and developers cannot push and merge' do
+      put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+        developers_can_push: 'tru', developers_can_merge: 'tr'
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq(branch_name)
+      expect(json_response['commit']['id']).to eq(branch_sha)
+      expect(json_response['protected']).to eq(true)
+      expect(json_response['developers_can_push']).to eq(false)
+      expect(json_response['developers_can_merge']).to eq(false)
+    end
+
+    context 'on a protected branch' do
+      let(:protected_branch) { 'foo' }
+
+      before do
+        project.repository.add_branch(user, protected_branch, 'master')
+        create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: protected_branch)
+      end
+
+      it 'updates that a developer can push' do
+        put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
+          developers_can_push: false, developers_can_merge: false
+
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq(protected_branch)
+        expect(json_response['protected']).to eq(true)
+        expect(json_response['developers_can_push']).to eq(false)
+        expect(json_response['developers_can_merge']).to eq(false)
+      end
+
+      it 'does not update that a developer can push' do
+        put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
+          developers_can_push: 'foobar', developers_can_merge: 'foo'
+
+        expect(response).to have_http_status(200)
+        expect(json_response['name']).to eq(protected_branch)
+        expect(json_response['protected']).to eq(true)
+        expect(json_response['developers_can_push']).to eq(true)
+        expect(json_response['developers_can_merge']).to eq(true)
+      end
     end
 
     it "should return a 404 error if branch not found" do
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index f5b39c3d69857663d00bc57e201d2eb76ad882a8..86a7b242fbe2ec143ca1742a670bf532b88f13b9 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -1,15 +1,15 @@
 require 'spec_helper'
 
-describe API::API, api: true  do
+describe API::API, api: true do
   include ApiHelpers
 
   let(:user) { create(:user) }
   let(:api_user) { user }
-  let(:user2) { create(:user) }
   let!(:project) { create(:project, creator_id: user.id) }
   let!(:developer) { create(:project_member, :developer, user: user, project: project) }
-  let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
-  let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) }
+  let(:reporter) { create(:project_member, :reporter, project: project) }
+  let(:guest) { create(:project_member, :guest, project: project) }
+  let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
   let!(:build) { create(:ci_build, pipeline: pipeline) }
 
   describe 'GET /projects/:id/builds ' do
@@ -172,10 +172,104 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
+    let(:api_user) { reporter.user }
+    let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+    def path_for_ref(ref = pipeline.ref, job = build.name)
+      api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
+    end
+
+    context 'when not logged in' do
+      let(:api_user) { nil }
+
+      before do
+        get path_for_ref
+      end
+
+      it 'gives 401' do
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context 'when logging as guest' do
+      let(:api_user) { guest.user }
+
+      before do
+        get path_for_ref
+      end
+
+      it 'gives 403' do
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context 'non-existing build' do
+      shared_examples 'not found' do
+        it { expect(response).to have_http_status(:not_found) }
+      end
+
+      context 'has no such ref' do
+        before do
+          get path_for_ref('TAIL', build.name)
+        end
+
+        it_behaves_like 'not found'
+      end
+
+      context 'has no such build' do
+        before do
+          get path_for_ref(pipeline.ref, 'NOBUILD')
+        end
+
+        it_behaves_like 'not found'
+      end
+    end
+
+    context 'find proper build' do
+      shared_examples 'a valid file' do
+        let(:download_headers) do
+          { 'Content-Transfer-Encoding' => 'binary',
+            'Content-Disposition' =>
+              "attachment; filename=#{build.artifacts_file.filename}" }
+        end
+
+        it { expect(response).to have_http_status(200) }
+        it { expect(response.headers).to include(download_headers) }
+      end
+
+      context 'with regular branch' do
+        before do
+          pipeline.update(ref: 'master',
+                          sha: project.commit('master').sha)
+
+          get path_for_ref('master')
+        end
+
+        it_behaves_like 'a valid file'
+      end
+
+      context 'with branch name containing slash' do
+        before do
+          pipeline.update(ref: 'improve/awesome',
+                          sha: project.commit('improve/awesome').sha)
+        end
+
+        before do
+          get path_for_ref('improve/awesome')
+        end
+
+        it_behaves_like 'a valid file'
+      end
+    end
+  end
+
   describe 'GET /projects/:id/builds/:build_id/trace' do
     let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
 
-    before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
+    before do
+      get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
+    end
 
     context 'authorized user' do
       it 'should return specific build trace' do
@@ -205,7 +299,7 @@ describe API::API, api: true  do
       end
 
       context 'user without :update_build permission' do
-        let(:api_user) { user2 }
+        let(:api_user) { reporter.user }
 
         it 'should not cancel build' do
           expect(response).to have_http_status(403)
@@ -237,7 +331,7 @@ describe API::API, api: true  do
       end
 
       context 'user without :update_build permission' do
-        let(:api_user) { user2 }
+        let(:api_user) { reporter.user }
 
         it 'should not retry build' do
           expect(response).to have_http_status(403)
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5219c8087915f787b1c95a86a6d8a0c17c3b8e21..e4ea850659899364107030693a126a1321bfb96f 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -73,9 +73,13 @@ describe API::API, api: true  do
     context "authorized user" do
       it "should return a commit by sha" do
         get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
         expect(response).to have_http_status(200)
         expect(json_response['id']).to eq(project.repository.commit.id)
         expect(json_response['title']).to eq(project.repository.commit.title)
+        expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
+        expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
+        expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
       end
 
       it "should return a 404 error if not found" do
diff --git a/spec/requests/api/deploy_keys.rb b/spec/requests/api/deploy_keys.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac42288bc3493d28e3ea21c3ac4dcfcdc9f7d37c
--- /dev/null
+++ b/spec/requests/api/deploy_keys.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe API::API, api: true  do
+  include ApiHelpers
+
+  let(:user)  { create(:user) }
+  let(:project) { create(:project, creator_id: user.id) }
+  let!(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
+  let(:admin) { create(:admin) }
+
+  describe 'GET /deploy_keys' do
+    before { admin }
+
+    context 'when unauthenticated' do
+      it 'should return authentication error' do
+        get api('/deploy_keys')
+        expect(response.status).to eq(401)
+      end
+    end
+
+    context 'when authenticated as non-admin user' do
+      it 'should return a 403 error' do
+        get api('/deploy_keys', user)
+        expect(response.status).to eq(403)
+      end
+    end
+
+    context 'when authenticated as admin' do
+      it 'should return all deploy keys' do
+        get api('/deploy_keys', admin)
+        expect(response.status).to eq(200)
+
+        expect(json_response).to be_an Array
+        expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..05e57905343bcfea499a17ceb987cf9b23ed8bde
--- /dev/null
+++ b/spec/requests/api/environments_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe API::API, api: true  do
+  include ApiHelpers
+
+  let(:user)          { create(:user) }
+  let(:non_member)    { create(:user) }
+  let(:project)       { create(:project, :private, namespace: user.namespace) }
+  let!(:environment)  { create(:environment, project: project) }
+
+  before do
+    project.team << [user, :master]
+  end
+
+  describe 'GET /projects/:id/environments' do
+    context 'as member of the project' do
+      it_behaves_like 'a paginated resources' do
+        let(:request) { get api("/projects/#{project.id}/environments", user) }
+      end
+
+      it 'returns project environments' do
+        get api("/projects/#{project.id}/environments", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(1)
+        expect(json_response.first['name']).to eq(environment.name)
+        expect(json_response.first['external_url']).to eq(environment.external_url)
+      end
+    end
+
+    context 'as non member' do
+      it 'returns a 404 status code' do
+        get api("/projects/#{project.id}/environments", non_member)
+
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe 'POST /projects/:id/environments' do
+    context 'as a member' do
+      it 'creates a environment with valid params' do
+        post api("/projects/#{project.id}/environments", user), name: "mepmep"
+
+        expect(response).to have_http_status(201)
+        expect(json_response['name']).to eq('mepmep')
+        expect(json_response['external']).to be nil
+      end
+
+      it 'requires name to be passed' do
+        post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com'
+
+        expect(response).to have_http_status(400)
+      end
+
+      it 'returns a 400 if environment already exists' do
+        post api("/projects/#{project.id}/environments", user), name: environment.name
+
+        expect(response).to have_http_status(400)
+      end
+    end
+
+    context 'a non member' do
+      it 'rejects the request' do
+        post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com'
+
+        expect(response).to have_http_status(404)
+      end
+
+      it 'returns a 400 when the required params are missing' do
+        post api("/projects/12345/environments", non_member), external_url: 'http://env.git.com'
+      end
+    end
+  end
+
+  describe 'PUT /projects/:id/environments/:environment_id' do
+    it 'returns a 200 if name and external_url are changed' do
+      url = 'https://mepmep.whatever.ninja'
+      put api("/projects/#{project.id}/environments/#{environment.id}", user),
+          name: 'Mepmep', external_url: url
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq('Mepmep')
+      expect(json_response['external_url']).to eq(url)
+    end
+
+    it "won't update the external_url if only the name is passed" do
+      url = environment.external_url
+      put api("/projects/#{project.id}/environments/#{environment.id}", user),
+          name: 'Mepmep'
+
+      expect(response).to have_http_status(200)
+      expect(json_response['name']).to eq('Mepmep')
+      expect(json_response['external_url']).to eq(url)
+    end
+
+    it 'returns a 404 if the environment does not exist' do
+      put api("/projects/#{project.id}/environments/12345", user)
+
+      expect(response).to have_http_status(404)
+    end
+  end
+
+  describe 'DELETE /projects/:id/environments/:environment_id' do
+    context 'as a master' do
+      it 'returns a 200 for an existing environment' do
+        delete api("/projects/#{project.id}/environments/#{environment.id}", user)
+
+        expect(response).to have_http_status(200)
+      end
+
+      it 'returns a 404 for non existing id' do
+        delete api("/projects/#{project.id}/environments/12345", user)
+
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 Not found')
+      end
+    end
+
+    context 'a non member' do
+      it 'rejects the request' do
+        delete api("/projects/#{project.id}/environments/#{environment.id}", non_member)
+
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 12f2cfa69426d4dce431f72ebcd0a70792c4a0d1..9d3d28e0b9151a940c05ed9076946b4d3ea6af67 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -531,10 +531,8 @@ describe API::API, api: true  do
 
   describe 'POST /projects/:id/issues with spam filtering' do
     before do
-      Grape::Endpoint.before_each do |endpoint|
-        allow(endpoint).to receive(:check_for_spam?).and_return(true)
-        allow(endpoint).to receive(:is_spam?).and_return(true)
-      end
+      allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true)
+      allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true)
     end
 
     let(:params) do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 152cd8028391b231325a257d2cb291a740905ea5..8c6a7e6529d0f555b10d646c7bf05396035ec221 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -396,7 +396,6 @@ describe API::API, api: true  do
       expect(json_response['alt']).to eq("dk")
       expect(json_response['url']).to start_with("/uploads/")
       expect(json_response['url']).to end_with("/dk.png")
-      expect(json_response['is_image']).to eq(true)
     end
   end
 
@@ -647,33 +646,33 @@ describe API::API, api: true  do
     let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
     let(:deploy_key) { deploy_keys_project.deploy_key }
 
-    describe 'GET /projects/:id/keys' do
+    describe 'GET /projects/:id/deploy_keys' do
       before { deploy_key }
 
       it 'should return array of ssh keys' do
-        get api("/projects/#{project.id}/keys", user)
+        get api("/projects/#{project.id}/deploy_keys", user)
         expect(response).to have_http_status(200)
         expect(json_response).to be_an Array
         expect(json_response.first['title']).to eq(deploy_key.title)
       end
     end
 
-    describe 'GET /projects/:id/keys/:key_id' do
+    describe 'GET /projects/:id/deploy_keys/:key_id' do
       it 'should return a single key' do
-        get api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
+        get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
         expect(response).to have_http_status(200)
         expect(json_response['title']).to eq(deploy_key.title)
       end
 
       it 'should return 404 Not Found with invalid ID' do
-        get api("/projects/#{project.id}/keys/404", user)
+        get api("/projects/#{project.id}/deploy_keys/404", user)
         expect(response).to have_http_status(404)
       end
     end
 
-    describe 'POST /projects/:id/keys' do
+    describe 'POST /projects/:id/deploy_keys' do
       it 'should not create an invalid ssh key' do
-        post api("/projects/#{project.id}/keys", user), { title: 'invalid key' }
+        post api("/projects/#{project.id}/deploy_keys", user), { title: 'invalid key' }
         expect(response).to have_http_status(400)
         expect(json_response['message']['key']).to eq([
           'can\'t be blank',
@@ -683,7 +682,7 @@ describe API::API, api: true  do
       end
 
       it 'should not create a key without title' do
-        post api("/projects/#{project.id}/keys", user), key: 'some key'
+        post api("/projects/#{project.id}/deploy_keys", user), key: 'some key'
         expect(response).to have_http_status(400)
         expect(json_response['message']['title']).to eq([
           'can\'t be blank',
@@ -694,22 +693,22 @@ describe API::API, api: true  do
       it 'should create new ssh key' do
         key_attrs = attributes_for :key
         expect do
-          post api("/projects/#{project.id}/keys", user), key_attrs
+          post api("/projects/#{project.id}/deploy_keys", user), key_attrs
         end.to change{ project.deploy_keys.count }.by(1)
       end
     end
 
-    describe 'DELETE /projects/:id/keys/:key_id' do
+    describe 'DELETE /projects/:id/deploy_keys/:key_id' do
       before { deploy_key }
 
       it 'should delete existing key' do
         expect do
-          delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
+          delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
         end.to change{ project.deploy_keys.count }.by(-1)
       end
 
       it 'should return 404 Not Found with invalid ID' do
-        delete api("/projects/#{project.id}/keys/404", user)
+        delete api("/projects/#{project.id}/deploy_keys/404", user)
         expect(response).to have_http_status(404)
       end
     end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 92a4fa216cd2b4d4c3fea0f5094837def558bdcb..3ccd0af652f7a05fb1f9c7743cca64c432482429 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -134,8 +134,7 @@ describe API::Todos, api: true do
         delete api('/todos', john_doe)
 
         expect(response.status).to eq(200)
-        expect(json_response).to be_an Array
-        expect(json_response.length).to eq(3)
+        expect(response.body).to eq('3')
         expect(pending_1.reload).to be_done
         expect(pending_2.reload).to be_done
         expect(pending_3.reload).to be_done
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index e7cbc3dd3a7a14136e72b5da41f1965ef4fa3071..cf1e8d9b514740d5e4544b60ba04c23dca420cec 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -73,12 +73,12 @@ describe Ci::API::API do
         post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
 
         expect(response).to have_http_status(201)
-        expect(json_response["variables"]).to eq([
+        expect(json_response["variables"]).to include(
           { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
           { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
           { "key" => "DB_NAME", "value" => "postgres", "public" => true },
           { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }
-        ])
+        )
       end
 
       it "returns variables for triggers" do
@@ -92,14 +92,14 @@ describe Ci::API::API do
         post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
 
         expect(response).to have_http_status(201)
-        expect(json_response["variables"]).to eq([
+        expect(json_response["variables"]).to include(
           { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
           { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
           { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
           { "key" => "DB_NAME", "value" => "postgres", "public" => true },
           { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
-          { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
-        ])
+          { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }
+        )
       end
 
       it "returns dependent builds" do
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 8b19936ae6d0ca5c74aeab2669e35161a19c04c0..69eeb45ed71589744275663176dae332d374d29b 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -1,6 +1,5 @@
 require 'spec_helper'
 
-# team_update_admin_user PUT    /admin/users/:id/team_update(.:format) admin/users#team_update
 #       block_admin_user PUT    /admin/users/:id/block(.:format)       admin/users#block
 #     unblock_admin_user PUT    /admin/users/:id/unblock(.:format)     admin/users#unblock
 #            admin_users GET    /admin/users(.:format)                 admin/users#index
@@ -11,10 +10,6 @@ require 'spec_helper'
 #                        PUT    /admin/users/:id(.:format)             admin/users#update
 #                        DELETE /admin/users/:id(.:format)             admin/users#destroy
 describe Admin::UsersController, "routing" do
-  it "to #team_update" do
-    expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1')
-  end
-
   it "to #block" do
     expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
   end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 620f328a1147c88226f04e7c4550da9439f9fe82..b941e78f983539666eb04cb5370fbb5bc67ce3f3 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do
   it 'to #archive format:tar.bz2' do
     expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
   end
-
-  it 'to #show' do
-    expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
-  end
 end
 
 describe Projects::BranchesController, 'routing' do
@@ -483,13 +479,16 @@ end
 describe Projects::NetworkController, 'routing' do
   it 'to #show' do
     expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
-    expect(get('/gitlab/gitlabhq/network/master.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+    expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+    expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
   end
 end
 
 describe Projects::GraphsController, 'routing' do
   it 'to #show' do
     expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+    expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+    expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
   end
 end
 
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 2c755919456cf9207e6ddfa94c8673fee31237b1..1d4df9197f68824fc2aac271d1f76f9276b5b230 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -116,12 +116,9 @@ describe HelpController, "routing" do
     expect(get(path)).to route_to('help#show',
                                   path: 'workflow/protected_branches/protected_branches1',
                                   format: 'png')
-    path = '/help/shortcuts'
-    expect(get(path)).to route_to('help#show',
-                                  path: 'shortcuts')
+    
     path = '/help/ui'
-    expect(get(path)).to route_to('help#show',
-                                  path: 'ui')
+    expect(get(path)).to route_to('help#ui')
   end
 end
 
@@ -179,18 +176,10 @@ describe Profiles::KeysController, "routing" do
     expect(post("/profile/keys")).to route_to('profiles/keys#create')
   end
 
-  it "to #edit" do
-    expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
-  end
-
   it "to #show" do
     expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
   end
 
-  it "to #update" do
-    expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
-  end
-
   it "to #destroy" do
     expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
   end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 47c0580e0f044339888250b46ceecf915ab4f947..ffa998dffc3e18ead1d88c46d8210361201bc163 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -7,6 +7,7 @@ describe GitPushService, services: true do
   let(:project)       { create :project }
 
   before do
+    project.team << [user, :master]
     @blankrev = Gitlab::Git::BLANK_SHA
     @oldrev = sample_commit.parent_id
     @newrev = sample_commit.id
@@ -172,7 +173,7 @@ describe GitPushService, services: true do
   describe "Push Event" do
     before do
       service = execute_service(project, user, @oldrev, @newrev, @ref )
-      @event = Event.last
+      @event = Event.find_by_action(Event::PUSHED)
       @push_data = service.push_data
     end
 
@@ -224,8 +225,10 @@ describe GitPushService, services: true do
       it "when pushing a branch for the first time" do
         expect(project).to receive(:execute_hooks)
         expect(project.default_branch).to eq("master")
-        expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: false })
         execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+        expect(project.protected_branches).not_to be_empty
+        expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER)
+        expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::MASTER)
       end
 
       it "when pushing a branch for the first time with default branch protection disabled" do
@@ -233,8 +236,8 @@ describe GitPushService, services: true do
 
         expect(project).to receive(:execute_hooks)
         expect(project.default_branch).to eq("master")
-        expect(project.protected_branches).not_to receive(:create)
         execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+        expect(project.protected_branches).to be_empty
       end
 
       it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
@@ -242,9 +245,12 @@ describe GitPushService, services: true do
 
         expect(project).to receive(:execute_hooks)
         expect(project.default_branch).to eq("master")
-        expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true, developers_can_merge: false })
 
-        execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
+        execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+
+        expect(project.protected_branches).not_to be_empty
+        expect(project.protected_branches.last.push_access_level.access_level).to eq(Gitlab::Access::DEVELOPER)
+        expect(project.protected_branches.last.merge_access_level.access_level).to eq(Gitlab::Access::MASTER)
       end
 
       it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
@@ -252,8 +258,10 @@ describe GitPushService, services: true do
 
         expect(project).to receive(:execute_hooks)
         expect(project.default_branch).to eq("master")
-        expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: true })
         execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+        expect(project.protected_branches).not_to be_empty
+        expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER)
+        expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::DEVELOPER)
       end
 
       it "when pushing new commits to existing branch" do
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index ba3a4dfc048fd742fa9aedd324130a53c790fdfe..321b54ac39df8b14f983c745a3e48f6bf4d2e80f 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -1,118 +1,106 @@
 require 'spec_helper'
 
 describe Issues::BulkUpdateService, services: true do
-  let(:user) { create(:user) }
-  let(:project) { Projects::CreateService.new(user, namespace: user.namespace, name: 'test').execute }
+  let(:user)    { create(:user) }
+  let(:project) { create(:empty_project, namespace: user.namespace) }
 
-  let!(:result) { Issues::BulkUpdateService.new(project, user, params).execute }
+  def bulk_update(issues, extra_params = {})
+    bulk_update_params = extra_params
+      .reverse_merge(issues_ids: Array(issues).map(&:id).join(','))
 
-  describe :close_issue do
-    let(:issues) { create_list(:issue, 5, project: project) }
-    let(:params) do
-      {
-        state_event: 'close',
-        issues_ids: issues.map(&:id).join(',')
-      }
-    end
+    Issues::BulkUpdateService.new(project, user, bulk_update_params).execute
+  end
+
+  describe 'close issues' do
+    let(:issues) { create_list(:issue, 2, project: project) }
 
     it 'succeeds and returns the correct number of issues updated' do
+      result = bulk_update(issues, state_event: 'close')
+
       expect(result[:success]).to be_truthy
       expect(result[:count]).to eq(issues.count)
     end
 
     it 'closes all the issues passed' do
+      bulk_update(issues, state_event: 'close')
+
       expect(project.issues.opened).to be_empty
       expect(project.issues.closed).not_to be_empty
     end
   end
 
-  describe :reopen_issues do
-    let(:issues) { create_list(:closed_issue, 5, project: project) }
-    let(:params) do
-      {
-        state_event: 'reopen',
-        issues_ids: issues.map(&:id).join(',')
-      }
-    end
+  describe 'reopen issues' do
+    let(:issues) { create_list(:closed_issue, 2, project: project) }
 
     it 'succeeds and returns the correct number of issues updated' do
+      result = bulk_update(issues, state_event: 'reopen')
+
       expect(result[:success]).to be_truthy
       expect(result[:count]).to eq(issues.count)
     end
 
     it 'reopens all the issues passed' do
+      bulk_update(issues, state_event: 'reopen')
+
       expect(project.issues.closed).to be_empty
       expect(project.issues.opened).not_to be_empty
     end
   end
 
   describe 'updating assignee' do
-    let(:issue) do
-      create(:issue, project: project) { |issue| issue.update_attributes(assignee: user) }
-    end
-
-    let(:params) do
-      {
-        assignee_id: assignee_id,
-        issues_ids: issue.id.to_s
-      }
-    end
+    let(:issue) { create(:issue, project: project, assignee: user) }
 
     context 'when the new assignee ID is a valid user' do
-      let(:new_assignee) { create(:user) }
-      let(:assignee_id) { new_assignee.id }
-
       it 'succeeds' do
+        result = bulk_update(issue, assignee_id: create(:user).id)
+
         expect(result[:success]).to be_truthy
         expect(result[:count]).to eq(1)
       end
 
       it 'updates the assignee to the use ID passed' do
-        expect(issue.reload.assignee).to eq(new_assignee)
+        assignee = create(:user)
+
+        expect { bulk_update(issue, assignee_id: assignee.id) }
+          .to change { issue.reload.assignee }.from(user).to(assignee)
       end
     end
 
     context 'when the new assignee ID is -1' do
-      let(:assignee_id) { -1 }
-
       it 'unassigns the issues' do
-        expect(issue.reload.assignee).to be_nil
+        expect { bulk_update(issue, assignee_id: -1) }
+          .to change { issue.reload.assignee }.to(nil)
       end
     end
 
     context 'when the new assignee ID is not present' do
-      let(:assignee_id) { nil }
-
       it 'does not unassign' do
-        expect(issue.reload.assignee).to eq(user)
+        expect { bulk_update(issue, assignee_id: nil) }
+          .not_to change { issue.reload.assignee }
       end
     end
   end
 
   describe 'updating milestones' do
-    let(:issue) { create(:issue, project: project) }
+    let(:issue)     { create(:issue, project: project) }
     let(:milestone) { create(:milestone, project: project) }
 
-    let(:params) do
-      {
-        issues_ids: issue.id.to_s,
-        milestone_id: milestone.id
-      }
-    end
-
     it 'succeeds' do
+      result = bulk_update(issue, milestone_id: milestone.id)
+
       expect(result[:success]).to be_truthy
       expect(result[:count]).to eq(1)
     end
 
     it 'updates the issue milestone' do
-      expect(project.issues.first.milestone).to eq(milestone)
+      expect { bulk_update(issue, milestone_id: milestone.id) }
+        .to change { issue.reload.milestone }.from(nil).to(milestone)
     end
   end
 
   describe 'updating labels' do
     def create_issue_with_labels(labels)
-      create(:issue, project: project) { |issue| issue.update_attributes(labels: labels) }
+      create(:labeled_issue, project: project, labels: labels)
     end
 
     let(:bug) { create(:label, project: project) }
@@ -129,15 +117,18 @@ describe Issues::BulkUpdateService, services: true do
     let(:add_labels) { [] }
     let(:remove_labels) { [] }
 
-    let(:params) do
+    let(:bulk_update_params) do
       {
-        label_ids: labels.map(&:id),
-        add_label_ids: add_labels.map(&:id),
+        label_ids:        labels.map(&:id),
+        add_label_ids:    add_labels.map(&:id),
         remove_label_ids: remove_labels.map(&:id),
-        issues_ids: issues.map(&:id).join(',')
       }
     end
 
+    before do
+      bulk_update(issues, bulk_update_params)
+    end
+
     context 'when label_ids are passed' do
       let(:issues) { [issue_all_labels, issue_no_labels] }
       let(:labels) { [bug, regression] }
@@ -263,40 +254,28 @@ describe Issues::BulkUpdateService, services: true do
     end
   end
 
-  describe :subscribe_issues do
-    let(:issues) { create_list(:issue, 5, project: project) }
-    let(:params) do
-      {
-        subscription_event: 'subscribe',
-        issues_ids: issues.map(&:id).join(',')
-      }
-    end
+  describe 'subscribe to issues' do
+    let(:issues) { create_list(:issue, 2, project: project) }
 
     it 'subscribes the given user' do
-      issues.each do |issue|
-        expect(issue.subscribed?(user)).to be_truthy
-      end
-    end
-  end
+      bulk_update(issues, subscription_event: 'subscribe')
 
-  describe :unsubscribe_issues do
-    let(:issues) { create_list(:closed_issue, 5, project: project) }
-    let(:params) do
-      {
-        subscription_event: 'unsubscribe',
-        issues_ids: issues.map(&:id).join(',')
-      }
+      expect(issues).to all(be_subscribed(user))
     end
+  end
 
-    before do
-      issues.each do |issue|
+  describe 'unsubscribe from issues' do
+    let(:issues) do
+      create_list(:closed_issue, 2, project: project) do |issue|
         issue.subscriptions.create(user: user, subscribed: true)
       end
     end
 
     it 'unsubscribes the given user' do
+      bulk_update(issues, subscription_event: 'unsubscribe')
+
       issues.each do |issue|
-        expect(issue.subscribed?(user)).to be_falsey
+        expect(issue).not_to be_subscribed(user)
       end
     end
   end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index f5bf3c1e3673d4b7c31f8151afaf7c1304e4518f..8ffebcac6986e738dad562eef552decb132bd8b8 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -75,6 +75,17 @@ describe MergeRequests::MergeService, services: true do
 
         expect(merge_request.merge_error).to eq("error")
       end
+
+      it 'aborts if there is a merge conflict' do
+        allow_any_instance_of(Repository).to receive(:merge).and_return(false)
+        allow(service).to receive(:execute_hooks)
+
+        service.execute(merge_request)
+
+        expect(merge_request.open?).to be_truthy
+        expect(merge_request.merge_commit_sha).to be_nil
+        expect(merge_request.merge_error).to eq("Conflicts detected during merge")
+      end
     end
   end
 end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index ce643b3f860be7505ad8f04447eeeb12f159cdb6..781ee7ffed3ed113fc6a3d64fc4dcb26f72f5475 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -57,7 +57,7 @@ describe MergeRequests::RefreshService, services: true do
 
       it 'should execute hooks with update action' do
         expect(refresh_service).to have_received(:execute_hooks).
-          with(@merge_request, 'update')
+          with(@merge_request, 'update', @oldrev)
       end
 
       it { expect(@merge_request.notes).not_to be_empty }
@@ -113,7 +113,7 @@ describe MergeRequests::RefreshService, services: true do
 
       it 'should execute hooks with update action' do
         expect(refresh_service).to have_received(:execute_hooks).
-          with(@fork_merge_request, 'update')
+          with(@fork_merge_request, 'update', @oldrev)
       end
 
       it { expect(@merge_request.notes).to be_empty }
@@ -158,7 +158,7 @@ describe MergeRequests::RefreshService, services: true do
 
       it 'refreshes the merge request' do
         expect(refresh_service).to receive(:execute_hooks).
-                                       with(@fork_merge_request, 'update')
+                                       with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA)
         allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
 
         refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f252e2c590234282da05c0e445d5757bf7493774..122a7cea2a1904fd07e41a8deb4e38a909e7df41 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -35,8 +35,6 @@ describe Projects::DownloadService, services: true do
 
         it { expect(@link_to_file).to have_key(:alt) }
         it { expect(@link_to_file).to have_key(:url) }
-        it { expect(@link_to_file).to have_key(:is_image) }
-        it { expect(@link_to_file[:is_image]).to be true }
         it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
         it { expect(@link_to_file[:alt]).to eq('rails_sample') }
       end
@@ -49,8 +47,6 @@ describe Projects::DownloadService, services: true do
 
         it { expect(@link_to_file).to have_key(:alt) }
         it { expect(@link_to_file).to have_key(:url) }
-        it { expect(@link_to_file).to have_key(:is_image) }
-        it { expect(@link_to_file[:is_image]).to be false }
         it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
         it { expect(@link_to_file[:alt]).to eq('doc_sample.txt') }
       end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index 9268a9fb1a253e4c27394e9e2dbddfd21b8993d3..c42eeba4b9ca5a4ef25ee7a9d6e1ef7d52992c1d 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -15,9 +15,7 @@ describe Projects::UploadService, services: true do
 
       it { expect(@link_to_file).to have_key(:alt) }
       it { expect(@link_to_file).to have_key(:url) }
-      it { expect(@link_to_file).to have_key(:is_image) }
       it { expect(@link_to_file).to have_value('banana_sample') }
-      it { expect(@link_to_file[:is_image]).to equal(true) }
       it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
     end
 
@@ -31,8 +29,6 @@ describe Projects::UploadService, services: true do
       it { expect(@link_to_file).to have_key(:alt) }
       it { expect(@link_to_file).to have_key(:url) }
       it { expect(@link_to_file).to have_value('dk') }
-      it { expect(@link_to_file).to have_key(:is_image) }
-      it { expect(@link_to_file[:is_image]).to equal(true) }
       it { expect(@link_to_file[:url]).to match('dk.png') }
     end
 
@@ -44,9 +40,7 @@ describe Projects::UploadService, services: true do
 
       it { expect(@link_to_file).to have_key(:alt) }
       it { expect(@link_to_file).to have_key(:url) }
-      it { expect(@link_to_file).to have_key(:is_image) }
       it { expect(@link_to_file).to have_value('rails_sample') }
-      it { expect(@link_to_file[:is_image]).to equal(true) }
       it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
     end
 
@@ -58,9 +52,7 @@ describe Projects::UploadService, services: true do
 
       it { expect(@link_to_file).to have_key(:alt) }
       it { expect(@link_to_file).to have_key(:url) }
-      it { expect(@link_to_file).to have_key(:is_image) }
       it { expect(@link_to_file).to have_value('doc_sample.txt') }
-      it { expect(@link_to_file[:is_image]).to equal(false) }
       it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
     end
 
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..842585f9e54e2ddfd7dce4076f587e929ad4fafb
--- /dev/null
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+describe RepositoryArchiveCleanUpService, services: true do
+  describe '#execute' do
+    subject(:service) { described_class.new }
+
+    context 'when the downloads directory does not exist' do
+      it 'does not remove any archives' do
+        path = '/invalid/path/'
+        stub_repository_downloads_path(path)
+
+        expect(File).to receive(:directory?).with(path).and_return(false)
+        expect(service).not_to receive(:clean_up_old_archives)
+        expect(service).not_to receive(:clean_up_empty_directories)
+
+        service.execute
+      end
+    end
+
+    context 'when the downloads directory exists' do
+      shared_examples 'invalid archive files' do |dirname, extensions, mtime|
+        it 'does not remove files and directoy' do
+          in_directory_with_files(dirname, extensions, mtime) do |dir, files|
+            service.execute
+
+            files.each { |file| expect(File.exist?(file)).to eq true }
+            expect(File.directory?(dir)).to eq true
+          end
+        end
+      end
+
+      it 'removes files older than 2 hours that matches valid archive extensions' do
+        in_directory_with_files('sample.git', %w[tar tar.bz2 tar.gz zip], 2.hours) do |dir, files|
+          service.execute
+
+          files.each { |file| expect(File.exist?(file)).to eq false }
+          expect(File.directory?(dir)).to eq false
+        end
+      end
+
+      context 'with files older than 2 hours that does not matches valid archive extensions' do
+        it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 2.hours
+      end
+
+      context 'with files older than 2 hours inside invalid directories' do
+        it_behaves_like 'invalid archive files', 'john_doe/sample.git', %w[conf rb tar tar.gz], 2.hours
+      end
+
+      context 'with files newer than 2 hours that matches valid archive extensions' do
+        it_behaves_like 'invalid archive files', 'sample.git', %w[tar tar.bz2 tar.gz zip], 1.hour
+      end
+
+      context 'with files newer than 2 hours that does not matches valid archive extensions' do
+        it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 1.hour
+      end
+
+      context 'with files newer than 2 hours inside invalid directories' do
+        it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb tar tar.gz], 1.hour
+      end
+    end
+
+    def in_directory_with_files(dirname, extensions, mtime)
+      Dir.mktmpdir do |tmpdir|
+        stub_repository_downloads_path(tmpdir)
+        dir = File.join(tmpdir, dirname)
+        files = create_temporary_files(dir, extensions, mtime)
+
+        yield(dir, files)
+      end
+    end
+
+    def stub_repository_downloads_path(path)
+      allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path)
+    end
+
+    def create_temporary_files(dir, extensions, mtime)
+      FileUtils.mkdir_p(dir)
+      FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime)
+    end
+  end
+end
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6f8f7109e143245241dd88b69a402c97918ff3e5
--- /dev/null
+++ b/spec/simplecov_env.rb
@@ -0,0 +1,54 @@
+require 'simplecov'
+
+module SimpleCovEnv
+  extend self
+
+  def start!
+    return unless ENV['SIMPLECOV']
+
+    configure_profile
+    configure_job
+
+    SimpleCov.start
+  end
+
+  def configure_job
+    SimpleCov.configure do
+      if ENV['CI_BUILD_NAME']
+        coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}"
+        command_name ENV['CI_BUILD_NAME']
+      end
+
+      if ENV['CI']
+        SimpleCov.at_exit do
+          # In CI environment don't generate formatted reports
+          # Only generate .resultset.json
+          SimpleCov.result
+        end
+      end
+    end
+  end
+
+  def configure_profile
+    SimpleCov.configure do
+      load_profile 'test_frameworks'
+      track_files '{app,lib}/**/*.rb'
+
+      add_filter '/vendor/ruby/'
+      add_filter 'config/initializers/'
+
+      add_group 'Controllers', 'app/controllers'
+      add_group 'Models', 'app/models'
+      add_group 'Mailers', 'app/mailers'
+      add_group 'Helpers', 'app/helpers'
+      add_group 'Workers', %w(app/jobs app/workers)
+      add_group 'Libraries', 'lib'
+      add_group 'Services', 'app/services'
+      add_group 'Finders', 'app/finders'
+      add_group 'Uploaders', 'app/uploaders'
+      add_group 'Validators', 'app/validators'
+
+      merge_timeout 7200
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3638dcbb2d3568b26e7b323a31e8d05b78a631ce..4f3aacf55be9dbe4740f5c28e5f5dff58c2ce0b0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,5 @@
-if ENV['SIMPLECOV']
-  require 'simplecov'
-  SimpleCov.start :rails
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
 
 ENV["RAILS_ENV"] ||= 'test'
 
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index 1b3cafb497c176a64495fc54bedcaff8659f3607..68b196d9033d14a359805b24de4ede8cd57a9b1c 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -24,8 +24,11 @@ module ApiHelpers
       (path.index('?') ? '' : '?') +
 
       # Append private_token if given a User object
-      (user.respond_to?(:private_token) ?
-        "&private_token=#{user.private_token}" : "")
+      if user.respond_to?(:private_token)
+        "&private_token=#{user.private_token}"
+      else
+        ''
+      end
   end
 
   def ci_api(path, user = nil)
@@ -35,8 +38,11 @@ module ApiHelpers
       (path.index('?') ? '' : '?') +
 
       # Append private_token if given a User object
-      (user.respond_to?(:private_token) ?
-        "&private_token=#{user.private_token}" : "")
+      if user.respond_to?(:private_token)
+        "&private_token=#{user.private_token}"
+      else
+        ''
+      end
   end
 
   def json_response
diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8524179374334dc15ab84ea4243c044877718d16
--- /dev/null
+++ b/spec/support/issue_helpers.rb
@@ -0,0 +1,13 @@
+module IssueHelpers
+  def visit_issues(project, opts = {})
+    visit namespace_project_issues_path project.namespace, project, opts
+  end
+
+  def first_issue
+    page.all('ul.issues-list > li').first.text
+  end
+
+  def last_issue
+    page.all('ul.issues-list > li').last.text
+  end
+end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index e005058ba5b6065d5107101638dcd5ea0cf79f76..8c98b1f988cb46df98ed1c1d351f899375bd1d83 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -178,6 +178,17 @@ module MarkdownMatchers
       expect(actual).to have_selector('span.idiff.deletion', count: 2)
     end
   end
+
+  # VideoLinkFilter
+  matcher :parse_video_links do
+    set_default_markdown_messages
+
+    match do |actual|
+      video = actual.at_css('video')
+
+      expect(video['src']).to end_with('/assets/videos/gitlab-demo.mp4')
+    end
+  end
 end
 
 # Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d5801c8272f9bda104cfe7542a5d68d5bbddc3d2
--- /dev/null
+++ b/spec/support/merge_request_helpers.rb
@@ -0,0 +1,13 @@
+module MergeRequestHelpers
+  def visit_merge_requests(project, opts = {})
+    visit namespace_project_merge_requests_path project.namespace, project, opts
+  end
+
+  def first_merge_request
+    page.all('ul.mr-list > li').first.text
+  end
+
+  def last_merge_request
+    page.all('ul.mr-list > li').last.text
+  end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 83f2ad96fd8bad4071b573c32b87dd03ce1d8788..1c0c66969e3bece8bd82183029bc4857a449c60f 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -6,6 +6,7 @@ module TestEnv
   # When developing the seed repository, comment out the branch you will modify.
   BRANCH_SHA = {
     'empty-branch'          => '7efb185',
+    'ends-with.json'        => '98b0d8b3',
     'flatten-dir'           => 'e56497b',
     'feature'               => '0b4bc9a',
     'feature_conflict'      => 'bb5206f',
@@ -20,7 +21,9 @@ module TestEnv
     'gitattributes'         => '5a62481',
     'expand-collapse-diffs' => '4842455',
     'expand-collapse-files' => '025db92',
-    'expand-collapse-lines' => '238e82d'
+    'expand-collapse-lines' => '238e82d',
+    'video'                 => '8879059',
+    'crlf-diff'             => '5938907'
   }
 
   # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb
index 69b2b9b6d5bf58ff72d1e4ebe177ec0a6f757cc6..1a3bbb9c8cc3fd87bb29a2ccad686a65a5c214dc 100644
--- a/spec/teaspoon_env.rb
+++ b/spec/teaspoon_env.rb
@@ -38,7 +38,7 @@ Teaspoon.configure do |config|
 
     # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These
     # files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
-    suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
+    suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.es6,es6}"
 
     # Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
     # suite.javascripts = []
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e8300abed5dc201ecf5cc4e01e2afe0d3a661a9d
--- /dev/null
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe FileUploader do
+  let(:project) { create(:project) }
+
+  before do
+    @previous_enable_processing = FileUploader.enable_processing
+    FileUploader.enable_processing = false
+    @uploader = FileUploader.new(project)
+  end
+
+  after do
+    FileUploader.enable_processing = @previous_enable_processing
+    @uploader.remove!
+  end
+
+  describe '#image_or_video?' do
+    context 'given an image file' do
+      before do
+        @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')))
+      end
+
+      it 'detects an image based on file extension' do
+        expect(@uploader.image_or_video?).to be true
+      end
+    end
+
+    context 'given an video file' do
+      before do
+        video_file = File.new(Rails.root.join('spec', 'fixtures', 'video_sample.mp4'))
+        @uploader.store!(video_file)
+      end
+
+      it 'detects a video based on file extension' do
+        expect(@uploader.image_or_video?).to be true
+      end
+    end
+
+    it 'does not return image_or_video? for other types' do
+      @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'doc_sample.txt')))
+
+      expect(@uploader.image_or_video?).to be false
+    end
+  end
+end
diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dae858a52f6b3bf1f3bb7016698137360117b6b0
--- /dev/null
+++ b/spec/views/admin/dashboard/index.html.haml_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'admin/dashboard/index.html.haml' do
+  include Devise::TestHelpers
+
+  before do
+    assign(:projects, create_list(:empty_project, 1))
+    assign(:users, create_list(:user, 1))
+    assign(:groups, create_list(:group, 1))
+
+    allow(view).to receive(:admin?).and_return(true)
+  end
+
+  it "shows version of GitLab Workhorse" do
+    render
+
+    expect(rendered).to have_content 'GitLab Workhorse'
+    expect(rendered).to have_content Gitlab::Workhorse.version
+  end
+end
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 42220a20c75bef6f547687af1b4cdaa08f26d93d..464051063d8903ba0d7e2ae6e54e48149b4d5d2a 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -44,9 +44,29 @@ describe 'projects/builds/show' do
 
     it 'shows commit title and not show commit message' do
       render
-      
+
       expect(rendered).to have_css('p.build-light-text.append-bottom-0',
         text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
     end
   end
+
+  describe 'shows trigger variables in sidebar' do
+    let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
+
+    before do
+      build.trigger_request = trigger_request
+      render
+    end
+
+    it 'shows trigger variables in separate lines' do
+      expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1'))
+      expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2'))
+    end
+  end
+
+  private
+
+  def variable_regexp(key, value)
+    /\A#{Regexp.escape("#{key}=#{value}")}\Z/
+  end
 end
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..78af61f15a719e9a02f74d6cd57be4b8650f7bea
--- /dev/null
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'projects/issues/_related_branches' do
+  include Devise::TestHelpers
+
+  let(:project) { create(:project) }
+  let(:branch) { project.repository.find_branch('feature') }
+  let!(:pipeline) { create(:ci_pipeline, project: project, sha: branch.target.id, ref: 'feature') }
+
+  before do
+    assign(:project, project)
+    assign(:related_branches, ['feature'])
+
+    render
+  end
+
+  it 'shows the related branches with their build status' do
+    expect(rendered).to match('feature')
+    expect(rendered).to have_css('.related-branch-ci-status')
+  end
+end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0f3fc1ee1ac2b2f831cb76ae0d2d00e9fa504444
--- /dev/null
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe 'projects/tree/show' do
+  include Devise::TestHelpers
+
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+
+  before do
+    assign(:project, project)
+    assign(:repository, repository)
+
+    allow(view).to receive(:can?).and_return(true)
+    allow(view).to receive(:can_collaborate_with_project?).and_return(true)
+  end
+
+  context 'for branch names ending on .json' do
+    let(:ref) { 'ends-with.json' }
+    let(:commit) { repository.commit(ref) }
+    let(:path) { '' }
+    let(:tree) { repository.tree(commit.id, path) }
+
+    before do
+      assign(:ref, ref)
+      assign(:commit, commit)
+      assign(:id, commit.id)
+      assign(:tree, tree)
+      assign(:path, path)
+    end
+
+    it 'displays correctly' do
+      render
+      expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
+      expect(rendered).to have_css('.readme-holder .file-content', text: ref)
+    end
+  end
+end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index de40a6f78af61754e0206c7aab04684a450c7cf4..fe70501eeac32314917ba9cc358bcaa73b54cf19 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -17,7 +17,7 @@ describe EmailReceiverWorker do
 
     context "when an error occurs" do
       before do
-        allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError)
+        allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError)
       end
 
       it "sends out a rejection email" do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 439da765c2c0522af8a8296283b769c428d8e7a8..796751efe8dcf9a1411df00aea63e7d12371fed1 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -12,6 +12,42 @@ describe EmailsOnPushWorker do
   subject { EmailsOnPushWorker.new }
 
   describe "#perform" do
+    context "when push is a new branch" do
+      let(:email) { ActionMailer::Base.deliveries.last }
+
+      before do
+        data_new_branch = data.stringify_keys.merge("before" => Gitlab::Git::BLANK_SHA)
+
+        subject.perform(project.id, recipients, data_new_branch)
+      end
+
+      it "sends a mail with the correct subject" do
+        expect(email.subject).to include("Pushed new branch")
+      end
+
+      it "sends the mail to the correct recipient" do
+        expect(email.to).to eq([user.email])
+      end
+    end
+
+    context "when push is a deleted branch" do
+      let(:email) { ActionMailer::Base.deliveries.last }
+
+      before do
+        data_deleted_branch = data.stringify_keys.merge("after" => Gitlab::Git::BLANK_SHA)
+
+        subject.perform(project.id, recipients, data_deleted_branch)
+      end
+
+      it "sends a mail with the correct subject" do
+        expect(email.subject).to include("Deleted branch")
+      end
+
+      it "sends the mail to the correct recipient" do
+        expect(email.to).to eq([user.email])
+      end
+    end
+
     context "when there are no errors in sending" do
       let(:email) { ActionMailer::Base.deliveries.last }
 
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 5f762282b5ea7ddae6a61adc14e006fe1c8780e3..60605460adbebde182e448888bf0a1ee41f09651 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -14,21 +14,24 @@ describe RepositoryForkWorker do
   describe "#perform" do
     it "creates a new repository from a fork" do
       expect(shell).to receive(:fork_repository).with(
-        project.repository_storage_path,
+        '/test/path',
         project.path_with_namespace,
+        project.repository_storage_path,
         fork_project.namespace.path
       ).and_return(true)
 
       subject.perform(
         project.id,
+        '/test/path',
         project.path_with_namespace,
         fork_project.namespace.path)
     end
 
     it 'flushes various caches' do
       expect(shell).to receive(:fork_repository).with(
-        project.repository_storage_path,
+        '/test/path',
         project.path_with_namespace,
+        project.repository_storage_path,
         fork_project.namespace.path
       ).and_return(true)
 
@@ -38,7 +41,7 @@ describe RepositoryForkWorker do
       expect_any_instance_of(Repository).to receive(:expire_exists_cache).
         and_call_original
 
-      subject.perform(project.id, project.path_with_namespace,
+      subject.perform(project.id, '/test/path', project.path_with_namespace,
                       fork_project.namespace.path)
     end
 
@@ -49,6 +52,7 @@ describe RepositoryForkWorker do
 
       subject.perform(
         project.id,
+        '/test/path',
         project.path_with_namespace,
         fork_project.namespace.path)
     end
diff --git a/vendor/assets/javascripts/task_list.js b/vendor/assets/javascripts/task_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc451506b6a71eab728b3480efdcac00ec2d9334
--- /dev/null
+++ b/vendor/assets/javascripts/task_list.js
@@ -0,0 +1,119 @@
+
+/*= provides tasklist:enabled */
+
+
+/*= provides tasklist:disabled */
+
+
+/*= provides tasklist:change */
+
+
+/*= provides tasklist:changed */
+
+(function() {
+  var codeFencesPattern, complete, completePattern, disableTaskList, disableTaskLists, enableTaskList, enableTaskLists, escapePattern, incomplete, incompletePattern, itemPattern, itemsInParasPattern, updateTaskList, updateTaskListItem,
+    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+  incomplete = "[ ]";
+
+  complete = "[x]";
+
+  escapePattern = function(str) {
+    return str.replace(/([\[\]])/g, "\\$1").replace(/\s/, "\\s").replace("x", "[xX]");
+  };
+
+  incompletePattern = RegExp("" + (escapePattern(incomplete)));
+
+  completePattern = RegExp("" + (escapePattern(complete)));
+
+  itemPattern = RegExp("^(?:\\s*(?:>\\s*)*(?:[-+*]|(?:\\d+\\.)))\\s*(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ")\\s+(?!\\(.*?\\))(?=(?:\\[.*?\\]\\s*(?:\\[.*?\\]|\\(.*?\\))\\s*)*(?:[^\\[]|$))");
+
+  codeFencesPattern = /^`{3}(?:\s*\w+)?[\S\s].*[\S\s]^`{3}$/mg;
+
+  itemsInParasPattern = RegExp("^(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ").+$", "g");
+
+  updateTaskListItem = function(source, itemIndex, checked) {
+    var clean, index, line, result;
+    clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').replace(itemsInParasPattern, '').split("\n");
+    index = 0;
+    result = (function() {
+      var i, len, ref, results;
+      ref = source.split("\n");
+      results = [];
+      for (i = 0, len = ref.length; i < len; i++) {
+        line = ref[i];
+        if (indexOf.call(clean, line) >= 0 && line.match(itemPattern)) {
+          index += 1;
+          if (index === itemIndex) {
+            line = checked ? line.replace(incompletePattern, complete) : line.replace(completePattern, incomplete);
+          }
+        }
+        results.push(line);
+      }
+      return results;
+    })();
+    return result.join("\n");
+  };
+
+  updateTaskList = function($item) {
+    var $container, $field, checked, event, index;
+    $container = $item.closest('.js-task-list-container');
+    $field = $container.find('.js-task-list-field');
+    index = 1 + $container.find('.task-list-item-checkbox').index($item);
+    checked = $item.prop('checked');
+    event = $.Event('tasklist:change');
+    $field.trigger(event, [index, checked]);
+    if (!event.isDefaultPrevented()) {
+      $field.val(updateTaskListItem($field.val(), index, checked));
+      $field.trigger('change');
+      return $field.trigger('tasklist:changed', [index, checked]);
+    }
+  };
+
+  $(document).on('change', '.task-list-item-checkbox', function() {
+    return updateTaskList($(this));
+  });
+
+  enableTaskList = function($container) {
+    if ($container.find('.js-task-list-field').length > 0) {
+      $container.find('.task-list-item').addClass('enabled').find('.task-list-item-checkbox').attr('disabled', null);
+      return $container.addClass('is-task-list-enabled').trigger('tasklist:enabled');
+    }
+  };
+
+  enableTaskLists = function($containers) {
+    var container, i, len, results;
+    results = [];
+    for (i = 0, len = $containers.length; i < len; i++) {
+      container = $containers[i];
+      results.push(enableTaskList($(container)));
+    }
+    return results;
+  };
+
+  disableTaskList = function($container) {
+    $container.find('.task-list-item').removeClass('enabled').find('.task-list-item-checkbox').attr('disabled', 'disabled');
+    return $container.removeClass('is-task-list-enabled').trigger('tasklist:disabled');
+  };
+
+  disableTaskLists = function($containers) {
+    var container, i, len, results;
+    results = [];
+    for (i = 0, len = $containers.length; i < len; i++) {
+      container = $containers[i];
+      results.push(disableTaskList($(container)));
+    }
+    return results;
+  };
+
+  $.fn.taskList = function(method) {
+    var $container, methods;
+    $container = $(this).closest('.js-task-list-container');
+    methods = {
+      enable: enableTaskLists,
+      disable: disableTaskLists
+    };
+    return methods[method || 'enable']($container);
+  };
+
+}).call(this);
diff --git a/vendor/assets/javascripts/task_list.js.coffee b/vendor/assets/javascripts/task_list.js.coffee
deleted file mode 100644
index 584751af8ea8e100974efdc5a06137d151626858..0000000000000000000000000000000000000000
--- a/vendor/assets/javascripts/task_list.js.coffee
+++ /dev/null
@@ -1,258 +0,0 @@
-# The MIT License (MIT)
-#
-# Copyright (c) 2014 GitHub, Inc.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-# TaskList Behavior
-#
-#= provides tasklist:enabled
-#= provides tasklist:disabled
-#= provides tasklist:change
-#= provides tasklist:changed
-#
-#
-# Enables Task List update behavior.
-#
-# ### Example Markup
-#
-#   <div class="js-task-list-container">
-#     <ul class="task-list">
-#       <li class="task-list-item">
-#         <input type="checkbox" class="js-task-list-item-checkbox" disabled />
-#         text
-#       </li>
-#     </ul>
-#     <form>
-#       <textarea class="js-task-list-field">- [ ] text</textarea>
-#     </form>
-#   </div>
-#
-# ### Specification
-#
-# TaskLists MUST be contained in a `(div).js-task-list-container`.
-#
-# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
-#
-# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
-# `disabled` by default.
-#
-# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
-# `value` attribute is the source (Markdown) to be udpated. The source MUST
-# follow the syntax guidelines.
-#
-# TaskList updates trigger `tasklist:change` events. If the change is
-# successful, `tasklist:changed` is fired. The change can be canceled.
-#
-# jQuery is required.
-#
-# ### Methods
-#
-# `.taskList('enable')` or `.taskList()`
-#
-# Enables TaskList updates for the container.
-#
-# `.taskList('disable')`
-#
-# Disables TaskList updates for the container.
-#
-## ### Events
-#
-# `tasklist:enabled`
-#
-# Fired when the TaskList is enabled.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-container`
-#
-# `tasklist:disabled`
-#
-# Fired when the TaskList is disabled.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-container`
-#
-# `tasklist:change`
-#
-# Fired before the TaskList item change takes affect.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** Yes
-# * **Target** `.js-task-list-field`
-#
-# `tasklist:changed`
-#
-# Fired once the TaskList item change has taken affect.
-#
-# * **Synchronicity** Sync
-# * **Bubbles** Yes
-# * **Cancelable** No
-# * **Target** `.js-task-list-field`
-#
-# ### NOTE
-#
-# Task list checkboxes are rendered as disabled by default because rendered
-# user content is cached without regard for the viewer.
-
-incomplete = "[ ]"
-complete   = "[x]"
-
-# Escapes the String for regular expression matching.
-escapePattern = (str) ->
-  str.
-    replace(/([\[\]])/g, "\\$1"). # escape square brackets
-    replace(/\s/, "\\s").         # match all white space
-    replace("x", "[xX]")          # match all cases
-
-incompletePattern = ///
-  #{escapePattern(incomplete)}
-///
-completePattern = ///
-  #{escapePattern(complete)}
-///
-
-# Pattern used to identify all task list items.
-# Useful when you need iterate over all items.
-itemPattern = ///
-  ^
-  (?:                     # prefix, consisting of
-    \s*                   # optional leading whitespace
-    (?:>\s*)*             # zero or more blockquotes
-    (?:[-+*]|(?:\d+\.))   # list item indicator
-  )
-  \s*                     # optional whitespace prefix
-  (                       # checkbox
-    #{escapePattern(complete)}|
-    #{escapePattern(incomplete)}
-  )
-  \s+                     # is followed by whitespace
-  (?!
-    \(.*?\)               # is not part of a [foo](url) link
-  )
-  (?=                     # and is followed by zero or more links
-    (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
-    (?:[^\[]|$)           # and either a non-link or the end of the string
-  )
-///
-
-# Used to filter out code fences from the source for comparison only.
-# http://rubular.com/r/x5EwZVrloI
-# Modified slightly due to issues with JS
-codeFencesPattern = ///
-  ^`{3}           # ```
-    (?:\s*\w+)?   # followed by optional language
-    [\S\s]        # whitespace
-  .*              # code
-  [\S\s]          # whitespace
-  ^`{3}$          # ```
-///mg
-
-# Used to filter out potential mismatches (items not in lists).
-# http://rubular.com/r/OInl6CiePy
-itemsInParasPattern = ///
-  ^
-  (
-    #{escapePattern(complete)}|
-    #{escapePattern(incomplete)}
-  )
-  .+
-  $
-///g
-
-# Given the source text, updates the appropriate task list item to match the
-# given checked value.
-#
-# Returns the updated String text.
-updateTaskListItem = (source, itemIndex, checked) ->
-  clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
-    replace(itemsInParasPattern, '').split("\n")
-  index = 0
-  result = for line in source.split("\n")
-    if line in clean && line.match(itemPattern)
-      index += 1
-      if index == itemIndex
-        line =
-          if checked
-            line.replace(incompletePattern, complete)
-          else
-            line.replace(completePattern, incomplete)
-    line
-  result.join("\n")
-
-# Updates the $field value to reflect the state of $item.
-# Triggers the `tasklist:change` event before the value has changed, and fires
-# a `tasklist:changed` event once the value has changed.
-updateTaskList = ($item) ->
-  $container = $item.closest '.js-task-list-container'
-  $field     = $container.find '.js-task-list-field'
-  index      = 1 + $container.find('.task-list-item-checkbox').index($item)
-  checked    = $item.prop 'checked'
-
-  event = $.Event 'tasklist:change'
-  $field.trigger event, [index, checked]
-
-  unless event.isDefaultPrevented()
-    $field.val updateTaskListItem($field.val(), index, checked)
-    $field.trigger 'change'
-    $field.trigger 'tasklist:changed', [index, checked]
-
-# When the task list item checkbox is updated, submit the change
-$(document).on 'change', '.task-list-item-checkbox', ->
-  updateTaskList $(this)
-
-# Enables TaskList item changes.
-enableTaskList = ($container) ->
-  if $container.find('.js-task-list-field').length > 0
-    $container.
-      find('.task-list-item').addClass('enabled').
-      find('.task-list-item-checkbox').attr('disabled', null)
-    $container.addClass('is-task-list-enabled').
-      trigger 'tasklist:enabled'
-
-# Enables a collection of TaskList containers.
-enableTaskLists = ($containers) ->
-  for container in $containers
-    enableTaskList $(container)
-
-# Disable TaskList item changes.
-disableTaskList = ($container) ->
-  $container.
-    find('.task-list-item').removeClass('enabled').
-    find('.task-list-item-checkbox').attr('disabled', 'disabled')
-  $container.removeClass('is-task-list-enabled').
-    trigger 'tasklist:disabled'
-
-# Disables a collection of TaskList containers.
-disableTaskLists = ($containers) ->
-  for container in $containers
-    disableTaskList $(container)
-
-$.fn.taskList = (method) ->
-  $container = $(this).closest('.js-task-list-container')
-
-  methods =
-    enable: enableTaskLists
-    disable: disableTaskLists
-
-  methods[method || 'enable']($container)
diff --git a/vendor/gitignore/Elm.gitignore b/vendor/gitignore/Elm.gitignore
index a594364e2c02e00643c10a556d507a7cef4afe4a..8b631e7de00937af125d4f143a50fb67c0c8c24c 100644
--- a/vendor/gitignore/Elm.gitignore
+++ b/vendor/gitignore/Elm.gitignore
@@ -1,4 +1,4 @@
 # elm-package generated files
-elm-stuff/
+elm-stuff
 # elm-repl generated files
 repl-temp-*
diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore
index daf913b1b347aae6de6f48d599bc89ef8c8693d6..cd0d5d1e2f4c7647a1bcca8b8927242c3831e6fb 100644
--- a/vendor/gitignore/Go.gitignore
+++ b/vendor/gitignore/Go.gitignore
@@ -22,3 +22,6 @@ _testmain.go
 *.exe
 *.test
 *.prof
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore
index 47fed6c20d9b87dfe78dc4817e198584cfa5f947..a9fe6fba80d90b63aee464ecfd89ea59f697c6e1 100644
--- a/vendor/gitignore/Leiningen.gitignore
+++ b/vendor/gitignore/Leiningen.gitignore
@@ -1,6 +1,7 @@
 pom.xml
 pom.xml.asc
-*jar
+*.jar
+*.class
 /lib/
 /classes/
 /target/
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 86f21d8e0ff7635d06025bddcae1cee3d7cd378f..20592083931a5924e8891b6e9916bd3b51b062bd 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -52,7 +52,7 @@ Carthage/Build
 fastlane/report.xml
 fastlane/screenshots
 
-#Code Injection
+# Code Injection
 #
 # After new code Injection tools there's a generated folder /iOSInjectionProject
 # https://github.com/johnno1962/injectionforxcode
diff --git a/vendor/gitignore/Scala.gitignore b/vendor/gitignore/Scala.gitignore
index c58d83b318909082a44d3a84222b74607d1268f8..a02d882cb88407d93f96cc647944b17408dcec7f 100644
--- a/vendor/gitignore/Scala.gitignore
+++ b/vendor/gitignore/Scala.gitignore
@@ -15,3 +15,7 @@ project/plugins/project/
 # Scala-IDE specific
 .scala_dependencies
 .worksheet
+
+# ENSIME specific
+.ensime_cache/
+.ensime
diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore
index 842c3ec518bf64ba78621449b076c919b4d5d98c..e9270205fd565f86bb2c667f0b82281b24cfaff1 100644
--- a/vendor/gitignore/SugarCRM.gitignore
+++ b/vendor/gitignore/SugarCRM.gitignore
@@ -7,6 +7,7 @@
 # For development the cache directory can be safely ignored and
 # therefore it is ignored.
 /cache/
+!/cache/index.html
 # Ignore some files and directories from the custom directory.
 /custom/history/
 /custom/modulebuilder/
@@ -22,4 +23,5 @@
 *.log
 # Ignore the new upload directories.
 /upload/
+!/upload/index.html
 /upload_backup/
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 3cb097c9d5e5d3cb7b63393d7e1ddb8b999b2e28..34f999df3e7e645e84f4dcea7e19ee84cf17cc31 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -19,6 +19,9 @@
 # *.eps
 # *.pdf
 
+## Generated if empty string is given at "Please type another file name for output:"
+.pdf
+
 ## Bibliography auxiliary files (bibtex/biblatex/biber):
 *.bbl
 *.bcf
@@ -31,6 +34,7 @@
 ## Build tool auxiliary files:
 *.fdb_latexmk
 *.synctex
+*.synctex(busy)
 *.synctex.gz
 *.synctex.gz(busy)
 *.pdfsync
@@ -84,6 +88,10 @@ acs-*.bib
 # gnuplottex
 *-gnuplottex-*
 
+# gregoriotex
+*.gaux
+*.gtex
+
 # hyperref
 *.brf
 
@@ -128,6 +136,9 @@ _minted*
 *.sagetex.py
 *.sagetex.scmd
 
+# scrwfile
+*.wrt
+
 # sympy
 *.sout
 *.sympy
diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore
index 7868d16d216f4aefc9a5ae335451af508629402f..41859c81f1c272fa7acf5f2bf5d67e1cb20cd3df 100644
--- a/vendor/gitignore/Terraform.gitignore
+++ b/vendor/gitignore/Terraform.gitignore
@@ -1,3 +1,6 @@
 # Compiled files
 *.tfstate
 *.tfstate.backup
+
+# Module directory
+.terraform/
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 5aafcbb7f1d4b3f02e1c63c1ce1693aa2b5a9cc8..1c10388911b354f39ac498923ad7c1ea5925fdbf 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -5,8 +5,9 @@
 /[Bb]uilds/
 /Assets/AssetStoreTools*
 
-# Autogenerated VS/MD solution and project files
+# Autogenerated VS/MD/Consulo solution and project files
 ExportedObj/
+.consulo/
 *.csproj
 *.unityproj
 *.sln
diff --git a/vendor/gitlab-ci-yml/C++.gitlab-ci.yml b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c83c49d8c950314bc5a40fa1e5080247f3307656
--- /dev/null
+++ b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml
@@ -0,0 +1,26 @@
+# use the official gcc image, based on debian
+# can use verions as well, like gcc:5.2
+# see https://hub.docker.com/_/gcc/
+image: gcc
+
+build:
+  stage: build
+  # instead of calling g++ directly you can also use some build toolkit like make
+  # install the necessary build tools when needed
+  # before_script: 
+  #   - apt update && apt -y install make autoconf 
+  script: 
+    - g++ helloworld.cpp -o mybinary
+  artifacts:
+    paths:
+      - mybinary
+  # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
+  # cache:
+  #   paths:
+  #     - "*.o"
+
+# run tests using the binary built before
+test:
+  stage: test
+  script:
+    - ./runmytests.sh
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
index 0b329aaf1c419971df5787a2502149718fc4e471..00f9541e89bc9c0629bd4547d74f37a56ba0e843 100644
--- a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -2,7 +2,7 @@
 # The image already has Hex installed. You might want to consider to use `elixir:latest`
 image: trenpixster/elixir:latest
 
-# Pic zero or more services to be used on all builds.
+# Pick zero or more services to be used on all builds.
 # Only needed when using a docker container to run your tests in.
 # Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
 services:
diff --git a/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7fc698d50cf62833219efe9e1e649cbc7ab73dca
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml
@@ -0,0 +1,40 @@
+# This template uses the java:8 docker image because there isn't any
+# official Grails image at this moment
+#
+# Grails Framework https://grails.org/ is a powerful Groovy-based web application framework for the JVM
+#
+# This yml works with Grails 3.x only
+# Feel free to change GRAILS_VERSION version with your project version (3.0.1, 3.1.1,...)
+# Feel free to change GRADLE_VERSION version with your gradle project version (2.13, 2.14,...)
+# If you use Angular profile, this yml it's prepared to work with it
+
+image: java:8
+
+variables:
+  GRAILS_VERSION: "3.1.9"
+  GRADLE_VERSION: "2.13"
+  
+# We use SDKMan as tool for managing versions
+before_script:
+   - apt-get update -qq && apt-get install -y -qq unzip
+   - curl -sSL https://get.sdkman.io | bash
+   - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+   - source /root/.sdkman/bin/sdkman-init.sh
+   - sdk install gradle $GRADLE_VERSION < /dev/null
+   - sdk use gradle $GRADLE_VERSION
+# As it's not a good idea to version gradle.properties feel free to add your
+# environments variable here   
+   - echo grailsVersion=$GRAILS_VERSION > gradle.properties
+   - echo gradleWrapperVersion=2.14 >> gradle.properties
+# refresh dependencies from your project   
+   - ./gradlew --refresh-dependencies
+# Be aware that if you are using Angular profile,
+# Bower cannot be run as root if you don't allow it before.
+# Feel free to remove next line if you are not using Bower
+   - echo {\"allow_root\":true} > /root/.bowerrc
+
+# This build job does the full grails pipeline
+# (compile, test, integrationTest, war, assemble).
+build:
+ script: 
+   - ./gradlew build
\ No newline at end of file
diff --git a/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a4aed36889ee1995c14ef30273fedc8d3a680fff
--- /dev/null
+++ b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml
@@ -0,0 +1,11 @@
+# use docker image with latex preinstalled
+# since there is no official latex image, use https://github.com/blang/latex-docker
+# possible alternative: https://github.com/natlownes/docker-latex
+image: blang/latex
+
+build:
+  script:
+    - latexmk -pdf
+  artifacts:
+    paths:
+      - "*.pdf"
diff --git a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc36a4e6966c5d47955cb7cb2bf03dada3327bac
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
@@ -0,0 +1,32 @@
+# This template uses the java:8 docker image because there isn't any
+# official JBake image at this moment
+#
+# JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers
+#
+# This yml works with jBake 2.4.0
+# Feel free to change JBAKE_VERSION version 
+#
+# HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/
+
+image: java:8
+
+variables:
+    JBAKE_VERSION: 2.4.0
+
+
+# We use SDKMan as tool for managing versions
+before_script:
+   - apt-get update -qq && apt-get install -y -qq unzip
+   - curl -sSL https://get.sdkman.io | bash
+   - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+   - source /root/.sdkman/bin/sdkman-init.sh
+   - sdk install jbake $JBAKE_VERSION < /dev/null
+   - sdk use jbake $JBAKE_VERSION
+
+# This build job produced the output directory of your site
+pages:
+ script:
+ - jbake . public
+ artifacts:
+   paths:
+   - public
\ No newline at end of file
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 2a761bbd127e3469f89c4066de985f4f2f09c12d..16a685ee03d6c43a798fd72d4e2c6c64ec691df6 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -19,6 +19,8 @@ cache:
 # services such as redis or postgres
 before_script:
   - ruby -v                                   # Print out ruby version for debugging
+  # Uncomment next line if your rails app needs a JS runtime:
+  # - apt-get update -q && apt-get install nodejs -yqq
   - gem install bundler  --no-ri --no-rdoc    # Bundler is not installed with the image
   - bundle install -j $(nproc) --path vendor  # Install dependencies into ./vendor/ruby